Full Code of podlove/podlove-publisher for AI

master 5fb91bd0fbff cached
736 files
3.8 MB
1.0M tokens
3383 symbols
1 requests
Download .txt
Showing preview only (4,146K chars total). Download the full file or copy to clipboard to get everything.
Repository: podlove/podlove-publisher
Branch: master
Commit: 5fb91bd0fbff
Files: 736
Total size: 3.8 MB

Directory structure:
gitextract_4vqhqypb/

├── .distignore
├── .dockerignore
├── .editorconfig
├── .github/
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE.md
│   └── workflows/
│       ├── docker-image.yml
│       ├── release-beta.yml
│       ├── release-wordpress.yml
│       └── tests.yml
├── .gitignore
├── .gitmodules
├── .php-cs-fixer.dist.php
├── .prettierrc
├── .wp-env.json
├── .wp-env.test.json
├── .zed/
│   └── settings.json
├── AGENTS.md
├── Dockerfile
├── Makefile
├── README.md
├── bin/
│   ├── code-coverage.sh
│   ├── docker-entry.sh
│   ├── docker-setup.sh
│   ├── release.sh
│   ├── remove-tunnel.sh
│   ├── reset-nux.sh
│   ├── template_ref.erb
│   ├── template_ref.rb
│   ├── template_ref_json.php
│   ├── uadetect.php
│   ├── update-opawg.sh
│   ├── update_pwp4.sh
│   ├── workspace.js
│   └── wp-env-test-after-start.js
├── bootstrap/
│   ├── autoload.php
│   ├── bootstrap.php
│   └── constants.php
├── changelog.txt
├── client/
│   ├── .tool-versions
│   ├── config.local.template.js
│   ├── index.html
│   ├── package.json
│   ├── postcss.config.js
│   ├── src/
│   │   ├── assets/
│   │   │   └── index.d.ts
│   │   ├── client.ts
│   │   ├── components/
│   │   │   ├── button/
│   │   │   │   └── Button.vue
│   │   │   ├── combobox/
│   │   │   │   └── Combobox.vue
│   │   │   ├── icons/
│   │   │   │   └── Avatar.vue
│   │   │   ├── modal/
│   │   │   │   └── Modal.vue
│   │   │   ├── module/
│   │   │   │   └── Module.vue
│   │   │   ├── popover/
│   │   │   │   └── Popover.vue
│   │   │   ├── steps/
│   │   │   │   └── Steps.vue
│   │   │   ├── tabs/
│   │   │   │   ├── Tab.vue
│   │   │   │   ├── TabsContainer.vue
│   │   │   │   └── index.ts
│   │   │   ├── tag/
│   │   │   │   └── Tag.vue
│   │   │   └── tooltip/
│   │   │       └── Tooltip.vue
│   │   ├── lib/
│   │   │   ├── api.ts
│   │   │   ├── array.ts
│   │   │   ├── auphonic.api.ts
│   │   │   ├── chapters.ts
│   │   │   ├── errorHandling.ts
│   │   │   ├── license.ts
│   │   │   ├── normalplaytime.ts
│   │   │   ├── popper.ts
│   │   │   ├── statusHelpers.ts
│   │   │   ├── timestamp.ts
│   │   │   └── wordpress.ts
│   │   ├── modules/
│   │   │   ├── auphonic/
│   │   │   │   ├── Auphonic.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── FileChooser.vue
│   │   │   │   │   ├── Logo.vue
│   │   │   │   │   ├── ManageProductionForm.vue
│   │   │   │   │   ├── SelectPreset.vue
│   │   │   │   │   ├── SelectProduction.vue
│   │   │   │   │   ├── StartScreen.vue
│   │   │   │   │   ├── WebhookToggle.vue
│   │   │   │   │   └── production_form/
│   │   │   │   │       ├── DonePage.vue
│   │   │   │   │       ├── PlusTransferStatus.vue
│   │   │   │   │       ├── TransferFileItem.vue
│   │   │   │   │       ├── TransferFileList.vue
│   │   │   │   │       ├── TransferHeader.vue
│   │   │   │   │       └── TransferStatusPanel.vue
│   │   │   │   └── index.ts
│   │   │   ├── chapters/
│   │   │   │   ├── Chapters.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── Export.vue
│   │   │   │   │   ├── Form.vue
│   │   │   │   │   └── Import.vue
│   │   │   │   └── index.ts
│   │   │   ├── contributors/
│   │   │   │   ├── Contributors.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── AddContribution.vue
│   │   │   │   │   └── Contribution.vue
│   │   │   │   └── index.ts
│   │   │   ├── description/
│   │   │   │   ├── Description.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── EpisodeContent.vue
│   │   │   │   │   ├── EpisodeNumber.vue
│   │   │   │   │   ├── EpisodePoster.vue
│   │   │   │   │   ├── EpisodeSubtitle.vue
│   │   │   │   │   ├── EpisodeSummary.vue
│   │   │   │   │   ├── EpisodeTitle.vue
│   │   │   │   │   └── EpisodeType.vue
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── license/
│   │   │   │   ├── License.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── LicenseName.vue
│   │   │   │   │   ├── LicenseSelector.vue
│   │   │   │   │   ├── LicenseSelectorButton.vue
│   │   │   │   │   ├── LicenseUrl.vue
│   │   │   │   │   └── LicenseView.vue
│   │   │   │   └── index.ts
│   │   │   ├── mediafiles/
│   │   │   │   ├── MediaFiles.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── AssetsEmptyState.vue
│   │   │   │   │   ├── AssetsTable.vue
│   │   │   │   │   ├── MediaSlug.vue
│   │   │   │   │   ├── MediaUpload.vue
│   │   │   │   │   └── PlusMediaUpload.vue
│   │   │   │   └── index.ts
│   │   │   ├── plus_features/
│   │   │   │   ├── Feature.vue
│   │   │   │   ├── PlusFeatures.vue
│   │   │   │   └── index.ts
│   │   │   ├── plus_file_migration/
│   │   │   │   ├── PlusFileMigration.vue
│   │   │   │   └── index.ts
│   │   │   ├── plus_token/
│   │   │   │   ├── PlusToken.vue
│   │   │   │   ├── TokenInput.vue
│   │   │   │   └── index.ts
│   │   │   ├── related/
│   │   │   │   ├── RelatedEpisodes.vue
│   │   │   │   └── index.ts
│   │   │   ├── shows/
│   │   │   │   ├── ShowSelect.vue
│   │   │   │   └── index.ts
│   │   │   ├── soundbite/
│   │   │   │   ├── Soundbite.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── Clear.vue
│   │   │   │   │   └── Form.vue
│   │   │   │   └── index.ts
│   │   │   └── transcripts/
│   │   │       ├── Transcripts.vue
│   │   │       ├── components/
│   │   │       │   ├── Delete.vue
│   │   │       │   ├── Export.vue
│   │   │       │   ├── Import.vue
│   │   │       │   ├── List.vue
│   │   │       │   └── Voices.vue
│   │   │       └── index.ts
│   │   ├── plugins/
│   │   │   └── translations.ts
│   │   ├── sagas/
│   │   │   ├── admin.sagas.ts
│   │   │   ├── api.ts
│   │   │   ├── auphonic.api.ts
│   │   │   ├── auphonic.sagas.ts
│   │   │   ├── chapters.sagas.ts
│   │   │   ├── contributors.sagas.ts
│   │   │   ├── episode.sagas.ts
│   │   │   ├── helper.ts
│   │   │   ├── lifecycle.sagas.ts
│   │   │   ├── mediafiles.duration.sagas.ts
│   │   │   ├── mediafiles.enable.sagas.ts
│   │   │   ├── mediafiles.fileselection.sagas.ts
│   │   │   ├── mediafiles.sagas.ts
│   │   │   ├── mediafiles.slug.sagas.ts
│   │   │   ├── mediafiles.upload.sagas.ts
│   │   │   ├── mediafiles.verification.sagas.ts
│   │   │   ├── notification.saga.ts
│   │   │   ├── plus.sagas.ts
│   │   │   ├── plusFileMigration.sagas.ts
│   │   │   ├── podcast.sagas.ts
│   │   │   ├── relatedEpisodes.sagas.ts
│   │   │   ├── shows.sagas.ts
│   │   │   ├── transcripts.sagas.ts
│   │   │   └── wordpress.sagas.ts
│   │   ├── store/
│   │   │   ├── admin.store.ts
│   │   │   ├── auphonic.store.ts
│   │   │   ├── chapters.store.ts
│   │   │   ├── contributors.store.ts
│   │   │   ├── episode.store.ts
│   │   │   ├── index.ts
│   │   │   ├── lifecycle.store.ts
│   │   │   ├── mediafiles.store.ts
│   │   │   ├── notification.store.ts
│   │   │   ├── plus.store.ts
│   │   │   ├── plusFileMigration.store.ts
│   │   │   ├── podcast.store.ts
│   │   │   ├── post.store.ts
│   │   │   ├── progress.store.ts
│   │   │   ├── reducers.ts
│   │   │   ├── relatedEpisodes.store.ts
│   │   │   ├── runtime.store.ts
│   │   │   ├── selectors.ts
│   │   │   ├── settings.store.ts
│   │   │   ├── shows.store.ts
│   │   │   ├── transcripts.store.ts
│   │   │   ├── vue.ts
│   │   │   └── wordpress.store.ts
│   │   ├── style.css
│   │   ├── types/
│   │   │   ├── chapters.types.ts
│   │   │   ├── contributors.types.ts
│   │   │   ├── episode.types.ts
│   │   │   ├── license.types.ts
│   │   │   ├── relatedEpisodes.types.ts
│   │   │   ├── shows.types.ts
│   │   │   └── transcripts.types.ts
│   │   └── vue-shims.d.ts
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   ├── typings/
│   │   ├── podlove.d.ts
│   │   └── redux-actions.d.ts
│   └── vite.config.js
├── composer.json
├── config/
│   └── php-scoper/
│       ├── matomo.inc.php
│       ├── monolog.inc.php
│       ├── piwik.inc.php
│       ├── psr.inc.php
│       └── twig.inc.php
├── css/
│   ├── about.css
│   ├── admin-font.css
│   ├── admin.css
│   ├── dc.css
│   └── frontend.css
├── data/
│   ├── .gitkeep
│   ├── opawg.json
│   └── podlove_v2_schema.json
├── devbox.d/
│   └── php/
│       ├── php-fpm.conf
│       └── php.ini
├── devbox.json
├── docker-compose.yml
├── includes/
│   ├── about.php
│   ├── api/
│   │   ├── admin/
│   │   │   ├── onboarding.php
│   │   │   └── plus.php
│   │   ├── analytics.php
│   │   ├── api.php
│   │   ├── chapters.php
│   │   ├── episodes/
│   │   │   ├── contributions.php
│   │   │   └── related_episodes.php
│   │   ├── episodes.php
│   │   ├── feeds.php
│   │   ├── podcast.php
│   │   ├── show.php
│   │   └── tools.php
│   ├── auto_post_titles.php
│   ├── cache.php
│   ├── capabilities.php
│   ├── chapters.php
│   ├── compatibility.php
│   ├── db_migration.php
│   ├── deprecations.php
│   ├── detect_duplicate_slugs.php
│   ├── donation_banner.html.php
│   ├── donation_banner.img.src
│   ├── donation_banner.php
│   ├── downloads.php
│   ├── episode_number_column.php
│   ├── episode_number_quick_edit_form.php
│   ├── explicit_content.php
│   ├── extras.php
│   ├── feed_discovery.php
│   ├── frontend_styles.php
│   ├── http.php
│   ├── images.php
│   ├── import.php
│   ├── jetpack.php
│   ├── license.php
│   ├── merge_episodes.php
│   ├── modules.php
│   ├── no_enclosure_autodiscovery.php
│   ├── permalinks.php
│   ├── podlove-web-player-5.php
│   ├── podlove_data_js_adapter.php
│   ├── recording_date.php
│   ├── redirects.php
│   ├── request_id_rehash.php
│   ├── require_curl.php
│   ├── screen_options.php
│   ├── scripts_and_styles.php
│   ├── search.php
│   ├── setup.php
│   ├── setup_wizard.php
│   ├── system_report.php
│   ├── template_pages.php
│   ├── templates.php
│   ├── theme_helper.php
│   ├── trash.php
│   ├── verify_itunes_category.php
│   ├── webhooks.php
│   └── wp_rocket.php
├── js/
│   ├── .tool-versions
│   ├── admin/
│   │   ├── ace/
│   │   │   ├── ace.js
│   │   │   ├── mode-twig.js
│   │   │   ├── theme-chrome.js
│   │   │   └── theme-github.js
│   │   ├── chosen/
│   │   │   ├── chosenImage.css
│   │   │   └── chosenImage.jquery.js
│   │   ├── cornify.js
│   │   ├── dc.js
│   │   ├── jquery-ui/
│   │   │   └── css/
│   │   │       └── smoothness/
│   │   │           └── jquery-ui.css
│   │   ├── spectrum/
│   │   │   ├── spectrum.css
│   │   │   └── spectrum.js
│   │   ├── template.js
│   │   └── tools/
│   │       └── useragent.js
│   ├── package.json
│   ├── src/
│   │   ├── admin/
│   │   │   ├── dashboard_asset_validation.js
│   │   │   ├── dashboard_feed_validation.js
│   │   │   ├── episode.js
│   │   │   ├── episode_asset_settings.js
│   │   │   ├── feed_settings.js
│   │   │   ├── jobs.js
│   │   │   ├── jquery.count_characters.js
│   │   │   ├── license.js
│   │   │   ├── md5.js
│   │   │   ├── media.js
│   │   │   ├── podlove_data_table.js
│   │   │   ├── post_title_autogenerate.js
│   │   │   ├── protected_feed.js
│   │   │   └── timeago.jquery.js
│   │   ├── admin.js
│   │   ├── analytics/
│   │   │   ├── common.js
│   │   │   ├── episode.js
│   │   │   └── totals.js
│   │   ├── app.js
│   │   ├── components/
│   │   │   ├── AnalyticsDatePicker.vue
│   │   │   ├── JobsDashboard.vue
│   │   │   ├── Shownotes.vue
│   │   │   ├── ShownotesEntry.vue
│   │   │   ├── Slacknotes.vue
│   │   │   ├── icons/
│   │   │   │   ├── CheveronDown.vue
│   │   │   │   ├── CheveronUp.vue
│   │   │   │   ├── Close.vue
│   │   │   │   ├── DotsVertical.vue
│   │   │   │   ├── Edit.vue
│   │   │   │   ├── Eye.vue
│   │   │   │   ├── EyeOff.vue
│   │   │   │   ├── Image.vue
│   │   │   │   ├── Link.vue
│   │   │   │   ├── Menu.vue
│   │   │   │   ├── Refresh.vue
│   │   │   │   └── Type.vue
│   │   │   ├── shownotes/
│   │   │   │   ├── link-compact.vue
│   │   │   │   ├── link-unfurling.vue
│   │   │   │   ├── link.vue
│   │   │   │   ├── sn-button.vue
│   │   │   │   ├── sn-card.vue
│   │   │   │   ├── suggestion.vue
│   │   │   │   └── topic.vue
│   │   │   └── temp.xml
│   │   └── lib/
│   │       ├── duration_errors.js
│   │       ├── guid.js
│   │       └── timestamp.js
│   └── webpack.mix.js
├── lib/
│   ├── ajax/
│   │   ├── ajax.analytics_global_total_downloads_by_show.html.php
│   │   ├── ajax.php
│   │   ├── file_controller.php
│   │   └── template_controller.php
│   ├── analytics/
│   │   ├── download_intent_cleanup.php
│   │   ├── download_sums_calculator.php
│   │   ├── episode_download_average.php
│   │   └── salt_shaker.php
│   ├── api/
│   │   ├── error.php
│   │   ├── permissions.php
│   │   ├── response.php
│   │   └── validation.php
│   ├── authentication.php
│   ├── cache/
│   │   ├── http_header_validator.php
│   │   └── template_cache.php
│   ├── chapters_manager.php
│   ├── comment/
│   │   └── comment.php
│   ├── cron.php
│   ├── custom_guid.php
│   ├── delete_head_requests.php
│   ├── dom_document_fragment.php
│   ├── downloads.php
│   ├── downloads_list_data.php
│   ├── downloads_list_table.php
│   ├── duplicate_post.php
│   ├── duration.php
│   ├── episode_asset_list_table.php
│   ├── feed_list_table.php
│   ├── feeds/
│   │   ├── base.php
│   │   ├── chapters.php
│   │   └── rss.php
│   ├── feeds.php
│   ├── file_type_list_table.php
│   ├── form/
│   │   └── input/
│   │       ├── builder.php
│   │       ├── div_wrapper.php
│   │       ├── table_wrapper.php
│   │       └── wrapper.php
│   ├── geo_ip.php
│   ├── has_page_documentation_trait.php
│   ├── helper.php
│   ├── http/
│   │   └── curl.php
│   ├── jobs/
│   │   ├── counting_job.php
│   │   ├── cron_job_runner.php
│   │   ├── download_intent_cleanup_job.php
│   │   ├── download_timed_aggregator_job.php
│   │   ├── job_cleaner.php
│   │   ├── job_trait.php
│   │   ├── request_id_rehash_job.php
│   │   ├── tools_section.php
│   │   ├── tools_section_cron_diagnostics.php
│   │   └── user_agent_refresh_job.php
│   ├── list_table.php
│   ├── log.php
│   ├── model/
│   │   ├── asset_assignment.php
│   │   ├── base.php
│   │   ├── download_intent.php
│   │   ├── download_intent_clean.php
│   │   ├── episode.php
│   │   ├── episode_asset.php
│   │   ├── feed.php
│   │   ├── file_type.php
│   │   ├── geo_area.php
│   │   ├── geo_area_name.php
│   │   ├── image.php
│   │   ├── job.php
│   │   ├── keeps_blog_reference_trait.php
│   │   ├── licensable.php
│   │   ├── license.php
│   │   ├── media_file.php
│   │   ├── network_trait.php
│   │   ├── podcast.php
│   │   ├── template.php
│   │   ├── template_assignment.php
│   │   └── user_agent.php
│   ├── modules/
│   │   ├── affiliate/
│   │   │   ├── affiliate.php
│   │   │   └── podcast_affiliate_settings_tab.php
│   │   ├── analytics_heartbeat/
│   │   │   ├── analytics_heartbeat.php
│   │   │   └── model/
│   │   │       └── heartbeat.php
│   │   ├── asset_validation/
│   │   │   └── asset_validation.php
│   │   ├── auphonic/
│   │   │   ├── api_wrapper.php
│   │   │   ├── auphonic.php
│   │   │   ├── episode_enhancer.php
│   │   │   ├── plus_file_transfer.php
│   │   │   └── rest_api.php
│   │   ├── automatic_numbering/
│   │   │   └── automatic_numbering.php
│   │   ├── base.php
│   │   ├── categories/
│   │   │   └── categories.php
│   │   ├── contributors/
│   │   │   ├── contributor_group_list_table.php
│   │   │   ├── contributor_list_table.php
│   │   │   ├── contributor_repair.php
│   │   │   ├── contributor_role_list_table.php
│   │   │   ├── contributors.php
│   │   │   ├── gender_stats.php
│   │   │   ├── jobs/
│   │   │   │   ├── podcast_import_contributor_episode_contributions_job.php
│   │   │   │   ├── podcast_import_contributor_groups_job.php
│   │   │   │   ├── podcast_import_contributor_roles_job.php
│   │   │   │   ├── podcast_import_contributor_show_contributions_job.php
│   │   │   │   └── podcast_import_contributors_job.php
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── model/
│   │   │   │   ├── contribution_gender_statistics.php
│   │   │   │   ├── contributor.php
│   │   │   │   ├── contributor_group.php
│   │   │   │   ├── contributor_role.php
│   │   │   │   ├── default_contribution.php
│   │   │   │   ├── episode_contribution.php
│   │   │   │   └── show_contribution.php
│   │   │   ├── rest_api.php
│   │   │   ├── settings/
│   │   │   │   ├── contributor_defaults.php
│   │   │   │   ├── contributor_settings.php
│   │   │   │   ├── generic_entity_settings.php
│   │   │   │   ├── podcast_contributors_settings_tab.php
│   │   │   │   └── tab/
│   │   │   │       ├── contributors.php
│   │   │   │       ├── defaults.php
│   │   │   │       ├── groups.php
│   │   │   │       └── roles.php
│   │   │   ├── shortcodes.php
│   │   │   ├── template/
│   │   │   │   ├── avatar.php
│   │   │   │   ├── contributor.php
│   │   │   │   └── contributor_group.php
│   │   │   ├── template_extensions.php
│   │   │   ├── templates/
│   │   │   │   ├── _contributor-table-flattr.twig
│   │   │   │   ├── _contributor-table-row.twig
│   │   │   │   ├── avatar.twig
│   │   │   │   ├── contributor-comma-separated.twig
│   │   │   │   ├── contributor-list.twig
│   │   │   │   ├── contributor-table.twig
│   │   │   │   ├── podcast-contributor-list.twig
│   │   │   │   └── podcast-contributor-table.twig
│   │   │   └── views/
│   │   │       └── form_table.php
│   │   ├── external_analytics/
│   │   │   └── external_analytics.php
│   │   ├── fyyd/
│   │   │   └── fyyd.php
│   │   ├── import_export/
│   │   │   ├── export/
│   │   │   │   ├── podcast_exporter.php
│   │   │   │   └── tracking_exporter.php
│   │   │   ├── import/
│   │   │   │   ├── podcast_import_assets_job.php
│   │   │   │   ├── podcast_import_episodes_job.php
│   │   │   │   ├── podcast_import_feeds_job.php
│   │   │   │   ├── podcast_import_filetypes_job.php
│   │   │   │   ├── podcast_import_job_table_trait.php
│   │   │   │   ├── podcast_import_job_trait.php
│   │   │   │   ├── podcast_import_mediafiles_job.php
│   │   │   │   ├── podcast_import_options_job.php
│   │   │   │   ├── podcast_import_templates_job.php
│   │   │   │   ├── podcast_import_tracking_area_job.php
│   │   │   │   ├── podcast_import_tracking_area_name_job.php
│   │   │   │   ├── podcast_import_user_agents_job.php
│   │   │   │   ├── podcast_importer.php
│   │   │   │   ├── podcast_importer_job.php
│   │   │   │   ├── tracking_importer.php
│   │   │   │   └── tracking_importer_job.php
│   │   │   ├── import_export.php
│   │   │   └── js/
│   │   │       └── import.js
│   │   ├── logging/
│   │   │   ├── log_table.php
│   │   │   ├── logging.php
│   │   │   ├── wpdbhandler.php
│   │   │   └── wpmail_handler.php
│   │   ├── networks/
│   │   │   ├── admin_bar_menu.php
│   │   │   ├── css/
│   │   │   │   └── admin.css
│   │   │   ├── model/
│   │   │   │   ├── network.php
│   │   │   │   └── podcast_list.php
│   │   │   ├── networks.php
│   │   │   ├── podcast_list_list_table.php
│   │   │   ├── podcast_list_table.php
│   │   │   ├── settings/
│   │   │   │   ├── dashboard.php
│   │   │   │   ├── podcast_lists.php
│   │   │   │   └── templates.php
│   │   │   └── template/
│   │   │       ├── network.php
│   │   │       └── podcast_list.php
│   │   ├── notifications/
│   │   │   ├── mailer_job.php
│   │   │   ├── notifications.php
│   │   │   └── settings_tab.php
│   │   ├── oembed/
│   │   │   └── oembed.php
│   │   ├── onboarding/
│   │   │   ├── css/
│   │   │   │   └── podlove-onboarding-banner.css
│   │   │   ├── onboarding.php
│   │   │   ├── rest_api.php
│   │   │   └── settings/
│   │   │       └── onboarding_page.php
│   │   ├── open_graph/
│   │   │   └── open_graph.php
│   │   ├── plus/
│   │   │   ├── api.php
│   │   │   ├── banner.html.php
│   │   │   ├── banner.php
│   │   │   ├── early_file_hosting_banner.php
│   │   │   ├── feed_proxy.php
│   │   │   ├── feed_pusher.php
│   │   │   ├── file_storage.php
│   │   │   ├── global_feed_settings.php
│   │   │   ├── growth_banner.php
│   │   │   ├── plus.php
│   │   │   ├── promotion_coordinator.php
│   │   │   ├── rest_api.php
│   │   │   └── settings_page.php
│   │   ├── podlove_web_player/
│   │   │   ├── media_tag_renderer.php
│   │   │   ├── player_printer_interface.php
│   │   │   ├── player_v3/
│   │   │   │   └── player_media_files.php
│   │   │   ├── player_v4/
│   │   │   │   ├── html5printer.php
│   │   │   │   ├── module.php
│   │   │   │   └── pwp4.js
│   │   │   ├── player_v5/
│   │   │   │   └── module.php
│   │   │   ├── podigee/
│   │   │   │   ├── html5printer.php
│   │   │   │   └── module.php
│   │   │   └── podlove_web_player.php
│   │   ├── protected_feed/
│   │   │   └── protected_feed.php
│   │   ├── pubsubhubbub/
│   │   │   └── pubsubhubbub.php
│   │   ├── readme.md
│   │   ├── related_episodes/
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── model/
│   │   │   │   └── episode_relation.php
│   │   │   ├── related_episodes.php
│   │   │   ├── shortcodes.php
│   │   │   ├── template_extensions.php
│   │   │   └── templates/
│   │   │       └── related-episodes-list.twig
│   │   ├── seasons/
│   │   │   ├── css/
│   │   │   │   └── admin.css
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── model/
│   │   │   │   ├── season.php
│   │   │   │   ├── season_map.php
│   │   │   │   ├── seasons_issue.php
│   │   │   │   └── seasons_validator.php
│   │   │   ├── podcast_import_seasons_job.php
│   │   │   ├── seasons.php
│   │   │   ├── settings/
│   │   │   │   ├── help/
│   │   │   │   │   └── settings.php
│   │   │   │   ├── season_list_table.php
│   │   │   │   └── settings.php
│   │   │   ├── template/
│   │   │   │   └── season.php
│   │   │   └── template_extensions.php
│   │   ├── shownotes/
│   │   │   ├── model/
│   │   │   │   └── entry.php
│   │   │   ├── rest_api.php
│   │   │   ├── shownotes.php
│   │   │   ├── template/
│   │   │   │   └── entry.php
│   │   │   ├── template_extensions.php
│   │   │   └── twig/
│   │   │       ├── plain-html-list-grouped.twig
│   │   │       ├── plain-html-list.twig
│   │   │       └── shownotes.twig
│   │   ├── shows/
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── model/
│   │   │   │   └── show.php
│   │   │   ├── rest_api.php
│   │   │   ├── settings/
│   │   │   │   ├── help/
│   │   │   │   │   └── settings.php
│   │   │   │   ├── settings.php
│   │   │   │   └── show_list_table.php
│   │   │   ├── shows.php
│   │   │   ├── template/
│   │   │   │   └── show.php
│   │   │   └── template_extensions.php
│   │   ├── slack_shownotes/
│   │   │   ├── message.php
│   │   │   ├── settings/
│   │   │   │   └── settings.php
│   │   │   └── slack_shownotes.php
│   │   ├── social/
│   │   │   ├── admin.css
│   │   │   ├── data/
│   │   │   │   └── services.yml
│   │   │   ├── jobs/
│   │   │   │   ├── podcast_import_contributor_services_job.php
│   │   │   │   ├── podcast_import_services_job.php
│   │   │   │   └── podcast_import_show_services_job.php
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── model/
│   │   │   │   ├── contributor_service.php
│   │   │   │   ├── service.php
│   │   │   │   └── show_service.php
│   │   │   ├── repair_social.php
│   │   │   ├── rest_api.php
│   │   │   ├── settings/
│   │   │   │   ├── podcast_settings_donation_tab.php
│   │   │   │   └── podcast_settings_social_tab.php
│   │   │   ├── shortcodes.php
│   │   │   ├── social.php
│   │   │   ├── template/
│   │   │   │   └── service.php
│   │   │   ├── template_extensions.php
│   │   │   └── templates/
│   │   │       ├── podcast-donations-list.twig
│   │   │       └── podcast-social-media-list.twig
│   │   ├── soundbite/
│   │   │   └── soundbite.php
│   │   ├── subscribe_button/
│   │   │   ├── button.php
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── subscribe_button.php
│   │   │   ├── template_extensions.php
│   │   │   └── widget.php
│   │   ├── title_migration/
│   │   │   ├── notices.php
│   │   │   ├── state.php
│   │   │   └── title_migration.php
│   │   ├── transcripts/
│   │   │   ├── jobs/
│   │   │   │   ├── import_transcripts_job.php
│   │   │   │   └── import_voice_assignments_job.php
│   │   │   ├── model/
│   │   │   │   ├── transcript.php
│   │   │   │   └── voice_assignment.php
│   │   │   ├── renderer.php
│   │   │   ├── rest_api.php
│   │   │   ├── template/
│   │   │   │   ├── group.php
│   │   │   │   └── line.php
│   │   │   ├── template_extensions.php
│   │   │   ├── transcripts.php
│   │   │   └── twig/
│   │   │       └── transcript.twig
│   │   ├── widgets/
│   │   │   ├── widgets/
│   │   │   │   ├── podcast_information.php
│   │   │   │   ├── podcast_license.php
│   │   │   │   ├── recent_episodes.php
│   │   │   │   └── render_template.php
│   │   │   └── widgets.php
│   │   └── wordpress_file_upload/
│   │       └── wordpress_file_upload.php
│   ├── network.php
│   ├── php/
│   │   ├── array.php
│   │   └── string.php
│   ├── php_deprecation_warning.php
│   ├── podcast_post_meta_box.php
│   ├── podcast_post_type.php
│   ├── repair.php
│   ├── settings/
│   │   ├── analytics.php
│   │   ├── dashboard/
│   │   │   ├── about.php
│   │   │   ├── file_validation.php
│   │   │   ├── news.php
│   │   │   └── statistics.php
│   │   ├── dashboard.php
│   │   ├── episode_asset.php
│   │   ├── expert/
│   │   │   ├── tab/
│   │   │   │   ├── file_types.php
│   │   │   │   ├── metadata.php
│   │   │   │   ├── redirects.php
│   │   │   │   ├── tracking.php
│   │   │   │   ├── web_player.php
│   │   │   │   └── website.php
│   │   │   ├── tab.php
│   │   │   └── tabs.php
│   │   ├── feed.php
│   │   ├── file_type.php
│   │   ├── help/
│   │   │   ├── analytics.php
│   │   │   ├── dashboard.php
│   │   │   ├── episode_asset.php
│   │   │   ├── feed.php
│   │   │   ├── modules.php
│   │   │   ├── podcast.php
│   │   │   ├── settings.php
│   │   │   ├── support.php
│   │   │   └── templates.php
│   │   ├── modules.php
│   │   ├── podcast/
│   │   │   ├── tab/
│   │   │   │   ├── description.php
│   │   │   │   ├── directory.php
│   │   │   │   ├── license.php
│   │   │   │   ├── media.php
│   │   │   │   └── player.php
│   │   │   └── tab.php
│   │   ├── podcast.php
│   │   ├── settings.php
│   │   ├── support.php
│   │   ├── templates.php
│   │   ├── tools/
│   │   │   └── user_agent_refresh.php
│   │   └── tools.php
│   ├── shortcodes.php
│   ├── slug_freeze.php
│   ├── system_report.php
│   ├── template/
│   │   ├── asset.php
│   │   ├── category.php
│   │   ├── chapter.php
│   │   ├── date_time.php
│   │   ├── duration.php
│   │   ├── episode.php
│   │   ├── episode_title.php
│   │   ├── feed.php
│   │   ├── file.php
│   │   ├── file_type.php
│   │   ├── image.php
│   │   ├── license.php
│   │   ├── podcast.php
│   │   ├── tag.php
│   │   ├── twig_date_extension.php
│   │   ├── twig_filter.php
│   │   ├── twig_loader_podlove_database.php
│   │   ├── twig_sandbox.php
│   │   └── wrapper.php
│   ├── tools.php
│   ├── tracking/
│   │   └── debug.php
│   ├── version.php
│   └── webhook/
│       └── webhook.php
├── license.txt
├── mise.toml
├── package.json
├── phpunit.xml.dist
├── plugin.php
├── podlove.php
├── readme.txt
├── templates/
│   ├── feed-rss2.php
│   ├── license.twig
│   ├── network/
│   │   └── network-bar.twig
│   └── shortcode/
│       ├── downloads-buttons.twig
│       ├── downloads-select.twig
│       ├── episode-list.twig
│       └── feed-list.twig
├── tests/
│   └── phpunit/
│       ├── bootstrap.php
│       ├── helpers/
│       │   ├── EpisodeFactory.php
│       │   ├── db.php
│       │   └── module.php
│       ├── integration/
│       │   ├── FeedHtmlOptimizationTest.php
│       │   ├── ModuleUninstallTest.php
│       │   ├── PluginActivationTest.php
│       │   ├── PlusFeedProxyTest.php
│       │   ├── SeasonsTest.php
│       │   ├── SeasonsValidatorTest.php
│       │   └── SlackMessageTest.php
│       └── rest/
│           └── EpisodesApiTest.php
├── vetur.config.js
└── views/
    ├── expert_settings/
    │   └── website/
    │       ├── blog_post_title.php
    │       ├── custom_episode_slug.php
    │       ├── episode_archive.php
    │       └── landing_page.php
    └── settings/
        └── dashboard/
            ├── about.php
            ├── dashboard.php
            ├── file_validation.php
            ├── news.php
            └── statistics.php

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

================================================
FILE: .distignore
================================================
/.wordpress-org
/.git
/.github
/node_modules

.distignore
.gitignore

/.build
.php_cs.dist
deploy_key.enc
mix-manifest.json
package-lock.json
package.json
*.code-workspace
webpack.mix.js
/vendor/twig/twig

*.swp


================================================
FILE: .dockerignore
================================================
!/dist
vendor
vendor-prefixed


================================================
FILE: .editorconfig
================================================
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org

root = true


[*]

# Change these settings to your own preference
indent_style = space
indent_size = 2
max_line_length = 120

# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[*.html]
indent_size = 2

[Makefile]
indent_style = tab


================================================
FILE: .github/CONTRIBUTING.md
================================================
Thanks for reading our contribution guidelines!

* [Report a Bug](#report-bug)
* [Ask for Support](#request-support)
* [Theme Incompatibility](#theme-compat)
* [Help / Donate](#donate)

<a name=“report-bug”></a>
# Report a Bug

Something is not working?
Please follow the steps below to help us isolate the cause of error.

### Disable Podlove Cache

While testing, disable our internal cache. Put the following at the end of `wp-config.php`

```php
# wp-config.php
define('PODLOVE_TEMPLATE_CACHE', false);
```

### Disable other Caches

If you are using a caching plugin, please deactivate it. Examples for such plugins are:

- W3 Total Cache
- WP Super Cache
- Quick Cache

### Does it work when you use a default theme (like “twentyfifteen”)?

Sometimes themes change default WordPress behavior that breaks plugins. By testing your setup with a default theme, we can make sure it's not the themes fault.

### Does it work when you disable all plugins except the Publisher?

Just like the theme, other plugins might interfere with how the Publisher works.

### Now What?

You followed the steps above and the error still persists?
Create a [GitHub Issue](https://github.com/podlove/podlove-publisher/issues) if you haven't done so already, paste the output from your `Podlove ➜ Support` menu and mention that you have followed the steps above.

Thank you!

<a name=“request-support”></a>
# Ask for Support

We have a community forum for questions, answers and feature discussions at [community.podlove.org](https://community.podlove.org).

Please check if your questions are answered in our growing documentation site [docs.podlove.org](http://docs.podlove.org). If you still have open questions, feel free to open a support issue.

<a name="theme-compat"></a>
# Theme Incompatibility

Unfortunately, many themes are incompatible with [Custom Post Types](https://codex.wordpress.org/Post_Types), which the Podlove Publisher uses for episodes. When you encounter problems, **please go to the theme support first**.

Only if you are sure you encountered a theme-related bug in the Podlove Publisher, post here. Otherwise, ask for help in our [community](https://community.podlove.org).

<a name=“donate”></a>
# Donate

We are happy about every donation. Please visit [podlove.org/donations](http://podlove.org/donations/) for details.


================================================
FILE: .github/FUNDING.yml
================================================
open_collective: podlove


================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
### Expected behavior

### Actual behavior

### System information (see `Podlove > Support` menu)


================================================
FILE: .github/workflows/docker-image.yml
================================================
# https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#publishing-images-to-github-packages
name: Create and publish a Docker image

# Configures this workflow to run every time a change is pushed to the branch called `release`.
on:
  push:
    branches: ['beta', 'master']

# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
jobs:
  build-and-push-image:
    runs-on: ubuntu-latest
    # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
    permissions:
      contents: read
      packages: write
      attestations: write
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install devbox
        uses: jetify-com/devbox-install-action@v0.11.0

      - name: Bootstrap Podlove Publisher
        run: devbox run bootstrap

      - name: Build Podlove Publisher
        run: devbox run build

      - name: Log in to the Container registry
        uses: docker/login-action@v3.2.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@v5.5.1
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        id: push
        uses: docker/build-push-action@v6.1.0
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}



================================================
FILE: .github/workflows/release-beta.yml
================================================
on:
  push:
    # Sequence of patterns matched against refs/tags
    tags:
      - '*-beta*'

name: Beta Release

jobs:
  build:
    name: Build and Release Beta Version
    runs-on: ubuntu-24.04
    steps:
      - name: Setup PHP with PECL extension
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.0'
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Build project
        env:
          TAG_NAME: ${{ github.ref }}
        run: |
          make install_php_scoper
          make build
          mv dist podlove-podcasting-plugin-for-wordpress
          zip -r podlove-podcasting-plugin-for-wordpress.zip podlove-podcasting-plugin-for-wordpress
      - name: Create Release
        id: create_release
        uses: softprops/action-gh-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          name: ${{ github.ref }}
          draft: false
          prerelease: false
          files: |
            podlove-podcasting-plugin-for-wordpress.zip


================================================
FILE: .github/workflows/release-wordpress.yml
================================================
name: Release to WordPress.org
on:
  push:
    tags:
      - '*'
      - '!*-beta*'
jobs:
  tag:
    name: Build and Release to WordPress.org
    runs-on: ubuntu-24.04
    steps:
      - name: Setup PHP with PECL extension
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.0'
      - uses: actions/checkout@v3
      - name: Build
        run: |
          make install_php_scoper
          make build
          npm install fs-extra
          node bin/workspace.js
      - name: WordPress Plugin Deploy
        uses: 10up/action-wordpress-plugin-deploy@master
        env:
          SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
          SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
          SLUG: podlove-podcasting-plugin-for-wordpress


================================================
FILE: .github/workflows/tests.yml
================================================
name: Tests

on:
  pull_request:

jobs:
  phpunit:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.0'
          extensions: mbstring, xml

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Cache wp-env
        uses: actions/cache@v4
        with:
          path: ~/.wp-env
          key: ${{ runner.os }}-wp-env-${{ hashFiles('.wp-env.test.json') }}

      - name: Cache Composer
        uses: actions/cache@v4
        with:
          path: ~/.composer/cache
          key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}

      - name: Cache npm
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}

      - name: Install PHP dependencies (with prefixing)
        run: make install

      - name: Install PHP dev dependencies (phpunit)
        run: composer install --no-interaction --prefer-dist

      - name: Install Node dependencies
        run: npm install

      - name: Start wp-env test environment
        run: npm run wp-env:test:start

      - name: Run integration tests
        run: npm run test


================================================
FILE: .gitignore
================================================
.DS_Store
.tags*
.wordpress_release
*.sublime-*
*.code-workspace
wprelease.yml
composer.phar
node_modules
js/node_modules
client/node_modules
vendor/*
vendor-*/*
test/config.yml
doc
.htaccess
Mix.json
mix-manifest.json
*.log
.vscode
dist
js/dist
lib/modules/podlove_web_player/player_v2/
.php_cs.cache
.php-cs-fixer.cache
.build/wp*
/.vs
config.local.js
*.cache


================================================
FILE: .gitmodules
================================================


================================================
FILE: .php-cs-fixer.dist.php
================================================
<?php

$finder = PhpCsFixer\Finder::create()
    ->exclude('vendor')
    ->in(__DIR__)
;

$config = new PhpCsFixer\Config();

$c = $config->setRules([
    '@PSR2' => true,
    '@PhpCsFixer' => true,
    'yoda_style' => false,
    'fully_qualified_strict_types' => false,
    'array_syntax' => ['syntax' => 'short'],
    'trailing_comma_in_multiline' => false,
    'no_trailing_comma_in_singleline_array' => true,
    'blank_line_before_statement' => ['statements' => ['break', 'continue', 'declare', 'default', 'return', 'throw', 'try']],
    'visibility_required' => ['elements' => ['method', 'property']]
])
    ->setFinder($finder)
;

return $c;


================================================
FILE: .prettierrc
================================================
{
    "printWidth": 100,
    "jsxBracketSameLine": true,
    "semi": false,
    "singleQuote": true,
    "bracketSpacing": true,
    "tabWidth": 2,
    "useTabs": false
  }


================================================
FILE: .wp-env.json
================================================
{
  "plugins": ["."],
  "testsEnvironment": false,
  "config": {
    "WP_DEBUG": true,
    "WP_DEBUG_LOG": true,
    "WP_DEBUG_DISPLAY": false
  }
}


================================================
FILE: .wp-env.test.json
================================================
{
  "port": 8889,
  "testsEnvironment": false,
  "mappings": {
    "wp-content/plugins/podlove-podcasting-plugin-for-wordpress": "."
  },
  "lifecycleScripts": {
    "afterStart": "node bin/wp-env-test-after-start.js",
    "afterReset": "node bin/wp-env-test-after-start.js"
  },
  "config": {
    "WP_DEBUG": true,
    "WP_DEBUG_LOG": true,
    "WP_DEBUG_DISPLAY": false
  }
}


================================================
FILE: .zed/settings.json
================================================
{
  "languages": {
    "PHP": {
      "language_servers": [
        "phpactor",
        "!intelephense",
        "!phptools",
        "..."
      ],
      "formatter": [
        {
          "language_server": {
            "name": "phpactor"
          }
        }
      ],
      "format_on_save": "on"
    }
  },
  "lsp": {
    "phpactor": {
      "initialization_options": {
        "language_server_php_cs_fixer.enabled": true,
        "language_server_php_cs_fixer.bin": "%project_root%/vendor-bin/php-cs-fixer/vendor/friendsofphp/php-cs-fixer/php-cs-fixer",
        "language_server_php_cs_fixer.config": "%project_root%/.php-cs-fixer.dist.php"
      }
    }
  }
}


================================================
FILE: AGENTS.md
================================================
# Repository Guidelines

## Project Structure & Module Organization
- `podlove.php` and `plugin.php` are the plugin entry points.
- PHP source lives in `includes/` and `lib/`; templates in `templates/`; view helpers in `views/`.
- Client assets are split between legacy JS in `js/` and the newer app in `client/`.
- Static assets are in `css/`, `images/`, and `fonts/`.
- Tests live in `tests/phpunit/` with suites under `integration/` and `rest/`.
- Build artifacts are staged in `dist/` (generated by `make build`).

## Build, Test, and Development Commands
- `make install`: installs PHP tooling and prefixed dependencies (uses PHP-Scoper).
- `make format`: runs PHP-CS-Fixer to format PHP code.
- `npm run wp-env:start`: starts the local WordPress development environment on port 8888.
- `npm run wp-env:stop`: stops the development `wp-env` environment.
- `npm run wp-env:test:start`: starts the dedicated test `wp-env` environment on port 8889.
- `npm run wp-env:test:stop`: stops the dedicated test environment.
- `npm run test`: runs PHPUnit inside the dedicated test config's `cli` container (start it first).
- DB quick query (read-only): `npx wp-env run cli -- wp db query "SELECT * FROM wp_options LIMIT 5;"`
- Legacy JS dev: `cd js && npm install && npm run serve`.
- Client dev: `cd client && npm install && npm run dev` (set `WORDPRESS_URL=...` for isolated dev).
- Tool runtime: this repo defines tool versions in `mise.toml` (`php = 8.4`, `node = 25`). Prefer running PHP and other pinned tools through `mise`, for example `mise exec -- php -l path/to/file.php`, instead of assuming `php` is available on `PATH`.

## Coding Style & Naming Conventions
- Indentation: 2 spaces (see `.editorconfig`), LF line endings, max line length 120.
- PHP formatting is enforced with PHP-CS-Fixer (`.php-cs-fixer.dist.php`).
- Before completing a task, format any touched PHP files with the repo formatter. `make format` runs PHP-CS-Fixer for the whole repo; prefer the equivalent targeted command for only the files you changed, for example `mise exec -- vendor-bin/php-cs-fixer/vendor/friendsofphp/php-cs-fixer/php-cs-fixer fix path/to/file.php --config .php-cs-fixer.dist.php`.
- Use descriptive, WordPress-appropriate names for hooks and filters; keep filenames lowercase with underscores where applicable.

## Testing Guidelines
- PHPUnit is configured in `phpunit.xml.dist` and bootstraps via `tests/phpunit/bootstrap.php`.
- Integration and REST tests live under `tests/phpunit/integration/` and `tests/phpunit/rest/`.
- Run tests with `npm run wp-env:test:start` followed by `npm run test`.

## Commit & Pull Request Guidelines
- Commit messages follow a lightweight conventional style (examples: `feat: ...`, `fix: ...`, `chore: ...`, `change: ...`).
- Changelog entries belong in `readme.txt` under the `== Changelog ==` header.
- PRs should include a clear description, linked issues if applicable, and notes on how changes were tested (commands and environment).

## Configuration Tips
- Local WordPress uses `wp-env`; see `README.md` for ports and default credentials.
- For release builds, use `make build` to generate a clean `dist/` directory.
- Read-only database queries are allowed without asking for permission.
- If a required runtime is missing from `PATH`, use `mise exec -- ...` from the repository root so commands run with the versions declared in `mise.toml`.


================================================
FILE: Dockerfile
================================================
FROM wordpress:6-php8.1-apache

RUN apt-get update
RUN apt-get install zip default-mysql-client -y
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && chmod +x wp-cli.phar && mv wp-cli.phar /usr/local/bin/wp

WORKDIR /var/www/html

COPY ./bin/docker-entry.sh /usr/local/bin/entry.sh
COPY ./bin/docker-setup.sh /usr/local/bin/setup.sh
COPY ./dist wp-content/plugins/podlove-podcasting-plugin-for-wordpress

ENTRYPOINT ["entry.sh"]


================================================
FILE: Makefile
================================================
PHP_CS_FIXER = vendor-bin/php-cs-fixer/vendor/friendsofphp/php-cs-fixer/php-cs-fixer

release:
	bin/release.sh

format:
	$(PHP_CS_FIXER) fix . --config .php-cs-fixer.dist.php

validateFormat:
	$(PHP_CS_FIXER) fix . --config .php-cs-fixer.dist.php -v --dry-run --stop-on-violation --using-cache=no

update_subscribe_button:
	rm -rf .tmppsb
	git clone https://github.com/podlove/podlove-subscribe-button.git .tmppsb
	rm -rf lib/modules/subscribe_button/dist
	mv .tmppsb/dist lib/modules/subscribe_button/dist
	rm -rf .tmppsb

player:
	mkdir -p $(player_dst)/bin
	mkdir -p $(player_dst)/css
	mkdir -p $(player_dst)/img
	mkdir -p $(player_dst)/js/vendor
	cp -r $(player_src)/css/vendor $(player_dst)/css/vendor
	cp -r $(player_src)/img/* $(player_dst)/img
	cp -r $(player_src)/js/*.min.js $(player_dst)/js
	cp -r $(player_src)/js/vendor/*.min.js $(player_dst)/js/vendor

composer_with_prefixing:
	mkdir -p vendor-prefixed
	composer install --no-progress --prefer-dist --optimize-autoloader --no-dev
	composer prefix-dependencies
	rm -rf vendor/matomo
	rm -rf vendor/twig
	rm -rf vendor/monolog
	rm -rf vendor/psr
	composer dump-autoload --classmap-authoritative
	# composer install --no-progress --prefer-dist --optimize-autoloader --no-dev

install_php_scoper:
	mkdir -p vendor-prefixed
	composer require --dev bamarni/composer-bin-plugin:1.4.1
	composer bin php-scoper config minimum-stability dev
	composer bin php-scoper config prefer-stable true
	composer bin php-scoper require --dev --update-with-all-dependencies humbug/php-scoper:0.17.5

install_php_cs_fixer:
	composer bin php-cs-fixer install

client_legacy:
	cd js && npm install
	cd js && NODE_ENV=production npm run build

client_next:
	cd client && npm install
	cd client && NODE_ENV=production npm run build

client: client_legacy client_next

build:
	make composer_with_prefixing
	make client

	rm -rf dist/*
	mkdir -p dist
	# move everything into dist
	rsync -r --exclude=.git --exclude=node_modules --exclude=./dist . dist
	# cleanup
	find dist -name "*.git*" | xargs rm -rf
	rm -rf dist/lib/modules/podlove_web_player/player_v2/player/podlove-web-player/libs
	rm -rf dist/lib/modules/podlove_web_player/player_v2/player/podlove-web-player/img/banner-772x250.png
	rm -rf dist/lib/modules/podlove_web_player/player_v2/player/podlove-web-player/img/banner-1544x500.png
	rm -rf dist/client/src
	rm -rf dist/client/package-lock.json
	rm -rf dist/tests
	rm -rf dist/vendor-bin
	rm -rf dist/vendor/bin
	rm -rf dist/vendor/phpunit/php-code-coverage
	rm -rf dist/vendor/phpunit/phpunit
	rm -rf dist/vendor/phpunit/phpunit-mock-objects
	rm -rf dist/vendor/twig/twig/test
	rm -rf dist/vendor/guzzle/guzzle/tests
	rm -f dist/.travis.yml
	rm -rf dist/bin
	rm -f dist/wprelease.yml
	rm -f dist/CONTRIBUTING.md
	rm -f dist/Makefile
	rm -f dist/phpunit.xml
	rm -f dist/Rakefile
	rm -f dist/README.md
	rm -f dist/*.code-workspace
	rm -f dist/.prettierrc
	rm -f dist/.editorconfig
	rm -rf dist/devbox.d
	rm -f dist/devbox.json
	rm -f dist/devbox.lock
	rm -f dist/Dockerfile
	rm -f dist/docker-compose.yml
	find dist -name "*composer.json" | xargs rm -rf
	find dist -name "*composer.lock" | xargs rm -rf
	find dist -name "*.swp" | xargs rm -rf
	# find dist/vendor -type d -iname "test" | xargs rm -rf
	# find dist/vendor -type d -iname "tests" | xargs rm -rf
	# player v2 / mediaelement
	find dist -iname "echo-hereweare.*" | xargs rm -rf
	find dist -iname "*.jar" | xargs rm -rf


install: install_php_scoper install_php_cs_fixer composer_with_prefixing


================================================
FILE: README.md
================================================
# Podlove Podcast Publisher

This is the podcast publishing plugin for WordPress.

- [Getting Started & Documentation][6]
- [Podlove Community][9]
- Latest stable version: in [WordPress plugin directory][3]
- [Podlove Project & Blog][7]
- Report a bug: [Use GitHub Issues][5]

[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fpodlove%2Fpodlove-publisher.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fpodlove%2Fpodlove-publisher?ref=badge_shield)

## Development Setup

PHP dependencies are managed via [Composer](http://getcomposer.org/). So you need to clone the repository and then fetch the dependencies via Composer. JavaScript packages are managed with [yarn](https://yarnpkg.com/lang/en/).

Clone the publisher in the `wp-content/plugins` directory.

```
git clone --recursive https://github.com/podlove/podlove-publisher.git
cd podlove-publisher
curl -sS https://getcomposer.org/installer | php
make install
```

If you have a docker environment handy you can simply run:

```
make docker-install
```

## Development

### Legacy JS Development

1. Change your working direcetory to `js/`
2. Run `npm install`
3. Run `npm run serve` to start the development build
4. Go to your local Wordpress environment and see your changes

### Client Development

1. Create an [Wordpress application password](https://www.paidmembershipspro.com/create-application-password-wordpress/)
2. Update the authorization tokens in= `client/index.html`
3. Change your working directory to `client/`
4. Run `npm install`
5. For isolated development run `WORDPRESS_URL=http://podlove.local npm run dev` with your Wordpress environment
6. For integrated development run `npm run serve` and go to your local Wordpress environment and see your changes

## Testing

Integration tests use the official WordPress PHPUnit setup via `wp-env` with a dedicated isolated config.

Prerequisites:
- Docker is running
- Node.js + npm
- PHP + Composer

First-time setup:
```
composer install
composer bin php-scoper install
composer bin php-cs-fixer install
composer prefix-dependencies
npm install
npm run wp-env:test:start
```

## Local Development with wp-env

You can use `wp-env` to run a local WordPress instance with the plugin mounted from this repo.

Start the environment:
```
npm install
npm run wp-env:start
```

Open:
- Site: http://localhost:8888
- Admin: http://localhost:8888/wp-admin
- Default credentials: admin / password

Useful commands:
```
npm run wp-env:stop
npx wp-env destroy
npx wp-env run cli -- wp option get siteurl
```

Run integration tests:
```
npm run wp-env:test:start
npm run test
```

Start the dedicated test environment:
```
npm run wp-env:test:start
```

Open:
- Test site: http://localhost:8889

Useful test commands:
```
npm run wp-env:test:stop
npm run wp-env:test:destroy
npm run wp-env:test:logs
```

If `wp-env run` fails with a missing docker-compose file, the environment was not created yet or was cleaned up. Recreate the relevant environment with:
```
npx wp-env destroy
npm run wp-env:start
```

For the test environment use:
```
npm run wp-env:test:destroy
npm run wp-env:test:start
```

## Formatting Code

Use [PHP-CS-Fixer](https://github.com/FriendsOfPhp/PHP-CS-Fixer) to format code before committing.

You can do so manually via command line (`make format`) or configure your editor to format the file on save. For VS Code, use the "php cs fixer" extension by junstyle.

## Releases

Both beta and stable releases are creates with GitHub Actions.

To release a new stable version:

1. _manually_ update the following fields in `readme.txt`:
  - Tested up to
  - Stable tag
  - check that changelog has an entry
2. `bash bin/release.sh`, which does:
  - updates version in `podlove.php`
  - creates release commit
  - tags commit
3. git push

The GitHub action detects the release via the tag, builds it and submits it to the wordpress.org plugin directory.

[3]: https://wordpress.org/plugins/podlove-podcasting-plugin-for-wordpress/
[4]: https://trello.com/b/zB4mKQlD/podlove-publisher
[5]: https://github.com/podlove/podlove-publisher/issues
[6]: http://docs.podlove.org/
[7]: http://podlove.org/
[8]: https://github.com/podlove/podlove-publisher/releases
[9]: https://community.podlove.org/


## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fpodlove%2Fpodlove-publisher.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fpodlove%2Fpodlove-publisher?ref=badge_large)


================================================
FILE: bin/code-coverage.sh
================================================
phpunit --coverage-html=../../coverage/report
echo "Code Coverage Report: http://podlove-publisher.dev/wp-content/coverage/report/"


================================================
FILE: bin/docker-entry.sh
================================================
#!/usr/bin/env bash
set -Eeuo pipefail

# Waiting for the MySQL server to start
HOST=$(echo $WORDPRESS_DB_HOST | cut -d: -f1)
PORT=$(echo $WORDPRESS_DB_HOST | cut -d: -f2)

until mysql -h $HOST -P $PORT -D $WORDPRESS_DB_NAME -u $WORDPRESS_DB_USER -p$WORDPRESS_DB_PASSWORD -e '\q'; do
  >&2 echo "Mysql is unavailable - sleeping..."
  sleep 2
done

uid="$(id -u)"
gid="$(id -g)"

if [ "$uid" = '0' ]; then
  user="${APACHE_RUN_USER:-www-data}"
  group="${APACHE_RUN_GROUP:-www-data}"

  # strip off any '#' symbol ('#1000' is valid syntax for Apache)
  pound='#'
  user="${user#$pound}"
  group="${group#$pound}"

	if [ ! -e index.php ] && [ ! -e wp-includes/version.php ]; then
		# if the directory exists and WordPress doesn't appear to be installed AND the permissions of it are root:root, let's chown it (likely a Docker-created directory)
		if [ "$uid" = '0' ] && [ "$(stat -c '%u:%g' .)" = '0:0' ]; then
			chown "$user:$group" .
		fi

		echo >&2 "WordPress not found in $PWD - copying now..."
		if [ -n "$(find -mindepth 1 -maxdepth 1 -not -name wp-content)" ]; then
			echo >&2 "WARNING: $PWD is not empty! (copying anyhow)"
		fi
		sourceTarArgs=(
			--create
			--file -
			--directory /usr/src/wordpress
			--owner "$user" --group "$group"
		)
		targetTarArgs=(
			--extract
			--file -
		)
		if [ "$uid" != '0' ]; then
			# avoid "tar: .: Cannot utime: Operation not permitted" and "tar: .: Cannot change mode to rwxr-xr-x: Operation not permitted"
			targetTarArgs+=( --no-overwrite-dir )
		fi
		# loop over "pluggable" content in the source, and if it already exists in the destination, skip it
		# https://github.com/docker-library/wordpress/issues/506 ("wp-content" persisted, "akismet" updated, WordPress container restarted/recreated, "akismet" downgraded)
		for contentPath in \
			/usr/src/wordpress/.htaccess \
			/usr/src/wordpress/wp-content/*/*/ \
		; do
			contentPath="${contentPath%/}"
			[ -e "$contentPath" ] || continue
			contentPath="${contentPath#/usr/src/wordpress/}" # "wp-content/plugins/akismet", etc.
			if [ -e "$PWD/$contentPath" ]; then
				echo >&2 "WARNING: '$PWD/$contentPath' exists! (not copying the WordPress version)"
				sourceTarArgs+=( --exclude "./$contentPath" )
			fi
		done
		tar "${sourceTarArgs[@]}" . | tar "${targetTarArgs[@]}"
		echo >&2 "Complete! WordPress has been successfully copied to $PWD"
	fi

	wpEnvs=( "${!WORDPRESS_@}" )
	if [ ! -s wp-config.php ] && [ "${#wpEnvs[@]}" -gt 0 ]; then
		for wpConfigDocker in \
			wp-config-docker.php \
			/usr/src/wordpress/wp-config-docker.php \
		; do
			if [ -s "$wpConfigDocker" ]; then
				echo >&2 "No 'wp-config.php' found in $PWD, but 'WORDPRESS_...' variables supplied; copying '$wpConfigDocker' (${wpEnvs[*]})"
				# using "awk" to replace all instances of "put your unique phrase here" with a properly unique string (for AUTH_KEY and friends to have safe defaults if they aren't specified with environment variables)
				awk '
					/put your unique phrase here/ {
						cmd = "head -c1m /dev/urandom | sha1sum | cut -d\\  -f1"
						cmd | getline str
						close(cmd)
						gsub("put your unique phrase here", str)
					}
					{ print }
				' "$wpConfigDocker" > wp-config.php
				if [ "$uid" = '0' ]; then
					# attempt to ensure that wp-config.php is owned by the run user
					# could be on a filesystem that doesn't allow chown (like some NFS setups)
					chown "$user:$group" wp-config.php || true
				fi
				break
			fi
		done
	fi
fi

eval setup.sh
apache2-foreground


================================================
FILE: bin/docker-setup.sh
================================================
#!/usr/bin/env bash


================================================
FILE: bin/release.sh
================================================
#!/usr/bin/env bash

PLUGIN_FILE=./podlove.php

CURRENT_VERSION=`head -n 20 $PLUGIN_FILE | grep "Version:" | cut -d: -f2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'`

echo "Creating a release will:"
echo "  - update the version in $PLUGIN_FILE"
echo "  - commit that change"
echo "  - create a git tag on that commit with the given version"
echo ""

echo Current Version: $CURRENT_VERSION
echo "Input new version:"
read version

echo "----------"
echo "Current Version: $CURRENT_VERSION"
echo "New Version:     $version"
echo "----------"

while true; do
    read -p "Correct & Continue?" yn
    case $yn in
        [Yy]* )
          sed -i.bak "s/\(Version:\).*/\1 `echo $version | rev | cut -d/ -f1 | rev`/" $PLUGIN_FILE
          rm $PLUGIN_FILE.bak
          git add $PLUGIN_FILE
          git commit -m "release $version"
          git tag -f $version -m $version
          break;;
        [Nn]* ) exit;;
        * ) echo "Please answer yes or no.";;
    esac
done


================================================
FILE: bin/remove-tunnel.sh
================================================
#!/usr/bin/env bash
set -euo pipefail

# Reset WordPress URLs back to local defaults after using a tunnel.
# Run this from within a Local site shell (where wp-config.php is present)
# or from the plugin root when using wp-env.
#
# Usage:
#   bin/remove-tunnel.sh
#
# Override defaults with:
#   LOCAL_HOST=publisher.local LOCAL_PORT=80 LOCAL_SCHEME=https bin/remove-tunnel.sh

if ! command -v wp >/dev/null 2>&1; then
  echo "Error: wp (WP-CLI) is not available in PATH." >&2
  exit 1
fi

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
LOCAL_WP_ENV_BIN="${REPO_ROOT}/node_modules/.bin/wp-env"

# Local defaults (Local app). wp-env defaults to localhost:8888.
LOCAL_HOST="${LOCAL_HOST:-localhost}"
LOCAL_SCHEME="${LOCAL_SCHEME:-http}"
LOCAL_PORT="${LOCAL_PORT:-8888}"
LOCAL_URL="${LOCAL_SCHEME}://${LOCAL_HOST}:${LOCAL_PORT}/"
WP_URL="${WP_URL:-$LOCAL_URL}"

set_wp_cli() {
  if [ -f "wp-config.php" ]; then
    WP="wp"
    return
  fi

  if command -v wp-env >/dev/null 2>&1; then
    WP="wp-env run cli wp"
    return
  fi

  if [ -x "${LOCAL_WP_ENV_BIN}" ]; then
    WP="${LOCAL_WP_ENV_BIN} run cli wp"
    return
  fi

  if command -v npx >/dev/null 2>&1; then
    if npx --no-install wp-env --version >/dev/null 2>&1; then
      WP="npx --no-install wp-env run cli wp"
      return
    fi
  fi

  echo "Error: wp-config.php not found and wp-env is not available." >&2
  echo "Run this from a WordPress root or from the plugin root with wp-env installed." >&2
  echo "Tip: run npm install (or npm run wp-env:start) to install wp-env locally." >&2
  exit 1
}

set_wp_cli

cat <<INFO
Resetting WordPress URLs to local defaults:
  Local URL: ${LOCAL_URL}
  URL:       ${WP_URL}
INFO

${WP} option update home "${WP_URL}" >/dev/null 2>&1 || true
${WP} option update siteurl "${WP_URL}" >/dev/null 2>&1 || true
${WP} rewrite flush --hard >/dev/null 2>&1 || true

# Local/wp-env may inject redirects via wp-config constants. Clear then set.
${WP} config delete WP_HOME --type=constant >/dev/null 2>&1 || true
${WP} config delete WP_SITEURL --type=constant >/dev/null 2>&1 || true
${WP} config set WP_HOME "${WP_URL}" --type=constant >/dev/null 2>&1 || true
${WP} config set WP_SITEURL "${WP_URL}" --type=constant >/dev/null 2>&1 || true

echo "Done. WordPress URL reset to local defaults."


================================================
FILE: bin/reset-nux.sh
================================================
#!/usr/bin/env bash
set -euo pipefail

# Full WordPress reset + fresh install for new-user experience testing.
# Run this from within a Local site shell (where wp-config.php is present)
# or from the plugin root when using wp-env.
#
# Usage:
#   bin/reset-nux.sh               # reset using LOCAL_URL
#   bin/reset-nux.sh --tunnel      # reset and set site URLs to ngrok public URL
#
# wp-env defaults:
# - Run from plugin root; wp-env exposes WP at http://localhost:8888 by default.
# - Override host/port (e.g. pretty URLs) using wp-env + Caddy:
#   1) Add a .wp-env.override.json like:
#      {
#        "port": 80,
#        "host": "publisher.local"
#      }
#   2) Add to /etc/hosts:
#      127.0.0.1 publisher.local
#   3) Run Caddy to serve HTTPS locally:
#      caddy reverse-proxy --from https://publisher.local --to http://localhost:80
#   4) Then run:
#      LOCAL_HOST=publisher.local LOCAL_PORT=80 LOCAL_SCHEME=https bin/reset-nux.sh

if ! command -v wp >/dev/null 2>&1; then
  echo "Error: wp (WP-CLI) is not available in PATH." >&2
  exit 1
fi

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
LOCAL_WP_ENV_BIN="${REPO_ROOT}/node_modules/.bin/wp-env"

TUNNEL=false
while [ $# -gt 0 ]; do
  case "$1" in
    --tunnel)
      TUNNEL=true
      ;;
    *)
      echo "Error: unknown argument: $1" >&2
      echo "Usage: $0 [--tunnel]" >&2
      exit 1
      ;;
  esac
  shift
done

# Local defaults (Local app). wp-env defaults to localhost:8888.
LOCAL_HOST="${LOCAL_HOST:-localhost}"
LOCAL_SCHEME="${LOCAL_SCHEME:-http}"
LOCAL_PORT="${LOCAL_PORT:-8888}"
LOCAL_URL="${LOCAL_SCHEME}://${LOCAL_HOST}:${LOCAL_PORT}/"
WP_URL="${WP_URL:-$LOCAL_URL}"
WP_TITLE="${WP_TITLE:-NUX Test}"
WP_ADMIN_USER="${WP_ADMIN_USER:-admin}"
WP_ADMIN_PASS="${WP_ADMIN_PASS:-admin}"
WP_ADMIN_EMAIL="${WP_ADMIN_EMAIL:-admin@example.com}"
PLUGIN_SLUG="${PLUGIN_SLUG:-podlove-podcasting-plugin-for-wordpress}"
NGROK_PID=""
NGROK_LOG=""

cleanup() {
  if [ "${TUNNEL}" = true ]; then
    return
  fi
  if [ -n "${NGROK_PID}" ] && kill -0 "${NGROK_PID}" >/dev/null 2>&1; then
    kill "${NGROK_PID}" >/dev/null 2>&1 || true
  fi
}

trap cleanup EXIT

print_ngrok_session_limit_hint() {
  if ! rg -q "ERR_NGROK_108|simultaneous ngrok agent sessions|limited to 1 simultaneous" "${NGROK_LOG}" 2>/dev/null; then
    return
  fi

  echo >&2
  echo "ngrok reports another agent session is already running." >&2
  echo "Find running ngrok processes:" >&2
  echo "  pgrep -fl ngrok" >&2
  echo "Stop them:" >&2
  echo "  pkill -f ngrok" >&2
}

print_ngrok_authtoken_hint() {
  if ! rg -q "ERR_NGROK_4018|authtoken|authentication failed" "${NGROK_LOG}" 2>/dev/null; then
    return
  fi

  echo >&2
  echo "ngrok needs an authtoken. Fix with:" >&2
  echo "  ngrok config add-authtoken <YOUR_TOKEN>" >&2
}

start_ngrok_tunnel() {
  if ! command -v ngrok >/dev/null 2>&1; then
    echo "Error: ngrok is required for --tunnel but was not found in PATH." >&2
    exit 1
  fi

  if ! curl -fsS --max-time 2 "http://127.0.0.1:${LOCAL_PORT}/" >/dev/null 2>&1 \
    && ! curl -fsS --max-time 2 "http://localhost:${LOCAL_PORT}/" >/dev/null 2>&1; then
    echo "Error: local WordPress is not reachable on http://localhost:${LOCAL_PORT}/." >&2
    echo "Check Docker port mappings (wp-env should map ${LOCAL_PORT}->80):" >&2
    echo "  docker ps --format '{{.Names}}\\t{{.Ports}}' | rg 'wordpress'" >&2
    echo "If the port differs, set LOCAL_PORT or LOCAL_HOST before running this script." >&2
    exit 1
  fi

  # Tunnel over plain HTTP to Local's router port; Local handles HTTPS itself.
  local tunnel_target="http://${LOCAL_HOST}:${LOCAL_PORT}/"
  echo "Starting ngrok tunnel for: ${tunnel_target}"

  # Start ngrok in the background and resolve the public URL via the local API.
  # macOS mktemp requires a template with at least 3-6 X's.
  NGROK_LOG="$(mktemp -t ngrok-reset-nux.XXXXXX)"
  local host_header="--host-header=${LOCAL_HOST}:${LOCAL_PORT}"
  case "${WP}" in
    *wp-env*)
      host_header="--host-header=preserve"
      ;;
  esac
  ngrok http "${tunnel_target}" ${host_header} --log=stdout >"${NGROK_LOG}" 2>&1 &
  NGROK_PID=$!
  echo "ngrok PID: ${NGROK_PID}"
  echo "ngrok log: ${NGROK_LOG}"

  sleep 1

  if ! kill -0 "${NGROK_PID}" >/dev/null 2>&1; then
    echo "Error: ngrok exited immediately." >&2
    echo "ngrok output:" >&2
    sed -n '1,80p' "${NGROK_LOG}" >&2 || true
    print_ngrok_session_limit_hint
    print_ngrok_authtoken_hint
    exit 1
  fi

  local tunnel_url=""
  local attempts=0
  local max_attempts=20

  while [ ${attempts} -lt ${max_attempts} ]; do
    attempts=$((attempts + 1))

    if ! kill -0 "${NGROK_PID}" >/dev/null 2>&1; then
      echo "Error: ngrok stopped while waiting for the tunnel URL." >&2
      echo "ngrok output:" >&2
      sed -n '1,120p' "${NGROK_LOG}" >&2 || true
      exit 1
    fi

    tunnel_url="$(
      curl -fsS http://127.0.0.1:4040/api/tunnels 2>/dev/null \
        | php -n -r '$d=json_decode(stream_get_contents(STDIN), true); if (!is_array($d)) { exit(1); } foreach (($d["tunnels"] ?? []) as $t) { $u=$t["public_url"] ?? ""; if (strpos($u, "https://") === 0) { echo $u; exit(0); } } exit(1);' \
        || true
    )"

    if [ -n "${tunnel_url}" ]; then
      echo "ngrok public URL: ${tunnel_url}"
      WP_URL="${tunnel_url}"
      return
    fi

    sleep 1
  done

  echo "Error: ngrok tunnel did not become ready via http://127.0.0.1:4040/api/tunnels." >&2
  echo "ngrok output (first lines):" >&2
  sed -n '1,120p' "${NGROK_LOG}" >&2 || true
  print_ngrok_session_limit_hint
  print_ngrok_authtoken_hint
  echo "Tip: make sure no other ngrok process is running and try again." >&2
  exit 1
}

set_wp_cli() {
  if [ -f "wp-config.php" ]; then
    WP="wp"
    return
  fi

  if command -v wp-env >/dev/null 2>&1; then
    WP="wp-env run cli wp"
    return
  fi

  if [ -x "${LOCAL_WP_ENV_BIN}" ]; then
    WP="${LOCAL_WP_ENV_BIN} run cli wp"
    return
  fi

  if command -v npx >/dev/null 2>&1; then
    if npx --no-install wp-env --version >/dev/null 2>&1; then
      WP="npx --no-install wp-env run cli wp"
      return
    fi
  fi

  echo "Error: wp-config.php not found and wp-env is not available." >&2
  echo "Run this from a WordPress root or from the plugin root with wp-env installed." >&2
  echo "Tip: run npm install (or npm run wp-env:start) to install wp-env locally." >&2
  exit 1
}

set_wp_cli

if [ "${TUNNEL}" = true ]; then
  start_ngrok_tunnel
fi

cat <<INFO
Resetting WordPress with:
  Local URL:    ${LOCAL_URL}
  URL:          ${WP_URL}
  Tunnel:       ${TUNNEL}
  Title:        ${WP_TITLE}
  Admin user:   ${WP_ADMIN_USER}
  Admin email:  ${WP_ADMIN_EMAIL}
  Plugin slug:  ${PLUGIN_SLUG}
INFO

${WP} db reset --yes

${WP} core install \
  --url="${WP_URL}" \
  --title="${WP_TITLE}" \
  --admin_user="${WP_ADMIN_USER}" \
  --admin_password="${WP_ADMIN_PASS}" \
  --admin_email="${WP_ADMIN_EMAIL}"

# Persist the URL explicitly for REST callbacks (important for tunnels).
${WP} option update home "${WP_URL}" >/dev/null 2>&1 || true
${WP} option update siteurl "${WP_URL}" >/dev/null 2>&1 || true
${WP} rewrite flush --hard >/dev/null 2>&1 || true

# Local/wp-env may inject redirects via wp-config constants. Clear then set.
${WP} config delete WP_HOME --type=constant >/dev/null 2>&1 || true
${WP} config delete WP_SITEURL --type=constant >/dev/null 2>&1 || true
${WP} config set WP_HOME "${WP_URL}" --type=constant >/dev/null 2>&1 || true
${WP} config set WP_SITEURL "${WP_URL}" --type=constant >/dev/null 2>&1 || true

activate_podlove_plugin() {
  local candidates=(
    "${PLUGIN_SLUG}"
    "podlove-podcast-publisher"
    "podlove-publisher"
    "$(basename "${REPO_ROOT}")"
  )
  local seen=""
  local slug=""

  for slug in "${candidates[@]}"; do
    case " ${seen} " in
      *" ${slug} "*) continue ;;
    esac
    seen="${seen} ${slug}"
    if ${WP} plugin is-installed "${slug}" >/dev/null 2>&1; then
      ${WP} plugin activate "${slug}" || true
      return
    fi
  done

  echo "Warning: Podlove plugin not found. Tried: ${seen# }" >&2
}

# Ensure the plugin repo is active after reinstall.
activate_podlove_plugin

# For convenience, also activate web player
${WP} plugin activate podlove-web-player || true

if [ "${TUNNEL}" = true ]; then
  echo "Done. Fresh install configured for tunnel URL: ${WP_URL}"
  echo "ngrok is still running in the background (PID: ${NGROK_PID})."
  echo "Stop it with: kill ${NGROK_PID}"
  echo "Switch back to local URLs with: bin/remove-tunnel.sh"
else
  echo "Done. Fresh WordPress install and plugin activation attempted."
fi


================================================
FILE: bin/template_ref.erb
================================================
<% templateRefClasses.each do |item| %>
<a id="podlove-class-<%= item['class']['templatetag'] %>"></a>

#### <%= item['class']['classname'] %>

<%= renderDescription(item['class']['description']) %>

<table>
    <% item['methods'].each do |method| %>
        <tr>
            <td valign="top">
                <code>
                    <%= item['class']['templatetag'] %>.<%= method['methodname'] %>
                </code>
            </td>
            <td>
                <strong>
                    <%= method['title'] %>
                </strong>
                <%= renderDescription(method['description']) %>
                <%
                seeTags = method['tags'].keep_if { |tag| tag['name'] == 'see' }
                if seeTags && seeTags.length > 0
                    %><p><% 
                    seeTags.each do |tag| %>
                        see <a href="#podlove-class-<%= tag['description'] %>"><%= renderDescription(tag['description']) %></a>
                    <% end
                    %></p><% 
                end %>
            </td>
        </tr>
    <% end %>
</table> 
<% end %>

================================================
FILE: bin/template_ref.rb
================================================
require "erb"
require "json"

def templateRefClasses
	classes = %w{podcast episode network list chapter feed asset file image tag category duration file_type contributor contributor_group season service show license flattr datetime line group}
	classes.map { |klass| JSON.parse(IO.read("doc/data/template/#{klass}.json")) }
end

def renderDescription(s)
	if s
		s.gsub!(/^(\s+)```/, "\n```")
		s.gsub!(/```(\w+)?([^`]+)```/) { |m| "```" + $1.to_s + "\n{% raw %}" + $2.to_s + "{% endraw %}\n```" }
		s.gsub!(/[^`]`([^`]+)`/) { |m| "`{% raw %}" + $1.to_s + "{% endraw %}`" }
		s = "{% capture tmp %}" + s + "{% endcapture %}\n{{ tmp | markdownify }}"
	end

	s
end

renderer = ERB.new(File.read("bin/template_ref.erb"))
puts output = renderer.result()


================================================
FILE: bin/template_ref_json.php
================================================
<?php

/**
 * Extracts template reference and saves them to JSON files.
 *
 * Complete workflow for generating reference markdown:
 *
 * - Warning: Does NOT work on multisite installs! Set `define('MULTISITE', false);`!
 * - use `php -d "opcache.enable=off"` to avoid opcache removing comments
 *
 * 1. $> WPBASE=/path/to/wordpress php -d "opcache.enable=off" bin/template_ref_json.php
 * 2. $> ruby bin/template_ref.rb > doc/template_ref.md
 */
require_once 'vendor/autoload.php';

use Podlove\Comment\Comment;

define('MULTISITE', false);

if (!getenv('WPBASE')) {
    exit("You need to set the environment variable WPBASE to your WordPress root\n");
}

require_once dirname(__FILE__).'/../lib/helper.php';
require_once getenv('WPBASE').'/wp-load.php';
require_once dirname(__FILE__).'/../bootstrap/bootstrap.php';

// $output_dir = '/tmp/podlove/doc';
$output_dir = dirname(__FILE__).'/../doc/data/template';
@mkdir($output_dir, 0777, true);

// classes containing dynamic accessors
$dynamicAccessorClasses = [
    '\Podlove\Modules\Contributors\TemplateExtensions',
    '\Podlove\Modules\Seasons\TemplateExtensions',
    '\Podlove\Modules\RelatedEpisodes\TemplateExtensions',
    '\Podlove\Modules\Shows\TemplateExtensions',
    '\Podlove\Modules\Social\TemplateExtensions',
    '\Podlove\Modules\SubscribeButton\TemplateExtensions',
    '\Podlove\Modules\Transcripts\TemplateExtensions',
];

$classes = [
    '\Podlove\Template\Podcast',
    '\Podlove\Template\Feed',
    '\Podlove\Template\Episode',
    '\Podlove\Template\EpisodeTitle',
    '\Podlove\Template\Asset',
    '\Podlove\Template\File',
    '\Podlove\Template\Duration',
    '\Podlove\Template\Chapter',
    '\Podlove\Template\License',
    '\Podlove\Template\DateTime',
    '\Podlove\Template\FileType',
    '\Podlove\Template\Tag',
    '\Podlove\Template\Category',
    '\Podlove\Template\Image',
    '\Podlove\Modules\Contributors\Template\Contributor',
    '\Podlove\Modules\Contributors\Template\ContributorGroup',
    '\Podlove\Modules\Seasons\Template\Season',
    '\Podlove\Modules\Shows\Template\Show',
    '\Podlove\Modules\Social\Template\Service',
    '\Podlove\Modules\Networks\Template\Network',
    '\Podlove\Modules\Networks\Template\PodcastList',
    '\Podlove\Modules\Transcripts\Template\Line',
    '\Podlove\Modules\Transcripts\Template\Group',
];

// first, parse dynamic accessors
$dynamicAccessors = [];
foreach ($dynamicAccessorClasses as $class) {
    $reflectionClass = new ReflectionClass($class);
    $methods = $reflectionClass->getMethods();

    $accessors = array_filter($methods, function ($method) {
        $comment = $method->getDocComment();

        return stripos($comment, '@accessor') !== false && stripos($comment, '@dynamicAccessor') !== false;
    });

    $parsedMethods = array_map(function ($method) {
        assert_options(ASSERT_CALLBACK, function () use ($method) {
            print_r("!!! Assertion failed in {$method->class}::{$method->name}\n");
        });

        $c = new Comment($method->getDocComment());
        $c->parse();

        $dynamicAccessor = $c->getTag('dynamicAccessor');
        $callData = explode('.', $dynamicAccessor['description']);

        return [
            'methodname' => $callData[1],
            'title' => $c->getTitle(),
            'description' => $c->getDescription(),
            'tags' => $c->getTags(),
            'class' => $callData[0],
        ];
    }, $accessors);

    foreach ($parsedMethods as $method) {
        if (!isset($dynamicAccessors[$method['class']])) {
            $dynamicAccessors[$method['class']] = [];
        }

        $dynamicAccessors[$method['class']][$method['methodname']] = $method;
    }
}

foreach ($classes as $class) {
    $reflectionClass = new ReflectionClass($class);
    $className = $reflectionClass->getShortName();
    $methods = $reflectionClass->getMethods();

    $accessors = array_filter($methods, function ($method) {
        $comment = $method->getDocComment();

        return stripos($comment, '@accessor') !== false;
    });

    $parsedMethods = array_map(function ($method) {
        $c = new Comment($method->getDocComment());
        $c->parse();

        return [
            'methodname' => $method->name,
            'title' => $c->getTitle(),
            'description' => $c->getDescription(),
            'tags' => $c->getTags(),
        ];
    }, $accessors);

    if (isset($dynamicAccessors[strtolower($className)])) {
        foreach ($dynamicAccessors[strtolower($className)] as $dynamicMethodName => $dynamicMethod) {
            $parsedMethods[] = $dynamicMethod;
        }
    }

    $classComment = new Comment($reflectionClass->getDocComment());
    $classComment->parse();
    $templatetag = $classComment->getTags()[0]['description'];

    assert(strlen($templatetag) > 0, 'templatetag must not be empty');

    $classdoc = [
        'class' => [
            'classname' => $className,
            'templatetag' => $templatetag,
            'description' => $classComment->getDescription(),
        ],
        'methods' => array_values($parsedMethods),
    ];

    file_put_contents($output_dir.'/'.$templatetag.'.json', wp_json_encode($classdoc), LOCK_EX);
}


================================================
FILE: bin/uadetect.php
================================================
<?php

require_once 'vendor/autoload.php';

use PodlovePublisher_Vendor\DeviceDetector\DeviceDetector;

$userAgent = $argv[1];
$dd = new DeviceDetector($userAgent);
$dd->parse();

if ($dd->isBot()) {
    var_dump($botInfo = $dd->getBot());
} else {
    $clientInfo = $dd->getClient(); // holds information about browser, feed reader, media player, ...
    $osInfo = $dd->getOs();
    $device = $dd->getDevice();
    $brand = $dd->getBrand();
    $model = $dd->getModel();
    var_dump($clientInfo, $osInfo, $device, $brand, $model);
}


================================================
FILE: bin/update-opawg.sh
================================================
#!/usr/bin/env bash

wget -O data/opawg.json https://raw.githubusercontent.com/opawg/user-agents/master/src/user-agents.json


================================================
FILE: bin/update_pwp4.sh
================================================
#!/usr/bin/env bash

npm update @podlove/web-player
rm -r lib/modules/podlove_web_player/player_v4/dist
cp -r node_modules/@podlove/web-player/ lib/modules/podlove_web_player/player_v4/dist


================================================
FILE: bin/workspace.js
================================================
const path = require('path')
const fs = require('fs-extra')

const toDelete = (fs.readdirSync(path.resolve('.')) || []).filter(item => item !== 'dist')

toDelete.forEach(file => fs.removeSync(path.resolve(file)))

const toCopy = fs.readdirSync(path.resolve('dist')) || []

toCopy.forEach(file => fs.copySync(path.resolve('dist', file), path.resolve('.', file)))

fs.removeSync(path.resolve('dist'))


================================================
FILE: bin/wp-env-test-after-start.js
================================================
const { spawnSync } = require('node:child_process');
const path = require('node:path');

const repoRoot = path.resolve(__dirname, '..');
const wpEnvBin = path.join(repoRoot, 'node_modules', '.bin', 'wp-env');
const configArg = '--config=.wp-env.test.json';
const pluginSlug = 'podlove-podcasting-plugin-for-wordpress';

function runWpEnv(args) {
  return spawnSync(wpEnvBin, [configArg, 'run', 'cli', 'wp', ...args], {
    cwd: repoRoot,
    stdio: 'inherit',
  });
}

const isActive = runWpEnv(['plugin', 'is-active', pluginSlug]);

if (isActive.status === 0) {
  process.exit(0);
}

const activated = runWpEnv(['plugin', 'activate', pluginSlug]);
process.exit(activated.status ?? 1);


================================================
FILE: bootstrap/autoload.php
================================================
<?php

function podlove_camelcase_to_snakecase($string)
{
    return preg_replace('/([a-z])([A-Z])/', '$1_$2', $string);
}

function podlove_camelsnakecase_to_camelcase($string)
{
    return str_replace('_', '', $string);
}

function podlove_snakecase_to_camelsnakecase($string)
{
    return ucwords(preg_replace_callback('/_\w/', function ($m) {
        return strtoupper($m[0]);
    }, $string));
}

// autoload all classes in /lib
function podlove_autoloader($class_name)
{
    // get class name without namespace
    $split = explode('\\', $class_name);
    // remove <Plugin> namespace
    $plugin = array_shift($split);

    if (!strlen($plugin)) {
        $plugin = array_shift($split);
    }

    // only load classes prefixed with <Plugin> namespace
    if ($plugin != 'Podlove') {
        return false;
    }

    // class name without namespace
    $class_name = array_pop($split);
    // CamelCase to snake_case
    $class_name = podlove_camelcase_to_snakecase($class_name);

    // the rest of the namespace, if any
    $namespaces = $split;

    // library directory
    $lib = dirname(dirname(__FILE__)).'/lib/';

    // register all possible paths for the class
    $possibilities = [];
    if (count($namespaces) >= 1) {
        $possibilities[] = $lib.strtolower(implode('/', array_map('podlove_camelcase_to_snakecase', $namespaces)).'/'.$class_name.'.php');
    } else {
        $possibilities[] = $lib.strtolower($class_name.'.php');
    }

    // search for the class
    foreach ($possibilities as $file) {
        if (file_exists($file)) {
            require_once $file;

            return true;
        }
    }

    if (defined('WP_DEBUG') && WP_DEBUG) {
        $trace = debug_backtrace();
        $functions = array_map(function ($t) {
            return $t['function'];
        }, $trace);

        if (in_array('class_exists', $functions)) {
            // don't log anything, we were just checking if that class exists
        } else {
            error_log(print_r([
                'message' => 'Class Autoload failed for "'.$class_name.'"',
                'attempts' => $possibilities,
                'trace' => $functions,
            ], true));
        }
    }

    return false;
}
spl_autoload_register('podlove_autoloader');


================================================
FILE: bootstrap/bootstrap.php
================================================
<?php

require_once __DIR__.'/autoload.php';
require_once __DIR__.'/constants.php';


================================================
FILE: bootstrap/constants.php
================================================
<?php

namespace Podlove;

/*
 * Conventions
 *
 * 	Plugin Name:		This Is My Plugin
 * 	Plugin Namespace:	ThisIsMyPlugin
 * 	Plugin File:		this-is-my-plugin.php
 * 	Plugin Textdomain:	this-is-my-plugin
 * 	Plugin Directory:	this-is-my-plugin
 */

define('Podlove\PLUGIN_FILE_NAME', strtolower(preg_replace('/([a-z])([A-Z])/', '$1-$2', __NAMESPACE__)).'.php');
define('Podlove\PLUGIN_DIR', plugin_dir_path(dirname(__FILE__)));
define('Podlove\PLUGIN_FILE', PLUGIN_DIR.PLUGIN_FILE_NAME);
define('Podlove\PLUGIN_URL', plugins_url('', PLUGIN_FILE));

/**
 * Get a value of the plugin header.
 *
 * @param mixed $tag_name
 */
function get_plugin_header($tag_name)
{
    static $plugin_data; // only load file once

    if (!function_exists('get_plugin_data')) {
        require_once ABSPATH.'/wp-admin/includes/plugin.php';
    }

    $plugin_data = get_plugin_data(PLUGIN_FILE, false, false);

    return $plugin_data[$tag_name];
}


================================================
FILE: changelog.txt
================================================
= 4.0.15 =

- security: add nonces to jobs management

= 4.0.14 =

- add: migrate episode license selector user interface
- change: show unknown duration as "--:--:--.---" instead of "00:00:00.000"
- fix: auto-generate file slug from episode-post-title
- fix: ensure slug field is always usable (wide enough, and prefix shortened if necessary)
- security: fix SQL injection vulnerability in Related Episodes module
- security: ensure only administrators can manage jobs

= 4.0.13 =

**Features**

- Templates: new `active` accessor for `File` objects. Returns if the file is marked as active.

**Bugfixes and Improvements**

- fix: don't use the Auphonic chapter image URL (real fix where chapter images are downloaded and served from WordPress will follow later)
- fix: only display active files in download widgets
- improve handling of upload directory

= 4.0.12 =

**Security**

- fix SSRF vulnerability in Slacknotes module
- add missing capability check and nonce validation to importer functions
- add missing capability check and nonce validation to exporter functions

= 4.0.11 =

- new: show admin notice when a database migration fails
- fix bug where tracking data could be lost by disabling a media file checkbox
- fix bug where imported Hindenburg chapters were not sorted by time
- fix build script (correctly delete all vendor prefixed dependencies)
- fix deprecation warnings ([#1431](https://github.com/podlove/podlove-publisher/pull/1431), [#1430](https://github.com/podlove/podlove-publisher/pull/1430))
- update js dependencies
- update help text for missing curl module

= 4.0.10 =

- fix security issues (XSS)
- do not unnecessarily flush rewrite rules ([Issue#1432](https://github.com/podlove/podlove-publisher/issues/1432))
- fix link to Slacknotes and Subscribe Button documentation
- fix psr library not removed after prefixing ([Issue#1421](https://github.com/podlove/podlove-publisher/issues/1421))

= 4.0.9 =

**Enhancements**

- trim whitespaces from beginning and end of file slug
- soundbite: change placeholder to HH:MM:SS to clarify format

**Bugfixes**

- fix division by zero in analytics
- fix default contributors missing position attribute
- fix Auphonic chapter timestamp import
- fix page reload when clicking chapter upload button

= 4.0.8 =

**Bugfixes**

- fix broken analytics episode screen

= 4.0.7 =

**Bugfixes**

- fix media verification not saving
- fix shownotes unfurling
- avoid failure during database migration

**Misc**

- update/cleanup various js dependencies

= 4.0.6 =

**Bugfixes**

- Auphonic: saving production not working when slug is not set (bug introduced in 4.0.5)

= 4.0.5 =

**Bugfixes**


- Auphonic: restore previous behaviour:
  - automatically fill in file slug, validate media files and fill in duration when production finishes
  - use slug as "output_basename" if it is set

**Misc**

- cleanup legacy js app (dependency updates, deletion of unused code etc.)

= 4.0.3 / 4.0.4 =

**Enhancements**

- Auphonic: sort presets alphabetically
- Contributors: make better use of available space

**Bugfixes**

- episode metadata not saving reliably for some people
- Auphonic: fix chapter time import
- WordPress File Upload: display slug input (should be filled automatically but does not seem to work reliably)

= 4.0.2 =

**Bugfixes**

- Auphonic: Chapters can be imported from production metadata
- Contributors: Add support for Gravatar and default contributor image on edit screens
- Dashboard: Asset Validation / Detection is working again [#1396](https://github.com/podlove/podlove-publisher/issues/1396)
- Automatic Numbering: error when selecting a show

= 4.0.1 =

**Enhancements**

- Auphonic: autosave before "Start Production" so it is not required to explicitly save before starting

**Bugfixes**

- Auphonic: open productions with missing algorithm information
- Templates: fix broken core templates `downloads-select` and `related-episodes-list`
- Classic Editor: display Episode Title Placeholder based on Blog Post Title

= 4.0.0 =

Podlove Publisher 4.0 is here, bringing a spring-clean (in November!) of the episode page. We tore up the foundation to bring you an all-new user interface.

**Warning:** PHP 8.0 and above is now required!

**Highlights**

- **Episode Form** User Interface is modernised and auto-saves, so no work is ever lost.
- **Auphonic module** includes Multitrack Support.
- **New Contributors** can be added without leaving the episode page.
- **Chapters** support images.
- **REST API V2** is now, including many more endpoints. See the [API documentation](https://docs.podlove.org/podlove-publisher/api) for all the details.


**Tidbits**

- file “slug” field is prefixed with the media location so it’s more obvious what it is used for
- episode duration is always auto-detected and not an input field any more
- Auphonic Preset can be selected directly in the episode and does not rely on global module setting any more
- removed module “Twitter Card Integration” (RIP). By the way, if you want to follow us on social media, find us here: https://fosstodon.org/@podlove
- fix various PHP notices and warnings to be PHP 8.0+ compatible

= 3.8.11, 3.8.12 =

* fix: templates XHR issue in WP MultiSite
* fix: lists UI in WP MultiSite

= 3.8.10 =

* fix: templates UI in WP MultiSite

= 3.8.9 =

* fix broken encoding when saving episode meta

= 3.8.8 =

* fix broken RSS feeds

= 3.8.7 =

* improve PHP 8 compatibility
  * stop using FILTER_SANITIZE_STRING
  * set default encoding in DOMDocument
  * set default values in transcript template internals

* fix(plus): gracefully handle when things go wrong in image generator
* fix(shownotes): handle image providers returning lists
* fix subtle `get_the_excerpt` bug (uses post parameter now instead of the global post object)

= 3.8.6 =

* fix incompatibilities with matomo plugin ([#1308](https://github.com/podlove/podlove-publisher/pull/1308))

= 3.8.5 =

* fix PHP notice on episode screen

= 3.8.4 =

* fix CSRF vulnerabilities by adding nonces to forms

= 3.8.3 =

* fix XSS vulnerability

= 3.8.2 =

* add support for op3.dev using the "External Analytics" module
* update OPAWG data (for download analytics / user agent detection)
* improve PHP 8.1 compatibility

= 3.8.1 =

* fix(api): episode title -- Episode title in API now follows the same rules as in RSS feed. There's a new field 'title_clean' for accessing the specifically set plain episode title, but that might be null in some cases, so it's better to default the 'title' attribute to the usual rules.
* fix: auphonic preset for show applies after saving the episode
* fix: remove debug error log for a shows analytics query

= 3.8.0 =

**New module: Automatic Numbering**

Automatically increase the Episode number when creating episodes.
When using the "Shows" module, each show has its own numbering.

This module is active by default for new setups.
To use it in your current setup, go to `Podlove > Modules`, search for "Automatic Numbering" and activate it.

**Other Changes:**

- Shows Module: each show can define its own Auphonic production preset
- episode: after file revalidation, auto-detect duration
- contributors: assign a `Sponsor` role and it will appear as `<podcast:person ... role="sponsor">...` in the RSS feed
- fix: correct PHP version number in message (https://github.com/podlove/podlove-publisher/pull/1272)
- fix: feed cache issue when using the "Shows" module

= 3.7.0 =

**Shownotes**

The Shownotes module helps you manage link based show notes to display on your website and podcatchers.

The module UI has been rewritten and streamlined for efficient workflows.
A new UI element was added to allow for quickly sorting long lists of links into topics:
Whenever a link is dragged, a floating list of all topics appears next to the cursor.
The link can then be dropped under the desired topic there instead of scrolling through the whole list of shownotes.

Disclaimer: URL metadata detection uses a service hosted at [plus.podlove.org](https://plus.podlove.org). It is currently available for all users of Podlove Publisher. In the future, metadata detection may only be availabe to Publisher PLUS users as it requires infrastructure to run. The rest of the Shownotes functionality will stay available to all Podlove users as usual.

Documentation: https://docs.podlove.org/podlove-publisher/modules/shownotes

**Contributors**

* Notifications: add "always send to..." section. Contributors selected there will always receive update notifications.
* Avatars: default avatar is now a static svg instead of Gravatar (can be customized using the WordPress Filter `podlove_default_contributor_avatar_url`)

**Enhancements for creating Auphonic productions** (thanks [lumaxis](https://github.com/lumaxis)!):

* when the episode title is set, send this instead of the post title ([#1240](https://github.com/podlove/podlove-publisher/pull/1240))
* send the episode number as track number ([#1240](https://github.com/podlove/podlove-publisher/pull/1240))
* when the post thumbnail is configured as cover image, use it as direct fallback  ([#1241](https://github.com/podlove/podlove-publisher/pull/1241))

**Webhooks**

Define a webhook that gets triggered every time an episode updates.

The webhook is a `POST` request with an `event` parameter and a `payload`.
`event` is the webhook name ("episode_updated"), `payload` is a serialized
JSON object of the current episode.

Configuration:

    # wp-config.php
    define('PODLOVE_WEBHOOKS', [
        'episode_updated' => 'https://example.com/webhook-endpoint'
    ]);

**Other Changes**

* soundbites: add title field ([#1257](https://github.com/podlove/podlove-publisher/pull/1257), [#1237](https://github.com/podlove/podlove-publisher/issues/1237))
* allow detection of episode duration on mp4 ([#1249](https://github.com/podlove/podlove-publisher/pull/1249))
* update OPAWG data (for download analytics / user agent detection)

**Fixes**

* fix: parameters for shortcode `[podlove-episode-contributor-list]` ([#1233](https://github.com/podlove/podlove-publisher/issues/1233))
* fix: PHP 8 warnings ([#1258](https://github.com/podlove/podlove-publisher/issues/1258))
* fix: deleting an episode deletes its transcript from the database ([#1252](https://github.com/podlove/podlove-publisher/issues/1252))
* fix(contributors): notification test email ([#1247](https://github.com/podlove/podlove-publisher/issues/1247))
* fix(analytics): filtering of httprange requests with one or two bytes ([#1243](https://github.com/podlove/podlove-publisher/issues/1243))
* fix(image cache): redirect to source URL if image can't be downloaded into the cache

= 3.6.1 =

* fix: sql issue when creating the episode database tables

= 3.6.0 =

**New Module: Soundbite**

Adds support for the `<podcast:soundbite>` RSS feed tag.
The intended use includes episodes previews, discoverability, audiogram generation, episode highlights, etc.

Using this module you can specify an audio segment for each episode that can be read by for example audiogram generation services.

**New Module: WordPress File Upload**

If you are using WordPress Media as storage for your Podlove assets, this new
module adds conveniences.

First, you define a `Upload subdirectory` for your Podlove assets. This overrides
any WordPress settings, so for example you can safely enable the typical date/month
structure for WordPress attachments and it will not affect your Podlove Uploads.

Then you can update your "Podlove - Media - Upload Location" setting. You can keep
it empty to let Podlove Publisher take care of it, or set it yourself if you have
a custom file hostname.

Now there is an "Upload Media File" button in your episode form above
the "Episode Media File Slug" where you can directly upload your files.
If you are using multiple assets, you can upload them all there. Just make sure
they all have the same filename (except the file extension) before you upload.

= 3.5.5, 3.5.6 =

**Fixes**

* SECURITY: sql injection in "Social & Donations" module
* transcript API returns list again
* PLUS open graph images (use new API)
* handle webvtt voice, missing Contributors
* related episodes: remove whitespace in shortcode HTML to fix rendering in Spotify

**Changes**

* webvtt transcripts use public contributor name
* transcript voices / contributors:
  * you can now select "none" in the voice assignment
  * only voices with an assigned contributor (and not "none") appear in public transcripts
* generate default copyright claim if it is not explcitly set

= 3.5.4 =

* adds copyright field in "Podcast Settings - Directory", which is apparently required by the Apple Podcast Directory since yesterday.
* perf: remove frontend.js (inline logic to download button HTML)

= 3.5.2 / 3.5.3 =

This releases reverses all changes to Permalinks in releases 3.5.0 and 3.5.1.

I severely underestimated the effect these changes would have and revert all changes until I find a better solution. It’s simply not acceptable to change episode URLs, especially without an option for automatic redirects.

Please verify your episode URLs and the two expert settings “Permalink structure for episodes” and “Episode Pages”.

What to do if you have used the “PODLOVE_ENABLE_PERMALINK_MAGIC” constant? It has no effect any more and you can safely remove it from your config file.

What happened to the “Simple Episode Permalink” setting from release 3.5.1? It has been removed, too.

Sorry for the trouble.
Happy podcasting :)

**Other**

* fix: remove usage of PHP 7.1 syntax in one file

= 3.5.1 =

= 2021-04-14 =

* add: expert setting to make episode permalinks `/%postname%/`
* add: include Publisher Database Version in system report
* drop WordPress version requirement to 4.9.6

= 3.5.0 =

**Breaking Change**

Removes two expert settings:

* "Permalink structure for episodes" and
* "Episode pages"

These settings allowed to define custom URL structures for episodes and the episode archive.
However they have caused trouble for a long time (see [#1038](https://github.com/podlove/podlove-publisher/issues/1038))
and the only viable way out seems to remove them.

How does that affect you?

If you have never touched these settings, feel free to shrug, smile and move on.

If you _are_ using these settings, I encourage you to consider not using them as they are mostly of cosmetic nature.
Should you however prefer to keep everything as is (including the known bugs of erratically broken permalinks / URLs), you can
enable the settings back with a single line of code in your wp-config.php:

    `define('PODLOVE_ENABLE_PERMALINK_MAGIC', true);`

**Experimental: Full-Page Podlove Templates**

If you want to create a 100% custom page based on an episode but without all the WordPress theme around, this is for you.
Possible use case: A dedicated page to print the episode transcript.

1. create a new Podlove Template, for example `page-episode-transcipt`
2. Write that transcript as a _full HTML page_. That means it starts with `<!doctype html><html>` and ends with `</html>`!
3. Append `?podlove_template_page=page-episode-transcipt` to your public episode URL. For example if your episode is `https://example.com/ep001/`, then open `https://example.com/ep001/?podlove_template_page=page-episode-transcipt`

Very simple example template:

    <!doctype html>
    <html>

    <head>
      <meta charset="utf-8">
      <title>Transcript | {{ episode.title }} | {{ podcast.title }}</title>
      <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>

    <body>

      <p>
        Here's the transcript for podcast <strong>{{ podcast.title }}</strong> episode <strong>{{ episode.title }}</strong>:
      </p>

      [podlove-transcript]

    </body>

    </html>

Enjoy!

**Shownotes Module**

* provide website screenshot as fallback when no sharing image is available (requires PLUS token)
* show images
* show image in edit view
* when importing, show all entries
* show import progress when unfurling
* fix osf importer
* fix encoding issue when importing from HTML

**Miscellaneous**

* update database for podcast user agents -- notably includes classification of Apple Watch downloads as bot [#1203](https://github.com/podlove/podlove-publisher/issues/1203)
* transcript: add some basic info about podcast and episode into webvtt as a note
* analytics: add hook `podlove_useragent_opawg_data` to add custom user agent detection
* Podlove Templates: add `dataUri` method to images. Takes same arguments as `url` but returns a data uri. Useful if you want to generate a self-contained HTML page. If you're not sure, better use `url`.
* fix: transcripts with trailing newlines don't confuse the importer
* fix: don't count contributors multiple times if they have multiple contributions in an episode ([#1200](https://github.com/podlove/podlove-publisher/issues/1200))
* fix: calling wptexturize too early ([#1194](https://github.com/podlove/podlove-publisher/issues/1194))

= 3.4.1 =

* fix: analytics shows section now does not include other taxonomies
* use image caching for shownotes images
* analytics shows section is now ordered by downloads

= 3.4.0 =

**podcastindex namespace**

Both additions add metadata to the feed automatically if the data is present. No new user interfaces or data entry is necessary.

* add support for feed tag [`podcast:transcript`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#transcript), linking to the transcript in various formats (json, webvtt, xml)
* add support for feed tag [`podcast:person`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#person) on episode level

**analytics**

* for selected date range, total downloads are shown
* for selected date range, display downloads per show (only visible when shows module is enabled)

= 3.3.2 =

* fix: in analytics, the "Export as CSV" section is now clickable when global statistics are loading or have no data
* fix: "Export as CSV" works again
* fix: "global statistics" charts idling indefinitely until a custom date range is chosen

= 3.3.0 / 3.3.1 =

* add support for feed tag [`podcast:funding`](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#funding) (see Podcast Settings -> Directory)
* unfurl uses https://plus.podlove.org/api/unfurl as API endpoint
* add banner linking to donations page (can be dismissed)
* shownotes:
  * add shortcode `[podlove-episode-shownotes]`
  * display links even if unfurling failed
  * template improvements
  * add "delete all" button
  * polished failure section UI and allow editing original URL
  * API: add missing permission callbacks
  * fix: keep order when importing via slacknotes
* slacknotes: update to new API
* change donation URL to https://opencollective.com/podlove
* fix: handle missing templates in TwigLoaderPodloveDatabase

= 3.2.2 =

* fix: crash when creating new episodes

= 3.2.1 =

* fix: coverart url encoding [#1181](https://github.com/podlove/podlove-publisher/pull/1181)
* fix: some settings not applying to episode title tag (thanks Dirk)
* fix: crash when accessing season data for an episode without season

= 3.2.0 =

* when automatically generated episode titles are used, use the blogpost title as fallback for the episode title
* fix: disable slug auto-updating after importing from Auphonic
* fix: webvtt-parser autoloading issues [#1175](https://github.com/podlove/podlove-publisher/issues/1175)
* fix: escape ampersands in itunes:image hrefs in the feed [#1176](https://github.com/podlove/podlove-publisher/issues/1176) (fixes incompatibilities with Jetpack image CDN)

= 3.1.* =

* fix twig namespace prefixing related issues
* remove unused vendor-bin directory from releases

= 3.1.1 =

* tracking: fix operating systems appearing twice in different spellings
* chore: prefix all composer packages (solves Twig related incompatibilities & crashes)
* chore: add content and files to episodes api (#1165)

= 3.1.0 =

* analytics: new chart showing download development from episode to episode [#1100](https://github.com/podlove/podlove-publisher/pull/1100/files) thanks [@poschi3](https://github.com/poschi3)!
* Auphonic: show production warnings in module (https://twitter.com/auphonic/status/1305849345762185217)
* download tracking: use OPAWG podcast user agent database in addition to Matomo database
* stability: detect plugins using older/incompatible versions of Twig. Display a warning on the site (instead of an error) and a detailed explanation on "Podlove > Support" screen.
* enhance: podcast file validation in dashboard includes all post stati and checks for missing slug [#1161](https://github.com/podlove/podlove-publisher/pull/1161)
* enhance: only allow episode numbers of 0 and higher in form input [#1158](https://github.com/podlove/podlove-publisher/pull/1158)
* api: add public endpoint for transcripts
* api: add public endpoint for shownotes
* fix: Podlove Web Player 5 includes all downloadable assets in download section
* fix: transcript API URL [#1145](https://github.com/podlove/podlove-publisher/pull/1145) thanks [gibso](https://github.com/gibso)!
* fix: editing/deleting shows ([#1077](https://github.com/podlove/podlove-publisher/issues/1077))
* fix: episodes and shows API
* fix: migration for Shownotes only when the database table exists

= 3.0.4 =

* fix: contributor notifications settings can be saved again ([#1144](https://github.com/podlove/podlove-publisher/issues/1144))
* fix: do not include invisible contributors in Web Player 5 API ([#1142](https://github.com/podlove/podlove-publisher/issues/1142))
* fix: detect Yoast SEO, wpSEO: disables Open Graph module ([#1132](https://github.com/podlove/podlove-publisher/issues/1132))
* fix: use podcast summary as RSS Feed `<description>` if subtitle is not set ([#1092](https://github.com/podlove/podlove-publisher/issues/1092))

= 3.0.3 =

* fix: title escaping in RSS feed when using native (not auto-generated) titles

= 3.0.2 =

* add: Untappd social service
* fix: Auphonic module (wrong HTTP API headers)
* chore: update npm dependencies

= 3.0.1 =

* fix: escaping issue in RSS feed (itunes:author and itunes:owner)
* fix: remove (rare) accidental double enclosure tag in RSS feed when "enclosure" post meta is present

= 3.0.0 =

**Breaking Changes**

* requires PHP 7.0 (or newer)
* requires WordPress 5.2 (or newer)
* Web Player:
  * removes Podlove Web Player 2
  * removes Podlove Web Player 3
  * removes "insert player automatically" option (probably does not affect anyone as the web player is by default inserted via template)
  * removes "Chapters Visibility" option (use dedicated Web Player settings instead)

**New Publisher PLUS**

=> [plus.podlove.org](https://plus.podlove.org/)

Publisher PLUS is a new service providing Feed Proxy and Podcast Subscriber statistics for Podlove Publisher.

To use it, enable the *Publisher PLUS* module, then visit [plus.podlove.org](https://plus.podlove.org/) to create an account.

Subscriber Statistics are only the beginning. Expect more features soon!

**Experimental: Shownotes**

Generate and manage episode show notes. Helps you provide rich metadata for URLs. Full support for Publisher Templates.

This module is a work-in-progress. But it's usable, so feel free to give it a try, especially if your shownotes are link-heavy and you're comfortable writing Podlove (Twig) templates.

The module is currently hidden. Make it visible by setting a PHP constant, for example in your `wp-config.php`: `define('PODLOVE_MODULE_SHOWNOTES_VISBLE', true);`.

Use this template as a starting point: https://gist.github.com/eteubert/d6c51c52372dc2da2f1734a5f54c7918

**Shortcodes**

* `podlove-episode-contributor-list`
  * new design
  * renders text-only in RSS feed
* `podlove-podcast-contributor-list`
  * new design
* `podlove-episode-downloads`
  * the text link variant is now the default style

**Miscellaneous**

* remove Bitlove module (service does not exist any more)
* remove Flattr module
* remove "Website Protocol" setting (not necessary any more as Let's Encrypt is widely supported)
* enable episode chapters by default
* convenience: "Copy to Clipboard" function for Podlove Template shortcodes
* expose iTunes id/URL in podcast feed ([#1078](https://github.com/podlove/podlove-publisher/pull/1078))
* improve feed rendering: use XML generator for all tags with user input to guarantee valid feeds for all inputs
* add function to remove a transcript from an episode ([#1131](https://github.com/podlove/podlove-publisher/issues/1131))
* add Steady as donation service
* add template tag: `episode.post_title` ([#1136](https://github.com/podlove/podlove-publisher/issues/1136))
* add template tag: `service.type` (https://community.podlove.org/t/replacing-social-icons/2321)
* add default avatar to transcript preview
* fix: search logic ([#1072](https://github.com/podlove/podlove-publisher/issues/1072))
* fix: fetch Podlove News via https ([#1037](https://github.com/podlove/podlove-publisher/issues/1037))
* fix: don't send Publisher logs to system log when WP_DEBUG is on ([#1065](https://github.com/podlove/podlove-publisher/issues/1065))
* fix: ensure uploads for webvtt (transcripts) and gz (exports) are allowed
* fix: ensure contributors module is active when transcripts are used
* fix: ensure permissions in shownotes and transcripts APIs
* fix: don't count download requests with http range header of `bytes=0-0` ([#1135](https://github.com/podlove/podlove-publisher/issues/1135))
* update dependencies
* build releases with GitHub Actions (in favour of TravisCI)

= 2.11.4 =

* fix: missing monolog dependency

= 2.11.2 =

* fix: ensure that logging library Monolog is available at version 1.x, otherwise disable the database logger

= 2.11.1 =

* Podlove Web Player 5: support "show" parameter `episode.player({show: 'my-show-slug'})`

= 2.11.0 =

* add global network bar [#1101](https://github.com/podlove/podlove-publisher/pull/1101) by [@gglnx](https://github.com/gglnx)
* improve template editing UI [#1109](https://github.com/podlove/podlove-publisher/pull/1109)
* fix: template tag `episode.player` uses correct shortcode internally
* fix: template tag `episode.player` uses correct episode on pages that are not its own episode-page

= 2.10.0 =

Add support for Podlove Web Player 5

Podlove Web Player 5 is the latest overhaul of our podcast web player.
It comes with its own configuration interface giving you full control over its appearance.

Activate it in `Podlove > Podcast Settings > Player`.
You are then prompted to install the "Podlove Web Player" plugin if you don't have it installed already.

Configure the web player appearance in `Settings > Podlove Web Player`.
Existing web player shortcodes and template accessors continue to work as expected.
For detailed shortcode options, please refer to https://wordpress.org/plugins/podlove-web-player/

= 2.9.10 =

* when using Google Analytics tracking, the show title is sent as content group

= 2.9.9 =

Re-Release of 2.9.8

= 2.9.8 =

* add Twig function `get_the_post_thumbnail_url` identical to the native WordPress function
* fix Podlove Web Player 4 issue in twentytwenty theme
* fix some importer issues
* shows module: itunes category can be set per show

= 2.9.7 =

* update JavaScript dependencies

= 2.9.6 =

* update PHP dependencies (including User Agent library for download analytics)
* add: expose voice attribute to transcript templates ([#1062](https://github.com/podlove/podlove-publisher/pull/1062))
* add(templating): add sort direction in seasons and season episodes, enabling `podcast.seasons({order: 'DESC'})` and `season.episode({order: 'DESC'})` ([#1080](https://github.com/podlove/podlove-publisher/issues/1080))
* fix: download list description in analytics on mobile ([#1056](https://github.com/podlove/podlove-publisher/issues/1056))
* fix: JS issue when selecting transcript voices
* fix: escaping error in contributor comments ([#1081](https://github.com/podlove/podlove-publisher/issues/1081))

= 2.9.5 =

* Slacknotes: reactivate date picker

= 2.9.4 =

* fix: error on "file types" settings page

**IAB Conformity**

When it comes to tracking download intents, Podlove Publisher was always close to IAB recommendations, with one exception: the time window in which two requests count as two. Podlove Publisher deduplicates by hour, IAB recommends a day.

There is a new setting in `Podlove > Expert Settings > Tracking`: "Deduplication Window". It enables you to change the window to "day". This is an opt-in setting, the default will continue to be hourly.

See also: [docs.podlove.org: IAB Conformity](https://docs.podlove.org/podlove-publisher/guides/download-analytics.html#iab-conformity)

This feature is sponsored by [Lage der Nation](https://lagedernation.org).

= 2.9.3 =

* add quick edit for episode number [#1096](https://github.com/podlove/podlove-publisher/pull/1069)
* fix settings tab issues when using a language in WordPress other than english ([e613e99](https://github.com/podlove/podlove-publisher/commit/e613e99bb4f07bb88234146567e76d21ce06f5ff))
* fix issue with category search / pages
* fix auphonic module issue in Gutenberg editor

= 2.9.2 =

* update Podlove Web Player (fixes issue when sharing/embedding the player)
* fix PHP notices [#1066](https://github.com/podlove/podlove-publisher/issues/1066) [#1064](https://github.com/podlove/podlove-publisher/issues/1064)

= 2.9.1 =

* fix web player sharing when using CDN player
* fix duplicating posts: create new guid; do not copy analytics [#1048](https://github.com/podlove/podlove-publisher/issues/1048)

= 2.9.0 =

**New Apple iTunes Categories**

Apple updated their list of available iTunes categories.
Please check in `Podlove > Podcast Settings > Directory > iTunes Category` if you need or want to update your category.
In case your previously selected category does not exist any more, a warning is shown.

Only one category is selectable now (instead of previously 3) to conform with iTunes specifications.

**Download tracking with Google Analytics**

Set your Google Analytics Tracking ID in Podlove > Expert Settings > Tracking.
Then every download intent will be forwarded to Google Analytics.

[#1058](https://github.com/podlove/podlove-publisher/pull/1058)

**Other**

* fix: check if podlovePlayer function is available before calling it [#1060](https://github.com/podlove/podlove-publisher/pull/1060)

= 2.8.10 =

* update Podlove Web Player 4 to latest version

= 2.8.9 =

* update Podlove Web Player 4 to latest version
* remove PHP dependency leth/ip-address

= 2.8.8 =

* update Podlove Web Player 4 to latest version

= 2.8.7 =

* update Podlove Web Player 4 to latest version
* add player setting to either use the podcast language or user's browser language for web player interface ([#1008](https://github.com/podlove/podlove-publisher/pull/1008))
* fix [#1047 Use of PHP 5.6 feature in Shows module](https://github.com/podlove/podlove-publisher/issues/1047)
* report duplicate guids in system report

= 2.8.0 =

**Transcripts**

“Transcripts” is the new module to manage transcripts, show them on your site and in the web player. You can import them from webvtt files. If you are already using the Podlove Publisher contributors, you can assign people to the voices inside the webvtt. Then you even get avatars automatically in your transcripts.

See [https://forschergeist.de/podcast/fg066-klimaneutralitaet/](https://forschergeist.de/podcast/fg066-klimaneutralitaet/) for an example episode with transcripts in the web player.

**Transcripts: Shortcode**

The shortcode `[podlove-transcript]` displays a pretty html version of the transcript for your website.

**Transcripts: Twig Template Support**

Of course there is a fully featured template API for transcripts as well. For example:

{% for group in episode.transcript %}
    <div class="ts-group">

        <div class="ts-speaker-avatar">
            {{ group.contributor.image.html({width: 50}) }}
        </div>

        <div class="ts-text">
            <div class="ts-speaker">
                {{ group.contributor.name }}
            </div>

            <div class="ts-content">
                {% for line in group.items %}
                <span class="ts-line">{{ line.content }}</span>
                {% endfor %}
            </div>
        </div>

    </div>
{% endfor %}

See [https://docs.podlove.org/podlove-publisher/reference/template-tags.html](https://docs.podlove.org/podlove-publisher/reference/template-tags.html "documentation") for all details.

**Global Podcast Analytics**

The following metrics are now available for the whole podcast:

- downloads per month
- top episodes
- episode asset
- podcast client
- operating system
- download source

**Raw Analytics**

I wouldn’t call this an Analytics API but since it exists to power the analytics screen, I might as well document it. The following endpoints return results in CSV format for easy processing or import to spreadsheets.

Here is an example call that returns the number of downloads in March 2019:

	https://your.domain/wp-admin/admin-ajax.php?action=podlove-analytics-global-downloads-per-month&date_from=2019-03-01T00:00:00.000Z&date_to=2019-03-31T23:59:59.999Z

All requests take the same three parameters:

- `action` defines what data you want
- `date_from` is the start date in ISO 8601
- `date_end` is the end date in ISO 8601

Available actions are:

- podlove-analytics-global-downloads-per-month
- podlove-analytics-global-top-episodes
- podlove-analytics-global-assets
- podlove-analytics-global-clients
- podlove-analytics-global-systems
- podlove-analytics-global-sources

You need to be logged in with admin permissions for the requests to work.

Disclaimer: Depending on the popularity of your podcast and chosen date range, the requests may take a long time to respond, or even fail if the calculation takes longer than the timeout defined in your web server.

**Other**

- background jobs: add button to abort job
- new tab style for chapter marks section
- Podlove Web Player 4 fallback for old browsers and disabled JavaScript

= 2.7.24 =

* update Podlove Web Player 4

= 2.7.23 =

* **slacknotes:** is now accessible to authors and editors
* update some PHP dependencies (including Twig)
* add ability to specify visible components in Podlove Web Player V4 ([#1032](https://github.com/podlove/podlove-publisher/pull/1032))

= 2.7.22 =

* (maybe) fix Gutenberg issues when creating a new episode

= 2.7.21 =

**Bug Fixes**

* **slacknotes:** avoid duplicate vue for-loop keys ([7578cdf](https://github.com/podlove/podlove-publisher/commit/7578cdf))
* **slacknotes:** date range filter ([2982f2b](https://github.com/podlove/podlove-publisher/commit/2982f2b))
* **slacknotes:** fix loading of datepicker component ([13ca12b](https://github.com/podlove/podlove-publisher/commit/13ca12b))
* **slacknotes:** follow redirects when resolving URLs ([5b39746](https://github.com/podlove/podlove-publisher/commit/5b39746))
* **slacknotes:** handle slack-resolved URLs in pipes format ([b08ec53](https://github.com/podlove/podlove-publisher/commit/b08ec53))
* **slacknotes:** hide link-fetch prompt while fetching ([f4e78e3](https://github.com/podlove/podlove-publisher/commit/f4e78e3))
* **slacknotes:** url re-fetching when changing dates ([344456d](https://github.com/podlove/podlove-publisher/commit/344456d))


**Features**

* **slacknotes:** add setting for link ordering ([c4c824e](https://github.com/podlove/podlove-publisher/commit/c4c824e))
* **slacknotes:** show link time ([53077b1](https://github.com/podlove/podlove-publisher/commit/53077b1))
* **slacknotes:** when resolving URLs, use effective URL ([974c7f8](https://github.com/podlove/podlove-publisher/commit/974c7f8))

= 2.7.20 =

**Slacknotes**

This release is sponsored by [Lage der Nation](https://lagedernation.org).

The new "Slacknotes" module extracts links and their metadata from a Slack channel and generates HTML that can be used as show notes.
A short demo video is available [in the documentation](https://docs.podlove.org/podlove-publisher/guides/slacknotes.html).

**Other**

* the "Modules" screen has been redesigned
* updated JavaScript and CSS processing library and other dependencies

= 2.7.19 =

We are now compatible to the new WordPress 5.0 Gutenberg block editor.
You can choose to use the new editor or stay with the classic editor for now by installing the classic editor plugin by WordPress.

* feed: do not include `<itunes:summary>` tag if it is empty (Apple Podcast requirement)
* adjustments for Gutenberg compatibility:
  * Shows metabox moved from sidebar to main area
  * remove broken form field autogrow behavior
  * fix contributors UI initialization

= 2.7.18 =

* improve feed generation time when seasons are used ([#1010](https://github.com/podlove/podlove-publisher/issues/1010))
* title migration module:
  * remove "episode type" selector, always use "full"
  * add warning when there might be too many form fields
* new PHP constant `PODLOVE_DISABLE_TAG_AND_CATEGORY_SEARCH` ([#1017](https://github.com/podlove/podlove-publisher/issues/1017))
* feed item limit: "1" is now an option
* add missing contributor template accessors: organisation, department, jobtitle
* ensure Gutenberg editor is not used for episodes

= 2.7.17 =

**Downloads Data Export**

Download data per episode can now be exported as JSON and CSV.
On the Analytics page you will now find a simple export interface.
Select the episodes you want in the export or don't select any to export them all at once.

**WP REST API Support**

Backbone for the data export is an implementation of the WP REST API.

Endpoint for the episode custom post type:

- /wp-json/wp/v2/episodes

Custom endpoints for episode analytics:

- /wp-json/podlove/v1/analytics/episodes/
- /wp-json/podlove/v1/analytics/episodes/123
- /wp-json/podlove/v1/analytics/episodes/123,82

All analytics are available as CSV by adding `?format=csv` as parameter, for example `/wp-json/podlove/v1/analytics/episodes/?format=csv`

Analytics endpoints require the `podlove_read_analytics` permission, the same as viewing analytics in the admin.

Please read https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/ if you want to use these endpoints.

**Other**

* Fix deprecation warning when using multiple categories ([#1009](https://github.com/podlove/podlove-publisher/pull/1009))

= 2.7.16 =

* update Podlove Web Player 4

= 2.7.15 =

* automatically abort stuck background jobs
* contributors now appear in feeds even if they don't have a URI [#939](https://github.com/podlove/podlove-publisher/issues/939#issuecomment-430248520)
* Shows: custom language is now used for Podlove Subscribe Button

= 2.7.14 =

* update Podlove Web Player 4

= 2.7.12/13 =

* use wp_enqueue_script instead of inline JS when calling PWP4, improving compatibility to other plugins ([#1000](https://github.com/podlove/podlove-publisher/issues/1000))
* uninstall: be more specific which options are deleted ([#997](https://github.com/podlove/podlove-publisher/issues/997))
* new filter `podlove_network_module_activate` to force-enable network module ([#995](https://github.com/podlove/podlove-publisher/issues/995))
* new social services: Mastodon, Fediverse, Friendica ([#987](https://github.com/podlove/podlove-publisher/issues/987), [#968](https://github.com/podlove/podlove-publisher/issues/968))
* fix related episodes disappearing when using post scheduling ([#980](https://github.com/podlove/podlove-publisher/issues/980))
* fix seasons error when there are no episodes ([#963](https://github.com/podlove/podlove-publisher/issues/963))
* related episodes: order by post date ([#947](https://github.com/podlove/podlove-publisher/issues/947))

= 2.7.10/11 =

* update Analytics JS frameworks, fixing [#982](https://github.com/podlove/podlove-publisher/issues/982)
* add download location analytics chart
* remove weekday chart
* add option to disable average episode display

= 2.7.9 =

Too much slimming in 2.7.8. Undo.

= 2.7.8 =

* fix release workflow
* slim down plugin size by removing unnecessary files from release
* update Podlove Web Player 4

= 2.7.7 =

Update 2.7.5 changed the way download tracking works to comply with GDPR. We tried the radical approach and anonymized IPs. As it turns out, this is not viable. Download numbers are skewed by this change and often much lower than they realistically should be. If you saw a drop in downloads since updating to 2.7.5 or 2.7.6, this is the reason.

The good news is that this update changes download tracking again and new download numbers should get back to normal. The bad news is that the data since the GDPR update cannot be fixed/restored because it's missing data granularity -- which was the point of the change; just not anitcipating the effect on the actual download numbers.

So what's the new tracking approach?

Podlove Publisher now stores the `request_id` again just like before the update: a hash based on the actual IP address and the user agent. What's new is that now once a day, all `request_id`s older than 24 hours are salted again, making it impossible to restore IPs from them. This 24 hour window is enough to determine download numbers exactly as before the GDPR update.

To be clear, IPs are never stored in plain text. But since IPs could be restored by brute force attack from the temporary unsalted `request_id` hashes, they have to be treated like plain IPs. The text snippet for your privacy page has been updated in the docs and you should update it on your site: https://docs.podlove.org/podlove-publisher/guides/dsgvo-gdpr.html

= 2.7.6 =

No changes.

= 2.7.5 =

**Preparation for GDPR/DSGVO**

If you are using Podlove Publisher Tracking/Analytics, an update to this version is highly recommended.

Tracking uses a `request_id` to be able to determine when two requests came from the same user and should be counted as one unique access. This request id used to be a hash of the original IP address and the user agent. This approach however is vulnerable to a brute force attack to get the IP address back from the hash. Here's what we are doing about that:

First, we anonymize the IP before generating the hash. So instead of using `171.23.11.209`, we use `171.23.11.0`.

Second, you need to deal with the existing `request_id`s. There is a new "DSGVO" section under "Tools" with a button that will rehash all existing `request_id`s with a randomly generated salt. That way it will become unfeasible to determine the original IP address but your analytics will stay the same.

In case you have a lot of downloads (let's say much more than 50.000), you may want to do this via command line because that will be _much_ quicker than via the tools section. You need [wp-cli](https://wp-cli.org/), then simply call `wp eval 'podlove_rehash_tracking_request_ids();'`. On a multisite, pass the blog id as a parameter: `wp eval 'podlove_rehash_tracking_request_ids(42);'`.

**Other**

* fix Podlove Subscribe Button language parameter
* fix `rel="self"` link in show feeds
* fix Podlove Subscribe Button not delivering show feeds
* templates: handle episode.show access when there is no show
* templates: allow episode filtering by show, for example: `{% for episode in podcast.episodes({show: "example"}) %}`

= 2.7.4 =

No changes, but the previous release is not delivered correctly by WordPress, so this is simply a re-release attempt to fix it.

= 2.7.3 =

* fix: geo database updater
* update Podlove Web Player 2: remove Flash Fallback
* update Podlove Web Player 4

= 2.7.2 =

* fix: `itunes:image` tag in show feeds
* fix: "Debug Tracking" choosing wrong media files to check availability
* enhancement: "Debug Tracking" now suggests disabling SSL-peer-verification if URL cannot be resolved and https is used
* system report: include active plugins

= 2.7.1 =

* fix: PHP warning when the_title filter is called with only one parameter
* fix: handle colons in migration tool
* fix: PWP4 warning when using shortcode
* new service: letterboxd

= 2.7.0 =

**New Module: Shows**

With shows you can offer feeds to subtopics of your podcast. Here's how it works: You create a show and define show meta, similar to a podcast: title, slug, subtitle, summary, image and language. These fields override your podcast settings. All other settings are the same as your podcast.

For each episode, you decide which show it's in. Each show has its own set of feeds that listeners can subscribe to. The main feed remains unchanged, containing all episodes from all shows.

The Podlove Subscribe Button can be configured to subscribe to a show by referencing the show slug. Use the shortcode `[podlove-subscribe-button show="show-slug"]` or the template tag `{ podcast.subscribeButton({show: 'show-slug'}) }}`.

We do not recommend using Shows and Seasons at the same time.

**Updated Metadata for Podcast/Episode/Seasons according to iOS11 Specification**

Apple announced an [updated specification for feed elements](http://podcasts.apple.com/resources/spec/ApplePodcastsSpecUpdatesiOS11.pdf). These changes enable the Apple Podcasts app to present podcasts in a better way. But since these feed extensions are readable by any podcast client, we expect others to take advantage of these new fields soon. Here is how we implemented the specification:

- The podcast has a new "type" field where you can select between "episodic" and "serial", which may affect the order of episodes. The field `<itunes:type>episodic</itunes:type>` appears in the feed.
- Episodes have a new "title" field. It defaults to the episode post title but can be set separately now, allowing you to define different titles for the website and podcast clients. The field `<itunes:title>Interview with Somebody Infamous</itunes:title>` will appear in the feed.
- Episodes have a new "type" field where you can select between "full" (default), "trailer" and "bonus". This won't have any effect in the Publisher but may be used by podcast clients. The field `<itunes:episodeType>full</itunes:episodeType>` appears in the feed.
- Episodes have a new "number" field. If used, `<itunes:episode>42</itunes:episode>` will appear in the feed.
- Episodes in seasons will have an `<itunes:season>2</itunes:season>` field in the feed automatically.

We decided to complement these changes by introducing a podcast mnemonic/abbreviation field. Now we can autogenerate blog episode titles, based on the episode number and title, if you like. The mnemonic can be set in podcast settings. The setting to autogenerate blog episode titles is an expert setting in the "Website" section.

To help existing podcasts to conform to these new fields we made a "Title Migration" module which will greet you with a notice once you update the Publisher. It will try to extract episode numbers and titles from your existing titles, saving you time and effort updating each episode one by one.

**Template API Changes**

- `episode.title` now returns the new episode title field, if it is set, but has a fallback to the post title. If you want a specific version, use `episode.title.clean` or `episode.title.blog`.
- the post title of an episode can still be accessed via `episode.post.post_title`
- new accessor: `episode.number`
- new accessor: `episode.type`
- new accessor: `podcast.mnemonic`
- new accessor: `podcast.type`
- new accessor: `season.mnemonic`

**Podlove Web Player 4**

The Shortcode `[podlove-web-player]` accepts several parameters, increasing its versatility.

With `post_id` you can embed episodes on any page, for example `[podlove-web-player post_id="1234"]`.

Every [config parameter available](http://docs.podlove.org/podlove-web-player/config.html) can be overridden using shortcode attributes. The only difference from the linked documentation page is the notation. For nested configs like `show.title` use underscores (`_`) instead. For example, display a green player with custom title like this: `[podlove-web-player show_title="Special Title" theme_main="#00ff00"]`

You can now also display a player with _live content_ like this: `[podlove-web-player mode="live" audio_0_url="http://mp3.theradio.cc/" audio_0_mimeType="audio/mp3" title="Livestream" link="https://theradio.cc"]`

You can choose to deliver Podlove Player via Podlove CDN (Content Delivery Network) or via your WordPress server. CDN is the default for new setups but if you are already using Podlove Publisher we continue delivering Podlove Player via your WordPress server unless you explicitly change it.

Podlove Web Player 4 is the new default player.

**Other**

* analytics: show download totals for last 24 hours and last 7 days in overview
* Podigee Player: add support for transcripts
    - create a Podigee Transcript asset
    - set this asset in Expert Settings > Web Player
    - See https://cdn.podigee.com/ppp/samples/transcript.txt for an example transcript
* Podlove Web Player 4: support contributors
* player settings: when no episode or files are available, use a "Podlove" demo sound
* reduce Podlove Template Cache duration from 1 day to 1 hour for the following change:
* new template accessor: `{{ episode.total_downloads }}`
* New in "Global Feed Settings": An option for how the episode title should be displayed. It defaults to "Blog Post Title", so that after the iOS 11 title migration, the output does not actually change -- following the principle of least surprise. However, the setting can be changed to "Episode Title", which is the new clean title, or "Custom Template", which is a title template with the same capabilities as the blog post title template.
* when using the Podlove Subscribe Button CDN and the CDN is not reachable, fall back to the locally hosted script
* fix Geo DB Updater: use our own Podlove CDN as download source
* fix quotes in contributor fields
* fix WordPress conditionals in episode archives
* fix deleting related episodes ([#907](https://github.com/podlove/podlove-publisher/issues/907))
* fix network admin bar now does not include broken links if Publisher is not activated network-wide ([#933](https://github.com/podlove/podlove-publisher/issues/933))
* fix import getting stuck issue ([#910](https://github.com/podlove/podlove-publisher/issues/910))
* Bitlove module: remove all frontend functionality because it has been dysfunctional for a long time
* fix Auphonic module showing wrong status message after file upload
* fix Audacity chapter import when times contain commas
* fix email notification issue where not emails were sent ([#938](https://github.com/podlove/podlove-publisher/issues/938))
* fix feed redirect issue for HTTP/1.0 clients
* fix network module: only activate when the plugin is activated network-wide, not when the plugin in active within a multisite
* fix calculation of contribution counts
* Fix various issues in the download table display. Until now, new downloads were calculated hourly, which provides a good estimate but often not exact numbers. The calculation could also get stuck, leading to missing data display. From now on, the estimates are still calculated hourly but additionally _a full, precise aggregation is done once a day_, which should lead to more consistent numbers overall.
* enhance email error reporting
* enhance open graph module: detects WP SEO plugin and does not output any tags to avoid conflicts
* social services: add SlideShare
* show warning if upload directory is not fully qualified
* remove download section from default template (because it is included in PWP4)
* image cache: instead of returning invalid URLs with 0 width and 0 height when something goes wrong, return the source URL instead
* episode list: add display option to display episode number as a column
 add Liberapay as donation service
* display current season in episode form

= 2.6.4 =

Podlove Web Player 2:

- fix: Remove Flash and Silverlight fallbacks due to security issue
- fix: resolve compatibility issue with mediaelement library shipped with WordPress

Podlove Web Player 4:

- fix: newlines in summary are not converted to HTML linebreaks

= 2.6.3 =

- new PHP constant `PODLOVE_IMAGE_CACHE_FORCE_DYNAMIC_URL`: When `PODLOVE_IMAGE_CACHE_FORCE_DYNAMIC_URL` is set to `true`, the static "physical" URL is never exposed, only the dynamic URL. This can be helpful when page caches keep serving the static URL even though it does not exist for some reason. The dynamic URL always works. Drawback is that serving with the dynamic URL is a bit slower, so only use it if you encounter caching issues.
- update mediaelement dependency in Podlove Web Player 2
- update Podlove Web Player 4

= 2.6.2 =

- send `HTTP 410 Gone` when accessing a download URL to a depublished episode
- fix Podlove Web Player 4 appearing on wrong positions when multiple players are embedded on the same page

= 2.6.1 =

- fix template bug (when a template returned an empty result, the template title was displayed instead)

**Flattr**

- rename "Flattr Username" to "Flattr ID"
- insert `flattr:id` meta tag to page heads, which is required for their new system

= 2.6.0 =

**New Module: E-Mail Notifications**

This is a new module complementing the existing contributor module. Once activated, it enables you to send emails to contributors when an episode gets published.

You can find the settings at `Contributors > E-Mail Notifications`. The settings are:

- _email subject and message_, customizable with all available template tags
- a _time delay_ until the message is sent after the episode was published
- who will receive the message by using _groups and roles as a filter_

**Podlove Web Player 4 Alpha**

After months of work we're ready to show you what we're working on: A new take on the modern Web Player. We learned from previous attempts and thought about responsiveness and embeddability from day one. Of course there's full support for chapter marks, too.

Give it a try if you like. Be aware though that it's marked as _alpha_, meaning we're still working on new features and fixing bugs in existing ones when we find them. But if you're curious, head over to `Podlove > Podcast Settings > Player`, switch to Podlove Web Player 4 and [let us know what you think](https://community.podlove.org/c/podlove-web-player).

Once Podlove Web Player 4 is stable, it will be the only actively supported Podlove Player. Podlove Web Player 3 isn't being developed anymore.

And finally, all web players can now be previewed on the settings screen.

**Templates**

* Assets now have an identifier `asset.id` to quickly access a file for an asset within an episode, for example: `episode.file("mp3")`
* Contributors already had such an id, but now there is a new accessor to get a single contributor by id, for example: `podcast.contributor("jerry")`
* DEPRECATED: id parameter in `podcast.contributors` to access a single contributor: `podcast.contributors({id: "jerry"})`.
* Feeds can now be accessed the same way by their slug: `podcast.feed("mp3")`

**Analytics**

Download tracking is now turned on by default in new setups.

**Bits & Pieces**

* network dashboard statistics: fix average length and file size; remove "days between episodes"
* episodes: prettify "detect episode duration" UI
* episodes: enhance media file UI:
  * rename "update" button to "verify"
  * move "update all media files" button above "verify" buttons and rename it to "verify all"
  * show file URL even if url cannot be reached
  * show Bytes in human readable format
* episodes: fix focus when adding contributors to episodes
* episode asset settings: increase assets per page from 10 to 100
* episode asset settings: fix format filtering by type
* episode assets: new "name" attribute for reference in templates, for example `episode.file('cover').url` where "cover" is the asset name
* support page: add Publisher icon to "Get Professional Support" callout
* contributors: services only get deleted on "Save Changes", not immediately
* contributors: fix sorting by contribution count in the admin interface
* contributors: fix minor PHP issue when creating new contributors
* templates: immediately purge cache on updating any template
* Podlove Subscribe Button: add module option to deliver locally instead of using the CDN, but continue to default to CDN
* job dashboard: add mode description to jobs where necessary to distinguish between different run settings
* be tolerant to missing PHP iconv module
* fix Auphonic chapter import
* update Hindenburg chapter parser
* new PHP constant `PODLOVE_DISABLE_IMAGE_CACHE` to disable image caching
* fix security vulnerability (thanks to DefenseCode, who found the vulnerability using their tool ThunderScan and kindly approached us)

= 2.5.3 =

* fix broken settings tabs (introduced by German translation)

= 2.5.2 =

* fix episode sorting by recording date (`podcast.episodes({orderby: 'recordingDate'})`)
* services: use https URLs where available ([#911](https://github.com/podlove/podlove-publisher/pull/911))
* services: add Spreaker ([#912](https://github.com/podlove/podlove-publisher/pull/912))
* contributors: adjust description of "Public Name" because it was misleading ([#914](https://github.com/podlove/podlove-publisher/pull/914))
* services: fix URL encoding issue ([#915](https://github.com/podlove/podlove-publisher/pull/915))
* network: fix system report notice that ensures module is setup correctly ([#916](https://github.com/podlove/podlove-publisher/pull/916))
* remove ADN link from support page

= 2.5.1 =

**Enhance Chapter UI**

* fix encoding of "&" character
* add support for Hindenburg project files

= 2.5.0 =

**New Chapter Management UI**

Until now if you wanted to add chapters to your podcast, you had to write the mp4chaps format by hand into a textfield. The Publisher now finally provides an easy-to-use interface to manage chapters that doesn't require any knowledge about the underlying formats.

The new inferace makes it simple to import chapters from files. We currently support [PSC (Podlove Simple Chapters)](https://podlove.org/simple-chapters/), mp4chaps and Audacity Track Labels. [Let us know](https://community.podlove.org/c/podlove-publisher) if we don't support your favorite program's export format.

**Module: Import/Export**

Podcast import has been rewritten to make full use of Background Jobs. That way podcasts of any size can be imported without running into system resource restrictions for large podcasts.

**Background Jobs**

The jobs dashboard on the tools page now shows job statuses in realtime (refreshes automatically).

Adjusted background job duration parameters and made them configurable. The change of defaults aims to make better use of available cron time (normally 30 seconds per request), which can speed up long running background jobs dramatically.

Please refer to the new [Background Jobs page in the documentation](http://docs.podlove.org/podlove-publisher/developer/background-jobs.html) for more details.

**Bits & Pieces**

* remove module: App.net (because they [are shutting down](http://blog.app.net/2017/01/12/app-net-is-shutting-down/))
* fix tracking export
* redirects (expert settings): redirect counter can be reset
* contributor avatars use WordPress media picker
* optimize use of JavaScript:
    - only load scripts on pages that require them
    - concatenate and minify some scripts

= 2.4.6 / 2.4.7 =

- Image cache: support non-pretty permalinks.
- fix feed compatibility issue with [Relevanssi plugin](https://wordpress.org/plugins/relevanssi/)
- services: fix lastfm url scheme (thanks [jazzcrack](https://github.com/jazzcrack))

= 2.4.5 =

Image cache: change URL encoding method to fix Gravatar issues.

= 2.4.4 =

Further image cache improvements:

- reject URLs that are not images
- prefix query vars to avoid naming conflicts with other plugins
- fix resizing sometimes not calculating the correct dimensions
- enhancement: skip http when using images shipped with the Publisher; copy images from Publisher to cache directory on filesystem instead

= 2.4.3 =

Fix issue with broken images introduced in 2.4.2.

= 2.4.2 =

**Improve Image Caches**

Images relevant to the Podlove Publisher are downloaded so they can be resized to desired dimensions. This makes it possible to deliver retina-images and improves the website performance because only the appropriate image size delivered.

This requires files to be downloaded. Until now, this happened in the background to avoid slow web page load times when the image is fetched. Until the cache existed, the original file URL to the unresized file was used. This worked alright, unless you were using a page cache plugin. The original file URL would be used much longer than necessary, causing big file downloads.

Now a different approach is used. Instead of the original file URL, a dynamic link is generated, looking like this one: `https://example.com/podlove/image/http%3A%2F%2Fexample.com%2Fmedia%2Fmypodcast%2Fmy-podcast-logo-1500x1500.jpg/300/300/0/my-podcast`. When this link is requested, the cached and resized image is either delivered or, if it doesn't exist, generated on-the-fly. Once the cached file exists, the direct link to the cached file is delivered, just like before. The major improvement is that even if the initial URL is stuck in your page cache, the Publisher is now able to deliver a properly resized image anyway.

**Other**

* set correct feed Content-Type in HEAD requests and redirects
* enhancement: repair & clear cache tools print a notice about other cache plugins
* fix "Last Month" download widget in analytics

= 2.4.1 =

* services: Playstation Network Account now links to `http://psnprofiles.com`
* allow spaces in episode slugs
* add help tab to analytics pages
* fix Podlove Web Player 2 timecode share link
* fix issue with image cache filenames ([PR 895](https://github.com/podlove/podlove-publisher/pull/895))
* fix WP-Rocket incompatibility with Podigee Player
* fix Auphonic: when neither episode image nor post thumbnail are present, fallback to podcast cover art
* fix PHP warnings in oembed module

= 2.4.0 =

**Background Jobs System**

Crunching numbers for Analytics takes time, especially for popular podcasts with many downloads. The old system was written optimistically and "let's-hope-we-finish-before-we-run-out-of-time"-ish. That was certainly good enough for podcasts with a few hundred downloads per episode, but more likely a gamble for popular shows.
To solve this issue in a scaleable way, we built what is known as "background processing" or "queues". That way we can break big tasks into small chunks and process them step by step. You don't really need to know about this, since the main effect is that calculating analytics should be a smoother experience (if you have ever had troubles in that regard) but if you are curious, have a look at the new "Tools" section, which lists running and recently finished jobs.

**New Analytics Dashboard**

* "Downloads per Day" is a stacked bar chart now so you can see which episode is responsible for peaks
* Downloads table now shows downloads in time segments starting from the moment of episode release for better comparability
* Show total number of all downloads in Analytics Dashboard
* New information under downloads table: age of the data and when the next refresh is due
* _MUCH_ better performance / page load times
* New option to configure how many episodes per page should be shown

**Podlove Web Player 2 Facelift**

* simplified, modernised look
* responsive layout for mobile devices
* fix: updated mediaelement library to fix volume bar display bug

**Podigee Podcast Player**

The [Podigee Podcast Player](https://www.podigee.com/en/podcast-player) is available as an alternative to the Podlove Web Players. It supports everything you can expect from a modern web player like chaptermarks. It is also embeddable.

**New "Tools" Menu**

* A new maintenance section gathers tools like the "Repair" button in one place
* There is a new section for analytics related maintenance
* Import & Export is now in the "Tools" menu

**Improved Logging Display**

* Logging is now in the "Support" menu
* add filtering for different severities
* hide "info" entries by default
* improve readability of data sections

**Other**

* add Emoji support for episode subtitle, summary and chapter marks (requires WordPress 4.2 or newer)
* Web Player setttings moved from Expert Settings to Podcast Settings
* When activating the plugin, add mp3 asset and feed to help users get over the most confusing part of the setup.
* Post thumbnails can be used as episode covers (see settings in "Episode Assets"). This is the new default.
* add contributors shortcode to default template (Many people activated contributors and then wondered why they were not displayed in the episode. Now the shortcode is part of the default template, but only if the contributors module is active.)
* add unmistakable warning if curl is not available and provide actionable steps for a solution
* change feed setting "Include HTML Content" default to "on"
* remove log entries for beginning and ending asset validations
* move feed protection into separate module
* move debug log from Dashboard to Support page
* add ptm_request parameter to redirected tracking URLs which contains a uuid
* add fyyd.de module
* add option to enforce http or https in URLs inside feeds (enclosures, images) and the actual feed URLs; see `Expert Settings > Website`
* improve tracking: ignore 1-byte requests
* update user agent library (new/updated clients: Podcat, Downcast, iCatcher, BashPodder)
* show total downloads per site in network dashboard
* remove `<itunes:keywords>` from feed (it disappeared from the specification)
* remove module: "Feed Validator"
* update recommended image size to 3000x3000 pixel
* add heartbeat to keep note of when tracking is active
* `shortcode_exists($shortcode_name)` is now available in Twig templates
* system report: add notice if ALTERNATE_WP_CRON is active
* fix tracking export: keep httprange
* fix compatibility with other plugins relying on Spyc library
* fix `{{ episode.duration.totalMilliseconds }}`
* fix image caching issue (invisible characters)
* fix: When plugin requirements are not met, admin notices are now still shown once but the plugin is automatically deactivated after that. This avoids faulty setups.
* fix: show podcast covers in network site switcher
* fix: expert settings not saving on some systems

= 2.3.18 =

* fix Auphonic authentication (https certificate issue)

= 2.3.17 =

* fixes a bug that broke some settings pages

= 2.3.16 =

* security: fix SQL injection
* security: remove several Cross-Site Scripting vulnerabilities

All vulnerabilities require admin capabilities. That means they cannot be exploitet easily, but could be using Cross-site request forgery (CSRF).

Thanks to [RIPS Technologies](https://www.ripstech.com/) for reporting these issues. The issues were found using their Static Source-Code Analyzer RIPS.

= 2.3.15 =

* ensure 3rd party PHP dependencies do not require PHP 5.5 or greater

= 2.3.14 =

* fix: send episode cover to Auphonic if available
* fix: improved download logic for `geoip.mmdb` should prevent faulty downloads
* enhancement: error message for faulty `geoip.mmdb` includes instructions on how to manually fix the file
* enhancement: automatically switch off geo tracking when no valid geo database is available
* enhancement: clarify episode image asset options

= 2.3.13 =

* fix: sort contributor names while ignoring uppercase/lowercase
* fix: when exporting a podcast, don't call `htmlspecialchars` on arrays because it breaks things
* fix: image caching issue (invisible characters)
* fix: broken geolocation database does not prevent playing episodes
* fix `{{ episode.duration.totalMilliseconds }}`
* fix: `{{ episode.duration }}` returns "00:00" if no duration is set
* fix: contributor avatar URLs with umlauts
* enhancement: check for geolocation database validity in tracking debug section
* enhancement: add current theme and feed URLs to system report
* Podlove Subscribe Buttons: parameters in templates and shortcodes can override Publisher provided fields: 'title', 'subtitle', 'description', 'cover'

= 2.3.12 =

Design Update for Podlove Subscribe Button

* The button now follows a flat design and has more options for customizability.
* See [docs.podlove.org/podlove-subscribe-button](http://docs.podlove.org/podlove-subscribe-button/) for a range of possible display variants.
* Widget module has been updates to support a color picker and settings for size, format and style. When using the "WordPress Customizer" you get a live preview of the button.
* If you are using the Template API, have a look at the updated [`podcast.subscribeButton` parameters](http://docs.podlove.org/podlove-publisher/reference/template-tags.html#podcast).

= 2.3.11 =

* fix feed issue that appeared with WordPress 4.5 (wrong content type)

= 2.3.10 =
* when activating the plugin, add mp3 asset and feed to help users get over the most confusing part of the setup
* fix tracking export: keep httprange
* fix compatibility with other plugins relying on Spyc library
* improve tracking: ignore 1-byte requests
* update user agent library (new/updated clients: Podcat, Downcast, iCatcher, BashPodder)
* remove `<itunes:keywords>` from feed (it disappeared from the specification)
* update recommended image size to 3000x3000 pixel

* fix Podlove Subscribe Button iTunes link
* add new "getting started" video to readme

= 2.3.9 =

* fix `open_basedir` related issues

= 2.3.8 =

**Bugfixes**

* fix '&' issue in some fields when exporting/importing
* player: pass podcast language code to web player v3
* open graph: do not include non-downloadable assets
* open graph: use tracking URLs if available
* template editor: add scrolling when having many templates in the list
* auphonic: disable "Open Production" button when no production is selected
* player: chapters visibility setting now applies to v3 beta player
* more defensive feed gzipping for compatibility with various caching plugins
* fix feed discovery cache (can now handle both http and https at the same time)

**Enhancements**

* enhance error message when resolving URL fails


= 2.3.7 =

* fix "add new" contributor button
* fix migration class error
* fix migration system report display
* use default WordPress background color in migration wizard

= 2.3.6 =

**Bugfixes**

* When creating a new contributor, social and donation services are saved correctly
* Deleting a contributor shows the correct confirmation message

**Enhancements**

* Podlove Subscribe Button: When an iTunes id is known for a feed, the button does not just pass the feed URL to the client when iTunes or Podcasts App are chosen. It redirects the user to the iTunes directory first. Because if you don't do this, "[it] does not increase your visibility on the iTunes Store or allow you to earn commission as part of the Affiliate Program." (http://www.apple.com/itunes/podcasts/specs.html)
* Detect and warn if an episode slug has been used before

= 2.3.5 =

Update Web Player v3

* beta.6 https://github.com/podlove/podlove-web-player/releases/tag/v3.0.0-beta.6
* rc.1 https://github.com/podlove/podlove-web-player/releases/tag/v3.0.0-rc.1
* rc.2 https://github.com/podlove/podlove-web-player/releases/tag/v3.0.0-rc2
* rc.3 https://github.com/podlove/podlove-web-player/releases/tag/v3.0.0-rc3

= 2.3.4 =

**Web Player (v3 Beta)**

The new web player can be selected in `Expert Settings > Web Player`. Please try it out and send us your feedback. Thanks!

* update player
* add theme player options
* fix player permalink parameter
* fix player width on Mobile Safari

**Other**

* fix Auphonic workflow bug: when finishing a production, media files would sometimes erroneously be detected as not existing
* detect when the configured Auphonic Preset does not exist
* fix focus when adding new related episode rows
* chosen search fields allow partial searches
* enable Twig date extension to allow `time_diff` filter; see [Date Extension Documentation](http://twig.sensiolabs.org/doc/extensions/date.html)

= 2.3.3 =

Updating all the things for your pleasure.

= 2.3.2 =

* add template accessor `episode.post` to get WordPress post object
* fix: template call `episode.chapters` returns an empty list when there are no chapters
* fix: deleting image cache when no image cache directory exists
* fix: cache purge also deletes timeout entries
* fix: cache purge affects downloads table
* fix: JavaScript event for secondary download button
* fix: default template assignment on plugin activation
* fix: unpublished relates episodes do not appear when using the shortcode or template accessor

= 2.3.1 =

* simplify download buttons (`[podlove-episode-downloads style="buttons"]`) style to better adapt to themes
* fix: missing "Show URL" download button in twentyfifteen theme
* fix: URL structure for YouTube channels
* fix: player visibility when JavaScript is disabled
* fix: stop loading nonexisting player assets in WordPress admin area
* enhanced system report: change wording for `open_basedir` issue to clarify that it _should_ be fixed but a workaround exists
* enhanced plugin loading
  * When upgrading from version 1.x to 2.x using PHP 5.3, the upgrade lead to the "White Screen of Death" because 2.x requires PHP 5.4. This case is now handled and the Publisher shows an appropriate admin notice.
  * Some shared hosters seem to have problems with the plugin update process, which leads to the Publisher missing files and breaking the site. This is now also detected and a notice appears, asking the user to manually redownload the plugin.

= 2.3.0 =

**New Module: Seasons**

Do you have seasonal content? We got you covered. The new "Seasons" module allows you to group episodes into seasons. Each season has a title and other optional metadata, like a custom image. You can access all this data using the template system.

New Template accessors:

- `episode.season` returns the season for the episode
- `podcast.seasons` returns a list of all seasons
- `season.episodes` returns a list of all episodes in a season

**New Module: Flattr**

Everything Flattr related was moved into its own module.
If you don't use Flattr, you can turn it off and it gets out of your way.

* If you are using the Flattr module, we write Flattr payment information into podcast feeds. This way you don't need to rely on the official Flattr plugin to do this. You can probably deactivate it if you were using it since we provide the main functionality within the Publisher now.
* We recently changed the default `flattr` parameter in shortcodes. Now there's a setting in Flattr Podcast Settings where you can define the default parameter for contributor shortcodes.

**New Module: Related Episodes**

You can now express that episodes are related to each other. You can list all related episodes using the new shortcode `[podlove-related-episodes]` or using the template accessor `episode.relatedEpisodes`.

**Templates & Themes**

If you are developing themes, you now have full access to the Publisher Template system. The API is exactly the same as in Twig, just the syntax is different. At the moment, there are 4 entry points:

- `\Podlove\get_episode()`
- `\Podlove\get_podcast()`
- `\Podlove\get_flattr()`
- `\Podlove\get_network()`

Please see the ["Understanding Templates" guide](http://docs.podlove.org/guides/understanding-templates/) for more details.

**Other**

* Use WordPress Object Cache API to cache model objects. All entities fetched by id are cached and reused within the same page call. Performance gains are most notably in complex templates, which often access the same data repeatedly.
* Analytics: Update & improve user agent detection library so you can have more accurate analytics.
* Canonical feed URLs. WordPress respects if you want your URLs to end with a slash or not (you do that by adding or removing the trailing slash from your WordPress permalink settings custom structure). Our feed URLs now respect this choice, too. Furthermore, we permanently redirect to the canonical URL if another one was accessed to ensure all clients access _exactly_ the same feed URL.
* News from podlove.org are displayed in the Podlove Dashboard
* Users with role "author" and higher now have access to the Podlove Dashboard and Analytics. They only have access to dashboard sections that make sense for authors, so they won't see logging, feed or asset validation.
* Contributors can now be edited in _Contributor Settings_ (instead of _Episodes > Contributors_)
* Contributors Social Services: It is now possible to add a YouTube "Channel", not just user profiles
* Contributors Social Donations: Add "paypal.me" option
* Add functionality to automatically determine the duration for episodes. This is especially useful for people who don't use Auphonic, which already determines the duration automatically.
* We are now able to handle media files that are served without a "Content-Length" header. A specific warning is generated and the size is displayed as "unknown", but the files are treated as valid so they can be played.
* Add support for Auphonic webhooks. This allows us to import your episode metadata once an Auphonic production is finished — even if you navigated away from the episode page.
* Podcast cover image can now be uploaded using the WordPress media uploader.
* Add `contributor.gender` template accessor
* Rename network list "description" to "summary" for consistency. In templates `list.description` is now deprecated. Please use `list.summary` instead.
* fix: Shortcodes in episode subtitle and summary are not interpreted any more. Both fields were always considered plain text and having shortcodes leads to various issues, especially in feeds.
* export files are now gzipped if possible
* fix JavaScript incompatibility related to Diaspora plugin ([#771](https://github.com/podlove/podlove-publisher/pull/771), [#770](https://github.com/podlove/podlove-publisher/pull/770), [#425](https://github.com/podlove/podlove-publisher/issues/425), thanks [@noplanman](https://github.com/noplanman)!)
* fix: failing geo-lookup does not break tracking links
* fix: Remove WordPress favicon (since WP 4.3) from podcast feeds if a podcast image is set
* fix: pasting into a template creates change-marker
* fix: tracking import does not skip the last few entries

= 2.2.4 =

* fix: erratically missing chapter information in RSS feeds
* fix: "Allow to skip feed redirects" setting was sometimes ignored

= 2.2.3 =

* fix: web player image fallback to podcast image when an episode image asset is defined but unused
* fix: gzip compression: only set content type if headers have not been sent
* fix: in networks, don't schedule template cleanups for blogs without an active Publisher

= 2.2.2 =

* fix: template cache issue where duplicate purge cronjobs could flood the cron system
* fix: image cache validation (didn't work due to missing library)

= 2.2.1 =

* fix: App.net announcement preview in modules
* fix: asset validations are always scheduled properly
* fix: Remove method calls that require WordPress 4.0+ (wpdb::esc_like)

= 2.2.0 =

**Image Caching, Resizing & Retina Support**

We now take better control of podcast images, episode images, contributor avatars and our own social icons.
We are able to *resize* them to ideal sizes, which results in *faster page load times* for your users. *Retina
images* for higher-resolution displays are also supported. We do this automatically, so all you need to do
is click update, lean back and enjoy.

Read all the details in our blog post ["Podlove Publisher 2.2: Say hello to image caching"](http://podlove.org/2015/05/20/podlove-publisher-2-2/)

This update increases the WordPress requirement from 3.0 to 3.5 (due to the required image editing functionality).

**Other**

* fix: duplicate feed discovery
* fix: ignore incomplete feed configurations
* fix: don't include network admin module css in frontend
* fix: dashboard episode edit links
* fix: when deleting WordPress Network sites, trigger plugin uninstall to remove database tables
* fix: web player flash fallback
* fix: network templates now also appear in the template widget and template auto-insert setting
* fix: issue where some database tables were not created
* fix: podcast covers are displayed in frontend admin menu bar
* show Twig template errors in dashboard log
* web player template tag can set tracking context: `episode.player({context: 'landing-page'})`
* add `episode.categories` template tag

**Deprecations**

- deprecated `episode.imageUrl`, use `episode.image` instead
- deprecated `episode.imageUrlWithFallback`, use `episode.image({fallback: true})` instead
- deprecated `podcast.imageUrl`, use `podcast.image` instead
- deprecated `service.logoUrl`, use `service.image` instead
- deprecated `contributor.avatar`, use `contributor.image` instead

While you are changing these, consider scaling them down appropriately. Your images are probably huge but in many cases you don't need the full size. So instead of `episode.image` or `episode.image.url`, specify a size, like this `episode.image.url({width: 200})`.

= 2.1.3 =

* add warning in system report for users with default permalink settings (which is problematic for some podcast clients)
* enhancement: delete caches in all blogs when changing a network template
* enhancement: delete caches when changing the template default assignment
* enhancement: do not rely on openssl module
* fix: add flattr setting to contributors general tab
* fix: duplicate episodes when using `podlove.episodes` template accessor
* fix: correctly fire plugin activation hooks in network mode
* fix: ensure network module is activated correctly
* fix: "Add New" link in empty list tables

= 2.1.2 =

* fix issue with users that have open_basedir set, which lead to all assets being invalid

= 2.1.1 =

* fix: remove obsolete "Add New" template button from network templates screen
* fix: template autoinsert does not use deprecated "id" parameter
* fix: template widget does not use deprecated "id" parameter
* fix: duplicate episodes in feeds
* fix: some server configurations (especially on shared webhosting) break cURLs ability to follow HTTP redirects. We now check for that configuration and, if necessary, resolve the URL manually before continuing normally.
* fix: XSS vulnerabilities in contributors search
* fix: Template accessor `contributor.id` now correctly returns the id, not the uri. `contributor.uri` is the new accessor to get the uri.
* fix: Filtering contributions by id is now correctly affected by other filters, like group and role. Until now, `podcast.contributors({id: 'james', role: 'on-air'})` always returned James, no matter if he had the given role or not.
* add "Add New Contributor" item to contributor select list. Selecting it opens the screen to add a new contributor.
* add Twig version to system report

= 2.1.0 =

**Networks: WordPress Multisite Support is Here**

- dedicated WordPress Multisite support
- "My Sites" menu features podcast covers and menus include often used pages like "Podlove Dashboard" and episodes
- Network Dashboard provides a birds-eye view over your podcast empire
- Network-Templates that are accessible in every podcast
- Podcast lists: give templates access to multiple podcasts at once, allowing you to automatically list all podcasts in your network, the 10 last episode releases in your network and much more

**Widgets**

We added a happy bunch of widgets to make your life easy.

* Podcast Information: Display cover, subtitle and summary of your podcast
* Recent Episodes: Display a list of recent episodes, with cover art and duration if you like
* Template: Display any Publisher template in a widget area
* Podcast License

The Subscribe Button Widget now defaults to "Big with Logo" and auto-width. It has also been renamed to "Podcast Subscribe Button" to be distinguishable from the new standalone plugin.

**Templates**

* add accessors `{{ podcast.landingPageUrl }}`, `{{ podcast.subscribe_button }}` (see http://docs.podlove.org/reference/template-tags/#podcast)
* add accessor `{{ flattr.button }}` (see http://docs.podlove.org/reference/template-tags/#flattr)
* add accessor `{{ episode.podcast }}`
* add query parameters to ``{{ contributor.episodes }}`:

    - group: Filter by contribution group. Default: ''.
    - role: Filter by contribution role. Default: ''.
    - post_status: Publication status of the post. Defaults to 'publish'
    - order: Designates the ascending or descending order of the 'orderby' parameter. Defaults to 'DESC'.
      - 'ASC' - ascending order from lowest to highest values (1, 2, 3; a, b, c).
      - 'DESC' - descending order from highest to lowest values (3, 2, 1; c, b, a).
    - orderby: Sort retrieved episodes by parameter. Defaults to 'publicationDate'.
      - 'publicationDate' - Order by publication date.
      - 'recordingDate' - Order by recording date.
      - 'title' - Order by title.
      - 'slug' - Order by episode slug.
      - 'limit' - Limit the number of returned episodes.

**Other**

* add gender contribution statistics to dashboard
* add expert setting "Allow to skip feed redirects"
* add warning in tracking settings when default permalink structure is used
* add support for Auphonic cover art
* add support for Jetpack "Publicize" module to podcast post type
* add warning when open_basedir is set to system report
* add daily cleanup of logging table (only keep entries of previous 4 weeks)
* contributor editing has a tabbed interface
* improved Podlove Dashboard performance
* Open Graph title does not include episode subtitle any more. If a subtitle is available, it is put in front of the summary in the description tag.
* fix: remove Jetpack "Site Icon" from podcast feeds
* fix: empty template editor when last template is deleted
* fix: empty caches when a scheduled episode gets published
* fix analytics episode average calculation for ancient episodes

**API changes**

* Flattr parameter in `[podlove-episode-contributor-list]` now defaults to "no". If you need to reactivate it, use `[podlove-episode-contributor-list flattr="yes"]`
* `[podlove-web-player]` was renamed to `[podlove-episode-web-player]` to avoid clashes with the standalone web player plugin. For now, the old shortcode still works.
* `[podlove-subscribe-button]` was renamed to `[podlove-podcast-subscribe-button]` to avoid clashes with the standalone button plugin. For now, the old shortcode still works.

= 2.0.5 =

* fix: template editor cursor position in Safari (by changing to a different theme that doesn't use bold styles)
* fix: double escaped feed enclosure URLs when using non-pretty-permalinks

= 2.0.4 =

* fix: missing flattr attribute for contributors
* fix: subscribe button description is properly wrapped in p-tags
* fix: faulty valid file if check returns "unreachable" but includes a Content-Length header
* fix: more thoughtful handling of ETags when validating files prevents failing updates
* fix: "NaN" analytics should display properly now
* fix: off-by-one display in analytics
* fix: don't HTML-encode quotes in episode title/subtitle/summary since it leads to invalid feeds
* add trakt.tv to the services list
* add support for RSS channel image tag

= 2.0.3 =

*Allow Non-Admins to access Analytics*

Analytics have a new capability called "podlove_read_analytics".
You can provide access to, for example, editors, using the following code snippet:

    function podsnip_add_capability() {
        // default roles: editor, author, contributor, subscriber
        $role = get_role('editor');
        $role->add_cap('podlove_read_analytics');
    }
    add_action( 'admin_init', 'podsnip_add_capability');

You can add snippets using the "Code Snippets" plugin.

*Bugfixes*

* fix: use proper HTTP method to create/update/delete templates
* fix: don't remove URLs from chapter marks when saving
* fix: optional episode form elements can be saved

= 2.0.2 =

* fix: include missing YAML library
* fix: namespacing issue in uninstall procedure
* fix: debug tracking example file must be downloadable

= 2.0.1 =

**Bugfixes**

* fix: properly sanitize episode form data (fixes "A wild Backslash appears")

**Enhancements**

* format download numbers in episode list
* remove check for PHP setting `allow_url_fopen` because we don't rely on it any more

= 2.0.0 =

**Download Analytics**

You want to know more about who listens to your podcast? We got you covered.

We spent months of research and prototyping to find a reliable way of tracking. We are confident that our approach works and produces trustworthy data. If you have not done so yet, you have to activate tracking in _Expert Settings -> Tracking_.

If you are interested in all the technical details, head over to http://docs.podlove.org/guides/download-analytics/.

But what you are seeing now is just the beginning. We have a plethora of ideas on how to give you even more insight into the data available. Stay tuned!

We are curious what you think about the current analytics interface? What do you love? What do you hate? What do you miss? Head over to our new community site and share your thoughts: https://community.podlove.org/

**Bugfixes**

* fix: use `home_url()` instead of `site_url()` to generate tracking URLs
* fix: tracking export does not get stuck forever when it fails once
* fix: disappearing podcast description settings
* fix: add function to repair button that removes duplicate episode entries
* fix: template editor does not forget changes if you reselect a template after changing it
* fix: improve uninstall routine
* fix: wrong month when choosing Auphonic productions
* fix: deactivate Jetpack's OpenGraph when the Publisher OpenGraph module is active

**Other Changes**

* add services: miiverse, prezi
* add missing services via repair button
* Bitlove: add `<bitlove:guid>` to RSS feed and use this to identify files
* moved episode GUID regeneration into separate metabox because it's rarely required
* always check media files when opening an episode edit page
* move podcast cover art from media tab to description tab

* Improved feed settings
  * check for missing and duplicate slugs
  * check for missing asset assignment
  * show prominent warning for detected problems
  * provide contextual help to better understand what's required and why

**Removed Functionality**

* removed module "Auphonic Production Data"
* removed the following shortcodes (use [Template Tags](http://docs.podlove.org/reference/template-tags/) instead)
  * `[podlove-episode-subtitle]`
  * `[podlove-episode-summary]`
  * `[podlove-episode-slug]`
  * `[podlove-episode-duration]`
  * `[podlove-episode-chapters]`
  * `[podlove-episode field="..."]`
  * `[podlove-podcast field="..."]`
  * `[podlove-show field="..."]`
  * `[podlove-podcast-license]`
  * `[podlove-episode-license]`
  * `[podlove-contributors]` (use `[podlove-episode-contributor-list]` instead)
  * `[podlove-contributor-list]` (use `[podlove-episode-contributor-list]` instead)
* removed the following template tags
  * `{{ contributor.publicemail }}` (use social module instead)
  * `{{ license.html }}` (use `{% include '@core/license.twig' %}` instead)

= 1.12.1 =

* fix: catch failed IP categorizations
* fix: solve PHP notice
* add custom icon to close template fullscreen mode
* add custom contributor css to look nicely in twentyfifteen theme

= 1.12 =

- enable some WordPress template tags in Twig Templates: `is_archive()`, `is_post_type_archive()`, `is_attachment()`, `is_tax()`, `is_date()`, `is_day()`, `is_feed()`, `is_comment_feed()`, `is_front_page()`, `is_home()`, `is_month()`, `is_page()`, `is_paged()`, `is_preview()`, `is_search()`, `is_single()`, `is_singular()`, `is_time()`, `is_year()`, `is_404()`, `is_main_query()`
- enable episode filtering by category slug: `podcast.episodes({category: "kitten"})`
- redesigned template editor interface
- fix feed cache issue which lead to enclosure URL mixups
- display PHP deprecation warning aggressively for everyone below 5.4

= 1.11.2 =

- Cache feed items. This drastically reduces load when no feed proxy is used; especially in a "full feed" with many episodes.
- Add Luxembourgish to languages

= 1.11.1 =

Subscribe Button fixes & enhancements:

- don't pass undiscoverable feeds to the button
- don't show a button if no feed is available
- change defaults to "big-logo" and "autowidth"
- fix issue with internal format

= 1.11 =

Say hello to the **Podlove Subscribe button**, the *Universal button to subscribe to buttons in the desired podcast client or player website*. It ships as a widget, so you can easily display it on your site. For more finegrained positioning, you can use the `[podlove-subscribe-button]` shortcode.

More info on those sites:

* Homepage: http://podlove.org/podlove-subscribe-button/
* Help Translate: http://translate.podlove.org
* GitHub: https://github.com/podlove/podlove-subscribe-button

**Other Changes**

* fix `contributor.episodes`: only show published episodes
* fix redirect form: remove url validation
* fix HEAD requests for download URLs
* redirects are counted and displayed in the redirect settings

= 1.10.23 =

**Bugfixes**

* fix social repair module
* empty rss feeds now render properly
* fix issue of randomly breaking URLs
* fix missing files when using auto-publish feature by automatically validating files before publishing
* fix "open" link for last contributor donations item
* fix javascript error in license ui

**New Features**

* add basic client-side input validation to avoid typing errors: Leading and trailing whitespace will be removed automatically. URL and email fields are automatically syntax checked.
* add support for scientific networks: ResearchGate, ORCiD, Scopus
* add explicit support for "Duplicate Post" plugin: duplicated episodes now regenerate GUIDs and contributions are copied, too

**Enhancements & Others**

* contributors form:
  * switch public name and real name fields
  * remove public email field (see deprecations)
  * move contact email field to general section
* ADN module: add option to not fall back on episode cover when no episode image is present
* adjust Bitlove script so it plays well with https sites
* include date in tracking export filename
* move web player settings to expert settings
* public contributor emails are handled by the social module now, instead of being a contributor attribute

**Deprecations & Migration**

If you are using `{{ contributor.publicemail }}` in your templates, you should change it to something like the following:

    {% for service in contributor.services({type: "email"}) %}
        <a target="_blank" href="{{ service.profileUrl }}">{{ service.rawValue }}</a>
    {% endfor %}

= 1.10.22 =

* fix bug in contribution counting
* simplify internal cache key handling to avoid technical issues
* support more licenses (CC4.0, CC0, Public Domain)
* tracking: don't count HEAD requests
* tracking: add manual migration notice to delete accidentally recorded HEAD requests

= 1.10.21 =

* improve HHVM compatibility
* resolve bug concerning internal article linking
* use WordPress method to generate default episode slugs for better results (if you are using a plugin that changes permalink slug behavior, that affects episode slugs now, too)

= 1.10.20 =

**Episode Form Improvements**

* Reorder components
* Display episode title in episode meta box
* Auto-generate media file slug based on the episode title. This is useful if your file slugs match the episode title. But don't worry, you can still change it to your liking if you prefer a different naming scheme.

**Other**

* Podlove Dashboard supports screen options
* fix contribution counting in contributor table (you may have to hit the "repair" button in `Podlove > Support` if you still see wrong numbers)
* fix tracking data export
* fix missing OpenGraph metadata
* improved redirects: added sortability and individual entries can be deactivated without being deleted
* `contributor.id` is accessible via template API now

As mentioned before, we will be phasing out PHP 5.3 soon. Please read the corresponding blog post for more details: http://podlove.org/2014/08/14/podlove-publisher-2-phasing-out-php-5-3/

= 1.10.19 =

* fix caching issue (cache keys were too long in last update, resulting in no cache hits at all)
* fix error when creating a new episode

= 1.10.18 =

**Improvements to media file slugs**

* Slugs may contain slashes now. This allows storing asset files in subfolders and using the WordPress media uploader to manage files.
* Media file validation is more consistent: when you get a green checkmark, the file is guaranteed to be valid and reachable.

**Other**

* Once we release Publisher 2.0, we will increase the minimum PHP version to 5.4 and recommend 5.5. A notice is now displayed in the system report if you are running a version requiring an upgrade.
* Rename a method to avoid a bug in early PHP 5.3 versions

= 1.10.17 =

* tracking now includes range headers
* plugin-migrations are more robust now
* add caching for OpenGraph module
* fix escaping in database logger
* fix feed validator for sites not using "pretty permalinks"
* fix dashboard box state saving
* fix generation of faulty URLs when tracking was on but pretty permalinks off
* fix auto-insertion of nonexisting templates
* fix routing issues when `/%category%/%postname%` is used as permalink structure
* fix rare cache concurrency issues by introducing a 24h auto-expiry
* remove "Critical Podlove Warnings" — they are scary and don't help a lot

= 1.10.16 =

* Hotfix: remove wrong output in HTML sites
* rework support page

= 1.10.15 =

**Various Fixes and Enhancements**

* Supply web player API with more data: "publicationDate" contains an ISO-8601 date and "show.url" the URL to the show.
* Auphonic UI improvement: When selecting a production, the "Select existing production" option disappears.
* Don't pass `redirect=no` parameter to feed URLs
* Ensure web player IDs are unique to avoid rendering bugs
* Fix caching bug that lead to disappearing web player and download buttons
* Fix redirection UI bug
* Flush rewrite rules after migrations to avoid broken links

= 1.10.14 =

**Performance**

A simple yet effective caching strategy has been implemented. This is used to cache rendered site segments. A complete cache invalidation happens when podcast related data changes. This should be a good start since such data rarely changes (mostly when a new episode is published). In a Multisite setup, each site handles its cache separately.

This is implemented using the [Transients API](http://codex.wordpress.org/Transients_API). By default, WordPress uses the database as a caching backend. If you want to squeeze out even more speed, consider installing a [Persistent Cache Plugin](http://codex.wordpress.org/Class_Reference/WP_Object_Cache#Persistent_Cache_Plugins) which replaces the database with a more efficient caching backend, such as memcached or APC. That might require some fiddling around, though.

Caching can be deactivated in the `wp-config.php` with the following line: `define('PODLOVE_TEMPLATE_CACHE', false);`

* Cache Publisher templates
* Cache feed discovery header
* Cache Bitlove widget
* Other minor performance improvements

**Templates**

* There is now a default template containing the player and download section
* Episode contributions can be sorted by comment and position, for example: `episode.contributors({orderby: "comment", order: "DESC"})` or episode.contributors({orderby: "position", order: "ASC"})
* Iterate over the list of episode tags: `{% for tag in episode.tags({order: "DESC", orderby: "count"}) %} {{ tag.name }} {% endfor %}`

**Other**

* Display available processing time in Auphonic production box
* Episode slugs may contain a wider variety of characters now, such as umlauts.
* Feeds now only contain contributors with an URI. Also, output of contributors in feeds can be filtered by group and/or role.
* New donation option for Auphonic Credits
* Remove scary debug output on failed media file validations. This can be found in the log now.
* Fix Auphonic authentication issue by providing the whole certificate chain
* Fix contributor related feed rendering issue

= 1.10.13 =

We decided to remove the "Force Download" feature. Its purpose was to guarantee that a click on a download button results in a download dialogue, rather than playing the media file in the browser. The way we implemented it worked, but came with many downsides. Just to name two of them:
1) We doubled the traffic and significantly increased load since we had to pull all the bytes through the webserver in addition to the download server (even if both are the same).
2) It was impossible to support HTTP range requests. That means no client was able to resume a broken or paused download. It also seemed to lead to strange behaviour in the web player.

But there is another, superior way to force downloads: configure your download server. The important setting here is [Content-Disposition](http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1).

In *Apache*, you need the `headers` module (`a2enmod headers` on Debian-ish distributions). Then you can add this to your configuration:

    <FilesMatch "\.(mp3|m4a|ogg|oga|ogv|opus|mpg|m4v|webm|flac|pdf|epub|zip)$">
        Header set Content-Disposition attachment
    </FilesMatch>

*lighttpd*:

    $HTTP["url"] =~ "\.(mp3|m4a|ogg|oga|ogv|opus|mpg|m4v|webm|flac|pdf|epub|zip)$" {
        setenv.add-response-header = ("Content-Disposition" => "attachment")
    }

*Nginx*:

    if ($request_filename ~ "\.(mp3|m4a|ogg|oga|ogv|opus|mpg|m4v|webm|flac|pdf|epub)$"){
        add_header Content-Disposition 'attachment';
    }

**Other Changes**

* Trim whitespace around some URLs that appear in the podcast feed.
* Update certificate for auth.podlove.org
* Fix an issue with saving contributors in `Podlove > Podcast Settings > Contributors`

= 1.10.12 =

**Tracking**

* Never redirect media file URLs to trailing-slash-URLs (WordPress has a habit of adding a trailing slash to every URL via redirect. That is undesirable here, since it create two download intents).
* Handle empty user agent strings
* Do not write anything to tracking-database unless tracking is in analytics mode.

**Other**

* Compress export files via gzip.
* Add tracking data to export files.

= 1.10.11 =

**Tracking**

* For increased compatibility (we are looking at you, iTunes), new file URLs without parameters are used when analytics are active.
* Add `&ptm_file=<episode-slug>.<file-extension>` parameter to the end of Parameter-URLs, so tools like wget generate a filename with a correct extension by default.
* Feed URLs now support a `&tracking=no` parameter, which dynamically disables tracking parameters in feed enclosures. This is introduced for debugging purposes and is only mentioned here for the sake of completeness.
* Fix PHP glitch that caused tracking to go into "Tracking URL Parameters" mode even when it was disabled

**ADN Module**

* Fix issue that could lead to repostings
* Fix tags description
* Messages longer than 256 characters will be shortened now and "…" will be appended

**Other**

* Fix: reenable "force download" option

= 1.10.10 =

We discovered incompatibilities between our tracking implementation and some clients. To avoid further trouble, we are *deactivating tracking* until we solve the issue. The option is still available, we just switch it off automatically with this release and it isn't on by default any more.

If you're of the curious type, feel free to activate it and tell us any issues you run into. Thanks!

= 1.10.9 =

* Fix: When tracking was active but no geo-location database available, downloads would fail. This exception is handled correctly now. You can check the status of tracking and the geo-location database in `Expert Settings > Tracking`

= 1.10.8 =

* Feature: Services in templates can be filtered by their type. That way, you can, for example, iterate over all Twitter accounts via `podcast.services({type: "twitter"})`. The previous "type" parameter (for choosing between "social", "donation" and "all") has been renamed to "category". All default templates have been adjusted accordingly _but if you were using this API in a custom template, you need to change it_.
* Feature: `podcast.contributors` in templates are sorted by name now. You can change the order by writing `podcast.contributors({order: "DESC"})`. When using grouping, each group will be sorted separately.
* Feature: `podcast.contributors({scope: "global-active"})` is limited to contributors with at least one contribution in a published episode. To list contributors ignoring this limitation, use `podcast.contributors({scope: "global"})`. "global-active" is the new default.
* Feature: Allow manual posting of ADN announcements
* Feature: Add contributor support to ADN announcements
* Feature: We are beginning to implement download intent tracking and statistics. As a first step, we are now tracking download intents. A following release will contain an analytics section where you can examine the statistics.
* Feature: The feed `<link>` can be configured in `Expert Settings > Website` now. It still defaults to the home page. Other options include the episode archive and any WordPress page.
* Enhancement: remove encryption for "protected feed" password to prevent autofill browser features to destroy contents
* Enhancement: default WordPress search now covers episode subtitle, summary and chapters
* Enhancement: add Vimeo, Gittip and about.me to services
* Enhancement: The expert setting "Display episodes on front page together with blog posts" changed to "Include episode posts on the front page and in the blog feed". So if you set it, episodes will additionally appear in `/feed`. However, only in the form of a post. You will not find enclosures, iTunes metadata etc. in `/feed` items.
* Enhancement: sort chapters imported from Auphonic by time
* Enhancement: Changes to feed list: redirect URL is shown and added screen options to hide columns
* Enhancement: Added Publisher version as an attribute to the export file. If a file is imported with a version different from the current Publisher, a warning is displayed.
* Fix: enable group and role selection in contributor shortcodes
* Fix: failing delayed ADN broadcast
* Fix: stop sending ADN announcements for old episodes
* Fix: refresh of Auphonic presets keeps current preset
* Fix: `contributor.episodes` does not return duplicate episodes any more
* Fix: Jabber URL scheme is now prefixed with `jabber:`
* Fix: Display podcast subtitle in feed description (it was the blog description before)
* Fix: Hide contributors missing a URI from feeds
* Fix: Escaping issue when saving podcast description settings

= 1.10.7 =

* Feature: Direct episode access in templates via `{{ podcast.episodes({slug: 'pod001'}).title }}`
* Feature: Episodes in templates can be filtered and ordered, for example `{{ podcast.episodes({orderby: 'title', 'order': 'ASC'}) }}`. For details, see [`podcast.episodes` documentation](http://docs.podlove.org/ref/template-tags.html#podcast)
* Feature: Direct contributor access in templates via `{{ podcast.contributors({id: 'john'}).name }}`
* Feature: Add shortcode `[podlove-podcast-social-media-list]`, which lists all social media accounts for the podcast
* Feature: Add shortcode `[podlove-podcast-donations-list]`, which lists all donation accounts for the podcast
* Feature: Add tag support for Auphonic
* Enhancement: Add "Save and Continue Editing" buttons to all table based management screens
* Enhancement: Use translations for month and day names in formatted template dates (if a language other than english is used)
* Enhancement: Add refresh buttons for Auphonic preset selector
* Enhancement: Pass more data to web player (as preparation for the next release)
* Enhancement: Improved export format: It has its own namespace and a version now. Publisher version and export date are included as XML comments. XML elements are indented for better readability.
* Remove default content for new templates
* Fix: "Network Activate" works now
* Fix: group and role filters for `[podlove-podcast-contributor-list]` shortcode work as expected now
* Fix: Add services and donations to export format
* Fix: `episode.player` in episode loops, outside the WordPress loop works now
* Fix: Auphonic chapter integration issue
* Fix: Instagram URL scheme

= 1.10.6 =

* Fix: contributor services will be saved correctly
* Enhancement: add a donation column to contributor management table

= 1.10.5 =

**Changes to the Templating System**

`episode.recordingDate` and `episode.publicationDate` are DateTime objects now. Available accessors are: year, month, day, hours, minutes, seconds. For custom formatting, use `episode.recordingDate.format("Y-m-d H:i:s")` for example. Calling `episode.recordingDate` directly is still supported and defaults to the format configured in WordPress.

**Other Changes**

* Enhancement: Add refresh buttons for ADN patter and broadcast channel selectors
* Fix: Avoid "Grey Goo" scenario of self-replicating contributors

= 1.10.4 =

* Hotfix: solve migration issue

= 1.10.3 =

**Changes to the Templating System**

* New filter: `padLeft(padCharacter, padLength)` can be used to append a character to the left of the given string until a certain length is reached. Example: `{{ "4"|padLeft("0",2) }}` returns "04";
* For consistency `{{ contributor.avatar }}` is now an object. To render an HTML image tag, use `{% include '@contributors/avatar.twig' with {'avatar': contributor.avatar} only %}`.
* `{{ episode.duration }}` has been turned into an object to enable custom time renderings. The duration object has the following accessors: hours, minutes, seconds, milliseconds and totalMilliseconds.

__DEPRECATIONS/WARNINGS__

* `{{ episode.duration }}` should not be used any more. The default templates are updated but if you have used it in a custom template, you must replace it. Example: `{{ episode.duration.hours }}:{{ episode.duration.minutes|padLeft("0",2) }}:{{ episode.duration.seconds|padLeft("0",2) }}`
* `{{ episode.license.html }}` and `{{ podcast.license.html }}` are deprecated. Use `{% include '@core/license.twig' %}` for the previous behaviour of choosing the correct license based on context. If you want to be more specific, use `{% include '@core/license.twig' with {'license': episode.license} %}` or `{% include '@core/license.twig' with {'license': podcast.license} %}`.

**Other Changes**

* Feature: ADN Module supports broadcasts
* Enhancement: Contributor shortcode defaults to `donations="yes"` to avoid confusion
* Enhancement: `[podlove-episode-downloads]` now uses templates internally
* Enhancement: Added 500px, Last.fm, OpenStreetMap and Soup to Services
* Enhancement: Use custom contributor social/donation titles as icon titles
* Enhancement: Template form has a "Save Changes and Continue Editing" button now
* Enhancement: feed validation is asynchronous now and has improved performance
* Enhancement: Licenses have a new interface and are compatible with Auphonic now: they can be imported from a finished production and are included when creating a production.
* Enhancement: Default MySQL character set is utf8 now when creating tables
* Enhancement: Add datepicker for episode recording date
* Fix: all default contributors appear in new episodes again
* Fix: change Tumblr URLs from https to http since Tumblr does not support them
* Fix: `[podlove-podcast-contributor-list]` shows the correct contributors now
* Fix: internal template warning when accessing empty contributor roles or groups
* Fix: episode rendering when no files are available
* Fix: flattr script in rss feeds
* Fix: importer issue where sometimes modules would not activate properly

= 1.10.2 =

* Feature: add template filter `formatBytes` to format an integer as kilobytes, megabytes etc. Example: `{{ file.size|formatBytes }}`
* Feature: New accessor `{{ file.id }}`. This is required to generate download forms.
* Fix: `[podlove-episode-contributor-list]` shortcode: Firstly, the "title" attribute works again. Secondly, output by group is optional now and defaults to "not grouped" (as it was before 1.10). If you are using contributor groups and would like grouped output, use `[podlove-episode-contributor-list groupby="group"]`
* Fix: division by zero bug in statistics dashboard
* Fix: parse time in statistics dashboard correctly as normalplaytime
* Fix: add missing template accessor `{{ episode.recordingDate }}`
* Remove separate "publication date" field in episodes. Instead, use the episode post publication date maintained by WordPress. It can be accessed via `{{ episode.publicationDate }}`
* Fix: missing contributor-edit-icon on last entries

= 1.10.1 =

* Fix: podlove-episode-contributor-list shortcode: add support for "group" and "role" attributes
* Fix: podlove-episode-contributor-list shortcode: fix broken flattr button
* Fix: feed widget: only compress if zlib extension is loaded

= 1.10.0 =

**All-new, mighty Templating system**

You can now use the [Twig Template Syntax](http://twig.sensiolabs.org/documentation) in all templates. Access all podcast/episode data via the new template API. Please read the [Template Guide](http://docs.podlove.org/tut/understanding-templates.html) to get started.

If you have used templates before, please note that some shortcodes are now _DEPRECATED_. That means they still work but will be removed at some point. Following is a list of affected shortcodes and their replacements:

Instead of `[podlove-web-player]`, write `{{ episode.player }}`.

Instead of `[podlove-podcast-license]`, write `{{ podcast.license.html }}`.

Instead of `[podlove-episode-license]`, write `{{ episode.license.html }}`.

Instead of `[podlove-episode field="subtitle"]`, write `{{ episode.subtitle }}`. Instead of `[podlove-episode field="summary"]`, write `{{ episode.summary }}` etc. When in doubt, look at the [Episode Template Reference](http://docs.podlove.org/ref/template-tags.html#episode).

Changing the podcast data shortcodes works exactly the same: Instead of `[podlove-podcast field="title"]`, write `{{ podcast.title }}` etc. When in doubt, look at the [Podcast Template Reference](http://docs.podlove.org/ref/template-tags.html#podcast).

**Other Changes**

* Feature: The Podlove dashboard includes a section for feeds if you activate the "Feed Validation" module. It is intended as an overview for the state of your feeds. It shows the latest modification date, the number of entries, compressed and uncompressed size and the latest item. Additionally, you can validate your feeds against the w3c feed validator right from the dashboard.
* Feature" Better Bitlove integration. There is a new setting in `Podlove > Podcast Feeds > Directory Settings` called "Available via Bitlove?". It checks if there is a corresponding Bitlove feed and verifies it on a regular basis.
* Feature: Support for the oEmbed format
* New shortcode: `[podlove-episode-list]` lists all episodes including their episode image, publication date, title, subtitle and duration chronologically. This replaces the archive pages generated by the [Archivist - Custom Archive Templates](https://wordpress.org/plugins/archivist-custom-archive-templates/) plugin, if you are using it right now.
* New shortcode: `[podlove-feed-list]` lists all public feeds
* New shortcode: `[podlove-global-contributor-list]` shows all podcast contributors and lists related episodes.
* New shortcode: `[podlove-podcast-contributor-list]` shows regular podcast contributors
* Enhancement: The feed title may now include the asset title for easier discovery. This setting can be found at `Podlove > Feed Settings`
* Changed shortcode: `[podlove-contributor-list]` is _DEPRECATED_. Please use `[podlove-episode-contributor-list]` instead.
* Enhancement: add "autogrow" feature to chaptermarks text field
* Enhancement: globally hide the migration-tool banner once dismissed rather than per-client via cookie
* Fix: When setting the chapter asset to manual, delete all chapter caches to avoid hiccups
* Fix: Contributor links in the backend use an ID now rather than the contributor slug. That way they work when no slug is set.
* Fix ADN backslash escaping issue in post titles
* Fix: all contributions can be deleted

= 1.9.12 =
* Enhancement: Take over chapters when switching from chapter asset to manual
* Enhancement: Contributor tables look better in a wider range of themes
* Fix: Auphonic module: Buttons cannot be clicked again while the corresponding action is in progress

= 1.9.11 =
* Enhancement: Split podcast settings into tabs.
* Enhancement: Import/Export module supports contributors and contributions
* Enhancement: Separate "default contributors" and "podcast contributors". You can configure default contributors in "Contributor Settings > Defaults" and podcast contributors in "Podcast Settings > Contributors". Display podcast contributors using the shortcode `[podlove-podcast-contributor-list]`.
* Enhancements: Plethora of adjustments in contributor interfaces to avoid confusions and smoothen workflows
* Feature: Contributions may have a public comment (to describe the context of the person), which can be displayed in contributor lists.
* Fix: Skip contributions with missing contributors.

= 1.9.10 =
* Fix: episode images when using manual entry
* Fix: do not include episodes in blog feed
* Fix: paged feed calculation of number of pages when using global Publisher default
* Fix: remove unused IDs from contributor lists

= 1.9.9 =
* Fix: several contributor episode form bugs
* Fix: sum of all media file sizes in dashboard statistics
* Add lost bugfix: Bundle crt file to avoid StartSSL trust issues.

= 1.9.8 =
* Enhancement: WordPress has an option to close commenting for posts after a certain amount of days. This now also applies to podcast episodes.
* Enhancement: Fallback for Contributor Names.
* We had to change the generated Flattr URL for contributors in episodes to a less error prone scheme. Flattr counts for those buttons will therefore reset to 0 (the actual clicks are _not_ lost! they are just not displayed).
* fix sum of all media file sizes in dashboard statistics
* fix license URLs
* fix feed paging issue
* Fix: Feed Item Limit is now displayed correctly
* Fix: Ignore deleted contributors if they were assigned to an Episode or Podcast
* Fix: activation / deactivation of multiple modules at once works as expected now
* add filter "podlove_enable_gzip_for_feeds" to disable gzip feed compression
* Contributor role and group columns will be hidden if no roles or groups were added

= 1.9.7 =
* fix and enhance dashboard statistics
* gender statistics: use episode contributions instead of contributors for counting

= 1.9.6 =
* fix redirect issue after podcast migrations
* fix legacy ADN module publishing issue
* only show `itunes:complete` in feeds if it is set avoid a feedvalidator.org bug
* add experimental episode fun facts in dashboard
* add PayPal Button link in contributor settings
* other contributor admin enhancements
* contributor public name defaults to real name now

= 1.9.5 =
* Contributor Module improvements
  * New icon graphics
  * "Contributor Groups" as a new way to divide contributors by participation. For example, you might want to have a "Team" group and one for supporting contributions.
  * No more default roles. It's just not possible to provide a sensible default set. So just add the ones you need :) (existing roles will *not* be deleted)
  * The contributors defined in `Podcast Settings > Contributors` are now the default contributors for new episodes
  * Reworked contributor management table. Better use of space, hideable columns, avatars and more.
  * Reworked episode contributor table. Avatars, edit links and more.
  * Support for more services
  * ... and a bunch of other tweaks
* Web Player Update: compatible with WordPress theme "Twenty Fourteen"
* Fix: don't gzip feeds when zlib compression is active
* Fix: episode media file checkbox width for WP3.8
* Fix: menu icons for WP3.8

= 1.9.4 =
* Fix: gzip feeds on compatible systems only (avoids failing feed generation)
* Fix: Feed paging (again)

= 1.9.3 =
* Fix: provide global feed limit default on setup
* Fix: managing contributor roles no longer outputs permission issues
* Fix: corrected a faulty "Add New" contributor link
* Fix: paged feeds were broken

= 1.9.2 =
* Fix: _Module: Contributors_ prevent initial migration to import duplicate contributors
* Fix: _Module: Contributors_ Fix faulty default roles

= 1.9.0 / 1.9.1 =

**New Module: Contributors**

Podcasts are not possible without their active communities. Huge contributions are being made behind the scenes and nobody notices except the podcaster. The contributors module shines light on all those diligent people. It's now easy to manage contributors of an episode and list them on the blog. The list contains references to their social profiles and the donation service Flattr. Shortcode to display them in an episode post: [`[podlove-contributor-list]`](http://docs.podlove.org/ref/template-tags.html#contributors).

**Simple Protected Feeds**

You can now protect some or all of your feeds using HTTP authentication. Authenticate via a defined username and password or use the WordPress user database as backend.

**License Selector**

We built an interface to generate a Creative Commons license for your podcast and episodes. You can still use a custom URL and name if you don't want a CC license. Use `[podlove-podcast-license]` and `[podlove-episode-license]` to display them in your episode posts.

**Other Changes**

* Feature: Add "Expert Settings" option to always redirect to media files instead of forcing a browser download. This is interesting for you if you want to minimize traffic on your server hosting the Publisher.
* Feature: add global setting to configure feed item limits
* Feature: Set "itunes:explicit" tag per episode if you want to (you have to activate the feature in the expert settings)
* Enhancement: Feeds are delivered with gzip compression if possible
* Enhancement: Support for temporary redirects in expert settings
* Fix: keep ?redirect=no flag in paged feeds
* Fix: _Module: Import/Export_ Importing episodes no longer causes floods of ADN posts.
* Fix: _Module: Auphonic_ respect Auphonic chapter offset
* _DEPRECATED_: `podlove-contributors` shortcode. Use `podlove-contributor-list` instead

= 1.8.13 =
* Feature: Update Web Player to 2.0.17 (for realsies). It fixes an issue with icon/font display.

= 1.8.12 =
* Feature: Update Web Player to 2.0.17
* Bugfix: Fix PHP 5.3 issue in import module

= 1.8.11 =
* Feature: New module for Import/Export. Now you can easily move all your podcast data to another WordPress instance.
* Feature: Add support for `<itunes:complete>` tag. If there won't be any additional episodes, you can go to `Podlove > Podcast Settings` and activate this setting.
* Bugfix: Bundle crt file to avoid StartSSL trust issues.

= 1.8.10 =
* Hotfix: Removes incompletely updated license feature which wasn't supposed to be in that release in the first place. Sorry!

= 1.8.9 =
* Feature: Update Web Player to 2.0.16
* Enhancement: Render Twitter and OpenGraph tags using a DOM-Generator to avoid all possible escaping issues.
* Enhancement: Allow multiple mime types for web player config slots. Fixes an issue with Firefox and Opus.
* Enhancement: I CAN HAZ SECURETEH?! auth.podlove.org haz https nao.
* Bugfix: Module settings screen rendering issue with PHP 5.3
* Bugfix: Fix link to shortcode documentation

= 1.8.7 / 1.8.8 =
* Enhancement: Refined Auphonic Workflow: Always import duration and slug; new option to automatically start productions after creation; new option to automatically publish episodes as soon as the production is ready
* Hotfix: escaping issue

= 1.8.6 =
* Enhancement: Change feed redirect hook and priority so it works better with Domain Mapping plugin
* Enhancement: Extend OpenGraph metadata by post thumbnail and episode description (thanks smichaelsen!)
* Feature: Update Web Player to 2.0.15
* Fix: Solve rare issue where first chapter line would be ignored
* Fix: Firefox display issue in migration assistant

= 1.8.5 (2013-08-11) =
* Fix: JavaScript issue preventing certain UI elements from working correctly (Tagging, Auphonic, …)

= 1.8.4 (2013-07-27) =
* Fix: Performance issue in Auphonic plugin

= 1.8.3 (2013-07-27) =
* Enhancement: dates with leading zeros in Auphonic module
* Enhancement: Auphonic UI smoothifications
* Enhancement: Update assets after successful production

= 1.8.2 (2013-07-27) =
Auphonic integration Enhancements

* Preset is only applied once
* Add Text for "Open Production" button
* "Start Production" button more prominent

= 1.8.1 (2013-07-27) =
* Fix Release

= 1.8.0 (2013-07-27) =
* Auphonic Module Update. You are now able to manage productions directly from within the Publisher without visiting Auphonic at all. As always, any feedback is more than welcome.
* App.net Module Update. Support for Patter, language annotations and delayed posting.
* Enhancement: Control sequence in which audio elements are printed in the web player. This encourages browsers to use superior codecs (rather than mp3).

= 1.7.3 (2013-07-18) =
* Enhancement: Show expected and actual mime type in log when an error occurs
* Bugfix: Fix Bitlove integration
* Bugfix: Correctly hide content in password protected posts
* Bugfix: ADN Plugin announced new episode every time the episode got saved
* Fix some PHP 5.4 Strict warnings

= 1.7.2 (2013-07-11) =
* Feature: Update Web Player to 2.0.13
* Bugfix: Feed web player with existing/valid files only
* Bugfix: Downloads work without JavaScript enabled
* Bugfix: Episode previews should work now
* Bugfix: Migration Assistant: you are now able to import file slugs containing dots
* Bugfix: Fix podlove_alternate_url issue

= 1.7.1 (2013-07-06) =
* Logging Module: Deactivate sending of mails until we figure out what causes some misbehaviours
* Enhancement: System Report: check for SimpleXML availability
* Bugfix: ADN Announcements should work with all kinds of templates now

= 1.7.0-alpha (2013-07-03) =
* New Module: App.net. Right now, it lets you announce new podcast episodes on ADN whenever you publish a new one. It's the groundwork for more ADN integrations. (Thanks @chemiker!)
* New Module: Auphonic. We did not shy away from writing a completely new module to present to you the best Auphonic integration the world has seen in a WordPress plugin. It replaces the previous one ("Auphonic Production Data"). You are now able to import Auphonic production data without the need for a production description file. Like the ADN module, this lays the groundwork for much deeper Auphonic integration. (Thanks @chemiker!)
* Enhancement: Return the correct content type when initiating a download so devices may choose intelligently whether to save the file or open it in a certain application.
* Enhancement: Remove download button styles so the style adjusts based on used browser and theme
* Bugfix: Fix incompatibility to some file name schemes
* Bugfix: Fix 404 status for paged feedburner feeds

= 1.6.11-alpha =
* Bugfix: use NPT library

= 1.6.10-alpha =
* Fix release issues

= 1.6.7-alpha =
* Enhancement: Move file types settings to expert settings
* Enhancement: Saving a template redirects to template list
* Enhancement: System Report is a readonly textarea
* Enhancement: Group modules
* Enhancement: When creating an asset: if that web player slot is not taken yet, assign it automatically
* Enhancement: Accept time formats with minutes > 59 if no hours are given
* Bugfix: Fix "Chapters Visibility" setting

= 1.6.6-alpha =
* Enhancement: When validating, ignore timeouts (so files don't disappear from feeds just because one request took too long)
* Enhancement: When episode permalinks are invalid, try to autoresolve by switching to "Use Post Permastruct"
* Bugfix: Fix some expert setting migration issues
* Bugfix: Hide invalid media files from downloads

= 1.6.5-alpha =
* Feature: Feeds are sortable
* Feature: You can revalidate single media files in the dashboard
* Enhancement: Use pretty status icons
* Enhancement: Add "sortable handle" for asset and feed lists, so the sortability feature is more discoverable
* Enhancement: Add "Podlove" entry to WordPress toolbar
* Enhancement: Organize "Expert Settings" into tabs
* Enhancement: Don't log "File not Modified"
* Bugfix: Activate feature "Activate asset for all existing episodes" for pending episodes
* Bugfix: Solve issue with chapter asset cache invalidation
* Bugfix: Solve chapter encoding issue when chapters start with umlauts
* Bugfix: Fix video display in some themes
* Other small UI changes in various places

= 1.6.4-alpha =
* Bugfix: use manual chapter entries if available
* Bugfix: PSC assets work properly
* Bugfix: URL magic doesn't interfere with other post types
* Bugfix: deactivate preload in web player

= 1.6.3-alpha =
* Bugfix: "Display episodes on front page together with blog posts" works again
* Bugfix: chapters at 0 seconds are not ignored any more
* Bugfix: correctly show feed title in deletion confirmation
* Bugfix: handle missing/invalid PSC file with appropriate grace
* Bugfix: remove player from feed
* Bugfix: fix false negatives in error log; reenable logging-mails
* Bugfix: fix timezone in logs

= 1.6.2-alpha =
* Bugfix: fix template autoinsert migration issue

= 1.6.1-alpha =
* Bugfix: fix call-time pass-by-reference
* Bugfix: deactivate logging-mails until we find out what's wrong

= 1.6.0-alpha =
* Feature: New modules "Asset Validation" and "Logging". Automatically verify assets once in a while (fresh posts will be validated more often than old posts). Detailed logging in Podlove dashboard. Receive an email when all episode assets are unavailable.
* Feature: always print PSC in feed if any chapter format is available (psc, mp4chaps, json)
* Feature: upgrade web player to v2.0.10
* Enhancement: template autoinsert settings are on templates page now
* Enhancement: correctly fall back to podcast image when episode image is activated but missing
* Enhancement: various UI fixes (thanks @MaZderMind)
* Enhancement: improve feed deletion dialogue
* Enhancement: default title for episode assets is file format title
* Bugfix: solve permalink issue after migrations
* Bugfix: migrate comment hierarchy correctly

= 1.5.4-alpha =
* Feature: PubSubHubbub support via new module
* Enhancement: Check for iconv availability in system report
* Turn permalink compatibility up to eleven

= 1.5.3-alpha =
* Bugfix: more robust permalink fix

= 1.5.2-alpha =
* Bugfix: Fix using the same permalink structure / 404 on pages

= 1.5.1-alpha =
* Enhancement: episodes may share the same permalink structure with WordPress posts
* Enhancement: episode archive url can be configured
* Enhancement: run system report more intelligently
* Enhancement: Auphonic module works more smoothly for new episodes
* Enhancement: Fallback to 302 redirects for HTTP/1.0 clients
* Enhancement: Confirm before deleting feeds and templates
* Enhancement: Parse time strictly following the NPT specification: http://www.w3.org/TR/media-frags/#npttimedef
* Bugfix: don't use feed redirect when a feed archive page is specified

= 1.4.8-alpha =

Minor fixes and improvements:

* feed: remove style tags from content:encoded (feedvalidator.org warning)
* feed: ensure description precedes content:encoded (feedvalidator.org warning)
* prevent feed proxy issue
* `HEAD` requests for paged feeds return correct responses
* enable paging for `/podcast` archives
* add description to redirect settings
* rename "record date" to "recording date"

= 1.4.7-alpha =
* Hotfix: ignore empty redirect rules

= 1.4.6-alpha =
* Bugfix: The podcast archive is available via `/podcast` again.

= 1.4.5-alpha =
* Enhancement: always show critical errors found by system report
* Enhancement: flush rewrite rules after migration and feed changes
* Enhancement: redirect settings support URL parameters

= 1.4.4-alpha =
* Feature: configure permanent redirects in Expert Settings
* Bugfix: fix feed url generation for "default style" permalinks
* Bugfix: migration assistant shows enclosure errors/warnings
* Bugfix: add missing atom prefix in feed link elements
* Bugfix: generate valid episode permalinks for "Default"/"Not Pretty" permalink settings
* Bugfix: change default episode permalink structure from `%podcast%` to `podcast/%podcast%` to avoid conflicts with those setups using %postname% as WordPress permalink — which is quite common.

= 1.4.3-alpha =
* Bugfix: fix system report issue
* Bugfix: fix feed setting "No limit. Include all items."

= 1.4.2-alpha =
* Bugfix: add Auphonic metadata file type
* Bugfix: fix bug regarding limiting feed items

= 1.4.1-alpha =
* Bugfix: reactivate /podcast url

= 1.4.0-alpha =
* Feature: "Soft Launch" for migration tool. It isn't activated by default but if you are adventurous, feel free to give it a try. Any feedback is greatly appreciated!
* Feature: Support paged feeds (RFC5005) so clients may always fetch all episodes even if the default feed only contains the most recent episodes
* Feature/Change: Similar to the web player setting, you now can insert templates automatically at the beginning or end of a post. You could even create multiple templates, one to append and one to prepend. This replaces the previous template-autoinsert feature.
* Feature: New module "Auphonic Production Data". Thanks @tobybaier!
* Enhancement: Update Web Player to v2.0.7
* Enhancement: open graph title is podcast title

= 1.3.30-alpha =
* Feature: Option to autoinsert web player at beginning or end of post
* Feature: Add "Support" page including a system report
* Enhancement: Add .post class to article-classes list to improve theme compatibility
* Bugfix: Fix feed validation mixup
* Bugfix: Support "future publishing" of episodes (thanks Marc!)

= 1.3.29-alpha =
* Bugfix: Fix some media file mixups

= 1.3.28-alpha =
* Feature: Two new episode fields `publication_date` and `record_date`. Accessible via episode shortcode. Must be enabled in expert settings.
* Feature: Assets can be sorted via drag'n'drop. Influences download button/list order.
* Bugfix: fix "No More Enclosures" feature. I was using a deprecated hook
* Enhancement: upgrade Podlove Web Player to 2.0.5
* Enhancement: move episode asset url to expert settings
* Change: Drop support for Atom feeds
* Change: Remove support for mnemonic and Episode Assistant module

In the beginning, everything evolved around the episode numbers and the
mnemonic. Then, it made sense to support this concept by something like the
episode assistant.

Now, the mnemonic is merely an afterthought. It's used by no part of
the system except the episode assistant. And this doesn't do a lot that
can't be done without it either. So we decided to drop both for now.

A similar concept might return once we tackle stuff like seasons.

= 1.3.27-alpha =
* Enhancement: enforce trailing slash at the end media file base url
* Enhancement: fix huge download-select-font
* Enhancement: doublecheck curl availability
* Bugfix: double quote escaping for Web Player title, subtitle and summary

= 1.3.26-alpha =
* Enhancement: upgrade Podlove Web Player to 2.0.4

= 1.3.25-alpha =
* Feature: Setting for Web Player to show or hide chapters by default
* Enhancement: Open Graph now correctly excludes non-audio assets
* Enhancement: "File not found" errors now result in some debug output which may help tracing the issue
* Enhancement: upgrade Podlove Web Player
* Bugfix: Generated Template shortcodes now use the "id" attribute rather than "title"

= 1.3.24-alpha =
* Enhancement: remove mediaelementjs demo files

= 1.3.23-alpha =
* Enhancement: upgrade Podlove Web Player
* Enhancement: improve handling of url_fopen setting
* Enhancement: feed item limit is now a select box. default is now "all" instead of "WordPress Default"

= 1.3.22-alpha =
* Hotfix: solve White Screen of Death issue for PHP 5.4

= 1.3.21-alpha =
* Bugfix: allow deletion of unused assets
* Enhancement: if an asset shouldn't be deleted, display where it's in use (allow deletion anyway)
* Enhancement: Downloads redirect to file if `allow_url_fopen` is disabled.

= 1.3.20-alpha =
* Enhancement: always add a trailing slash to media file base url
* Bugfix: trying to fix escaping part whatnotsoever

= 1.3.19-alpha =
* Hotfix: slugs are not forced into lowercase any more

= 1.3.18-alpha =
* Feature: Module for Bitlove.org support! Adds links to torrent-files to the downloads-section of your episodes.
* Feature: add video support for web player
* Enhancement: fix a (possibly rare) memory bug when downloading files
* Enhancement: enable episodes on home page by default
* Enhancement: change default download widget style to the select-thingy
* Bugfix: fix feed warning

= 1.3.17-alpha =
* Bugfix: fix issue with 3rd party custom post types
* Enhancement: improve Feed Settings screen

= 1.3.16-alpha =
* Feature: new style for file downloads `[podlove-episode-downloads style="select"]`
* Enhancement: Solve feed url issues:
** ensure validity on save
** support non-pretty url format
* Enhancement: un-default some modules: episode assistant, twitter card summary
* Enhancement: fix asset & feed setting redirect issue
* Enhancement: add caption file types
* Enhancement: new icons!
* Enhancement: allow underscores and dots in slugs
* Bugfix: fix issue with multiple backslash-escapings

= 1.3.15-alpha =
* Hotfix: fix 404 issue concerning episode prefixes and posts

= 1.3.14-alpha =
* Feature: ajaxy asset revalidation in dashboard
* Feature: duration support for web player
* Feature: add option to provide web players with opus format
* Enhancement: slightly improved web player settings pane
* Enhancement: deprecate [podlove-template title=""] in favor of [podlove-template id=""] for clarity
* Enhancement: move category support for episodes into a module
* Enhancement: force feed & episode slugs into url conformity
* update plugin description and add a FAQ section

= 1.3.13-alpha =
* Bugfix: Podcast model works with `switch_to_blog` now

= 1.3.12-alpha =
* Enhancement: don't embed cover image fallback in feed as episode image when there is no episode image
* Feature: add action link for assets to enable it for all existing episodes. useful when adding a new asset for an existing podcast

= 1.3.11-alpha =
* Enhancement: Image input fields try to show pasted image immediately
* Enhancement: remove unused "post episode to show" setting
* Bugfix: fix asset preview glitch when changing the episode slug
* Bugfix: fix GUID upgrade migration

= 1.3.10-alpha =
* Hotfix: too much escaping when `get_magic_quotes` is on

= 1.3.9-alpha =
* Enhancement: rectify feed generator title
* Bugfix: add missing sql escaping

= 1.3.8-alpha =
* Bugfix: fix episode image fallback to podcast image

= 1.3.7-alpha =
* Enhancement: In feed settings, URL preview updates live now
* Enhancement: "Add New" button in blank list table views
* Enhancement: display `<language>` tag in RSS channel and correct xml:lang in ATOM
* Enhancement: forbid asset deletion when used in feed or web player
* Bugfix: Templates list view highlights template preview correctly now for more than one entry
* Bugfix: remove duplicate rel="self" entry from RSS feeds
* Bugfix: correct escaping for all input fields
* Bugfix: fix 404s when using an empty episode url prefix

= 1.3.6-alpha =
* Bugfix: Minor WordPress 3.5 compatibility issue
* Bugfix: Use correct shortcodes in default template
* Enhancement: Add support for `[podlove-episode field="title"]`
* Enhancement: Improve auto-updating of media files. It will now work correctly without the need to save the post after changing the media file slug. It updates every time you change the slug and lose focus of the input field.

= 1.3.5-alpha =
* Bugfix: pages and menu items don't appear unexpectedly in main loop any more
* Bugfix: when using the WordPress importer, don't create new GUIDs
* Enhancement: rename GUID meta so it doesn't appear as custom field

= 1.3.4-alpha =
* Hotfix: fix asset creation issue

= 1.3.3-alpha =
* Enhancement: Use episode image fallback to podcast image in webplayer.

= 1.3.2-alpha =
* Feature: When using manual mp4chaps style chapter marks, the Publisher generates "Podlove Simple Chapters" for the feed automatically. Includes link support using chevrons (example: `00:00:00 Intro <http://podlove.org>`).

= 1.3.1-alpha =
* update web player to 1.2.1

= 1.3.0-alpha =
* Feature: [Podlove Deep Linking](http://podlove.org/deep-link/) support
* Feature: support for new web player
* Bugfix: enable tag and category search results for all post types
* Bugfix: Feed item limit setting works now
* Bugfix: avoid rare curl warning
* Bugfix: improve feed validity
* Enhancement: remove unused feed setting `show description`
* Enhancement: Podlove feeds don't override /feed/* WordPress feeds any more
* Enhancement: Rename plugin to "Podlove Podcast Publisher"
* Enhancement: Move asset assignments from podcast settings to asset settings

= 1.2.24-alpha =
* Bugfix: don't show milliseconds in feed so feedvalidator.org stops complaining

= 1.2.22/23-alpha =
* Fix deployment bug, delete unused files from SVN

= 1.2.21-alpha =
* Bugfix: check for asset relations (not just media file relations) when trying to delete assets
* Bugfix: asset form can handle file types using brackets now
* Bugfix: There was an undocumented way to just show episodes on the front page. However, this made using static pages as front page unusable. So for now, this functionality has been deactivated. The expert option to display both episodes and articles on the front page is not affected and will continue to work.
* Enhancement: duration is now normalized and can be printed full (HH:MM:SS.mmm) or HH:MM:SS using `[podlove-episode field="duration" format="full/HH:MM:SS"]`
* Enhancement: curl requests set user agent

= 1.2.20-alpha =
* Bugfix: forbid deletion of episode assets referenced by existing media files
* Bugfix: fix episode asset type selector

= 1.2.19-alpha =
* Feature: add episode image shortcode `[podlove-episode field="image"]`
* Bugfix: fix some bugs
* Enhancement: when creating new form entries, the user is now redirected to the index page rather than the edit form

= 1.2.18-alpha =
* Feature: 4 new podcast fields: publisher_name, publisher_url, license_name, license_url
* Feature: Shortcode `[podlove-podcast]` to access podcast data. See [Shortcode Documentation](https://github.com/eteubert/podlove/wiki/Shortcodes) for more details.
* Feature: Shortcode `[podlove-episode]` to access episode data. *all previous episode accessors are deprecated!* See [Shortcode Documentation](https://github.com/eteubert/podlove/wiki/Shortcodes) for more details.
* Feature: Add support for tags and categories in episodes.
* Feature: Chapter File (txt and psc) as episode asset
* Feature: Feed redirects can be a) turned off and b) permanent c) temporary
* Feature: Module for Twitter Card support
* Enhancement: Minor template editor enhancements and updated default template.
* Enhancement: Enable revisions for episodes.
* Enhancement: RSS/Atom cleanup. Less WordPress, more Podlove.
* Enhancement: UI improvements in episode asset forms
* Enhancement: Menu reorganisation. Moved important stuff up, expert stuff down. Separate site for modules.

= 1.2.17-alpha =
* Nothing. Just some WordPress-Plugin-Directory-Thingamajig-Version-Foobar.

= 1.2.16-alpha =
* Feature: Episode templates. Go to `Podlove > Templates` to find out more. See [Shortcode Documentation](https://github.com/eteubert/podlove/wiki/Shortcodes) for more details.
* Feature: Custom GUID for episodes. A GUID in the form of "podlove-`time`-`hash`" is generated for each new episode. It removes the ambiguity of the permalink-ish looking WordPress GUID. Bonus: If you need podcatchers to redownload all media files (maybe you detected a glitch in your files and fixed it), you are now able to change the GUID to achieve that.
* Enhancement: remove episode excerpt support in favor of episode summary
* Bugfix: Short Episode Routing compatibility

= 1.2.15-alpha =
* Bugfix: remove all Show model references for now
* Enhancement: proper summary/description feed elements

= 1.2.14-alpha =
Download .txt
gitextract_4vqhqypb/

├── .distignore
├── .dockerignore
├── .editorconfig
├── .github/
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE.md
│   └── workflows/
│       ├── docker-image.yml
│       ├── release-beta.yml
│       ├── release-wordpress.yml
│       └── tests.yml
├── .gitignore
├── .gitmodules
├── .php-cs-fixer.dist.php
├── .prettierrc
├── .wp-env.json
├── .wp-env.test.json
├── .zed/
│   └── settings.json
├── AGENTS.md
├── Dockerfile
├── Makefile
├── README.md
├── bin/
│   ├── code-coverage.sh
│   ├── docker-entry.sh
│   ├── docker-setup.sh
│   ├── release.sh
│   ├── remove-tunnel.sh
│   ├── reset-nux.sh
│   ├── template_ref.erb
│   ├── template_ref.rb
│   ├── template_ref_json.php
│   ├── uadetect.php
│   ├── update-opawg.sh
│   ├── update_pwp4.sh
│   ├── workspace.js
│   └── wp-env-test-after-start.js
├── bootstrap/
│   ├── autoload.php
│   ├── bootstrap.php
│   └── constants.php
├── changelog.txt
├── client/
│   ├── .tool-versions
│   ├── config.local.template.js
│   ├── index.html
│   ├── package.json
│   ├── postcss.config.js
│   ├── src/
│   │   ├── assets/
│   │   │   └── index.d.ts
│   │   ├── client.ts
│   │   ├── components/
│   │   │   ├── button/
│   │   │   │   └── Button.vue
│   │   │   ├── combobox/
│   │   │   │   └── Combobox.vue
│   │   │   ├── icons/
│   │   │   │   └── Avatar.vue
│   │   │   ├── modal/
│   │   │   │   └── Modal.vue
│   │   │   ├── module/
│   │   │   │   └── Module.vue
│   │   │   ├── popover/
│   │   │   │   └── Popover.vue
│   │   │   ├── steps/
│   │   │   │   └── Steps.vue
│   │   │   ├── tabs/
│   │   │   │   ├── Tab.vue
│   │   │   │   ├── TabsContainer.vue
│   │   │   │   └── index.ts
│   │   │   ├── tag/
│   │   │   │   └── Tag.vue
│   │   │   └── tooltip/
│   │   │       └── Tooltip.vue
│   │   ├── lib/
│   │   │   ├── api.ts
│   │   │   ├── array.ts
│   │   │   ├── auphonic.api.ts
│   │   │   ├── chapters.ts
│   │   │   ├── errorHandling.ts
│   │   │   ├── license.ts
│   │   │   ├── normalplaytime.ts
│   │   │   ├── popper.ts
│   │   │   ├── statusHelpers.ts
│   │   │   ├── timestamp.ts
│   │   │   └── wordpress.ts
│   │   ├── modules/
│   │   │   ├── auphonic/
│   │   │   │   ├── Auphonic.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── FileChooser.vue
│   │   │   │   │   ├── Logo.vue
│   │   │   │   │   ├── ManageProductionForm.vue
│   │   │   │   │   ├── SelectPreset.vue
│   │   │   │   │   ├── SelectProduction.vue
│   │   │   │   │   ├── StartScreen.vue
│   │   │   │   │   ├── WebhookToggle.vue
│   │   │   │   │   └── production_form/
│   │   │   │   │       ├── DonePage.vue
│   │   │   │   │       ├── PlusTransferStatus.vue
│   │   │   │   │       ├── TransferFileItem.vue
│   │   │   │   │       ├── TransferFileList.vue
│   │   │   │   │       ├── TransferHeader.vue
│   │   │   │   │       └── TransferStatusPanel.vue
│   │   │   │   └── index.ts
│   │   │   ├── chapters/
│   │   │   │   ├── Chapters.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── Export.vue
│   │   │   │   │   ├── Form.vue
│   │   │   │   │   └── Import.vue
│   │   │   │   └── index.ts
│   │   │   ├── contributors/
│   │   │   │   ├── Contributors.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── AddContribution.vue
│   │   │   │   │   └── Contribution.vue
│   │   │   │   └── index.ts
│   │   │   ├── description/
│   │   │   │   ├── Description.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── EpisodeContent.vue
│   │   │   │   │   ├── EpisodeNumber.vue
│   │   │   │   │   ├── EpisodePoster.vue
│   │   │   │   │   ├── EpisodeSubtitle.vue
│   │   │   │   │   ├── EpisodeSummary.vue
│   │   │   │   │   ├── EpisodeTitle.vue
│   │   │   │   │   └── EpisodeType.vue
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── license/
│   │   │   │   ├── License.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── LicenseName.vue
│   │   │   │   │   ├── LicenseSelector.vue
│   │   │   │   │   ├── LicenseSelectorButton.vue
│   │   │   │   │   ├── LicenseUrl.vue
│   │   │   │   │   └── LicenseView.vue
│   │   │   │   └── index.ts
│   │   │   ├── mediafiles/
│   │   │   │   ├── MediaFiles.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── AssetsEmptyState.vue
│   │   │   │   │   ├── AssetsTable.vue
│   │   │   │   │   ├── MediaSlug.vue
│   │   │   │   │   ├── MediaUpload.vue
│   │   │   │   │   └── PlusMediaUpload.vue
│   │   │   │   └── index.ts
│   │   │   ├── plus_features/
│   │   │   │   ├── Feature.vue
│   │   │   │   ├── PlusFeatures.vue
│   │   │   │   └── index.ts
│   │   │   ├── plus_file_migration/
│   │   │   │   ├── PlusFileMigration.vue
│   │   │   │   └── index.ts
│   │   │   ├── plus_token/
│   │   │   │   ├── PlusToken.vue
│   │   │   │   ├── TokenInput.vue
│   │   │   │   └── index.ts
│   │   │   ├── related/
│   │   │   │   ├── RelatedEpisodes.vue
│   │   │   │   └── index.ts
│   │   │   ├── shows/
│   │   │   │   ├── ShowSelect.vue
│   │   │   │   └── index.ts
│   │   │   ├── soundbite/
│   │   │   │   ├── Soundbite.vue
│   │   │   │   ├── components/
│   │   │   │   │   ├── Clear.vue
│   │   │   │   │   └── Form.vue
│   │   │   │   └── index.ts
│   │   │   └── transcripts/
│   │   │       ├── Transcripts.vue
│   │   │       ├── components/
│   │   │       │   ├── Delete.vue
│   │   │       │   ├── Export.vue
│   │   │       │   ├── Import.vue
│   │   │       │   ├── List.vue
│   │   │       │   └── Voices.vue
│   │   │       └── index.ts
│   │   ├── plugins/
│   │   │   └── translations.ts
│   │   ├── sagas/
│   │   │   ├── admin.sagas.ts
│   │   │   ├── api.ts
│   │   │   ├── auphonic.api.ts
│   │   │   ├── auphonic.sagas.ts
│   │   │   ├── chapters.sagas.ts
│   │   │   ├── contributors.sagas.ts
│   │   │   ├── episode.sagas.ts
│   │   │   ├── helper.ts
│   │   │   ├── lifecycle.sagas.ts
│   │   │   ├── mediafiles.duration.sagas.ts
│   │   │   ├── mediafiles.enable.sagas.ts
│   │   │   ├── mediafiles.fileselection.sagas.ts
│   │   │   ├── mediafiles.sagas.ts
│   │   │   ├── mediafiles.slug.sagas.ts
│   │   │   ├── mediafiles.upload.sagas.ts
│   │   │   ├── mediafiles.verification.sagas.ts
│   │   │   ├── notification.saga.ts
│   │   │   ├── plus.sagas.ts
│   │   │   ├── plusFileMigration.sagas.ts
│   │   │   ├── podcast.sagas.ts
│   │   │   ├── relatedEpisodes.sagas.ts
│   │   │   ├── shows.sagas.ts
│   │   │   ├── transcripts.sagas.ts
│   │   │   └── wordpress.sagas.ts
│   │   ├── store/
│   │   │   ├── admin.store.ts
│   │   │   ├── auphonic.store.ts
│   │   │   ├── chapters.store.ts
│   │   │   ├── contributors.store.ts
│   │   │   ├── episode.store.ts
│   │   │   ├── index.ts
│   │   │   ├── lifecycle.store.ts
│   │   │   ├── mediafiles.store.ts
│   │   │   ├── notification.store.ts
│   │   │   ├── plus.store.ts
│   │   │   ├── plusFileMigration.store.ts
│   │   │   ├── podcast.store.ts
│   │   │   ├── post.store.ts
│   │   │   ├── progress.store.ts
│   │   │   ├── reducers.ts
│   │   │   ├── relatedEpisodes.store.ts
│   │   │   ├── runtime.store.ts
│   │   │   ├── selectors.ts
│   │   │   ├── settings.store.ts
│   │   │   ├── shows.store.ts
│   │   │   ├── transcripts.store.ts
│   │   │   ├── vue.ts
│   │   │   └── wordpress.store.ts
│   │   ├── style.css
│   │   ├── types/
│   │   │   ├── chapters.types.ts
│   │   │   ├── contributors.types.ts
│   │   │   ├── episode.types.ts
│   │   │   ├── license.types.ts
│   │   │   ├── relatedEpisodes.types.ts
│   │   │   ├── shows.types.ts
│   │   │   └── transcripts.types.ts
│   │   └── vue-shims.d.ts
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   ├── typings/
│   │   ├── podlove.d.ts
│   │   └── redux-actions.d.ts
│   └── vite.config.js
├── composer.json
├── config/
│   └── php-scoper/
│       ├── matomo.inc.php
│       ├── monolog.inc.php
│       ├── piwik.inc.php
│       ├── psr.inc.php
│       └── twig.inc.php
├── css/
│   ├── about.css
│   ├── admin-font.css
│   ├── admin.css
│   ├── dc.css
│   └── frontend.css
├── data/
│   ├── .gitkeep
│   ├── opawg.json
│   └── podlove_v2_schema.json
├── devbox.d/
│   └── php/
│       ├── php-fpm.conf
│       └── php.ini
├── devbox.json
├── docker-compose.yml
├── includes/
│   ├── about.php
│   ├── api/
│   │   ├── admin/
│   │   │   ├── onboarding.php
│   │   │   └── plus.php
│   │   ├── analytics.php
│   │   ├── api.php
│   │   ├── chapters.php
│   │   ├── episodes/
│   │   │   ├── contributions.php
│   │   │   └── related_episodes.php
│   │   ├── episodes.php
│   │   ├── feeds.php
│   │   ├── podcast.php
│   │   ├── show.php
│   │   └── tools.php
│   ├── auto_post_titles.php
│   ├── cache.php
│   ├── capabilities.php
│   ├── chapters.php
│   ├── compatibility.php
│   ├── db_migration.php
│   ├── deprecations.php
│   ├── detect_duplicate_slugs.php
│   ├── donation_banner.html.php
│   ├── donation_banner.img.src
│   ├── donation_banner.php
│   ├── downloads.php
│   ├── episode_number_column.php
│   ├── episode_number_quick_edit_form.php
│   ├── explicit_content.php
│   ├── extras.php
│   ├── feed_discovery.php
│   ├── frontend_styles.php
│   ├── http.php
│   ├── images.php
│   ├── import.php
│   ├── jetpack.php
│   ├── license.php
│   ├── merge_episodes.php
│   ├── modules.php
│   ├── no_enclosure_autodiscovery.php
│   ├── permalinks.php
│   ├── podlove-web-player-5.php
│   ├── podlove_data_js_adapter.php
│   ├── recording_date.php
│   ├── redirects.php
│   ├── request_id_rehash.php
│   ├── require_curl.php
│   ├── screen_options.php
│   ├── scripts_and_styles.php
│   ├── search.php
│   ├── setup.php
│   ├── setup_wizard.php
│   ├── system_report.php
│   ├── template_pages.php
│   ├── templates.php
│   ├── theme_helper.php
│   ├── trash.php
│   ├── verify_itunes_category.php
│   ├── webhooks.php
│   └── wp_rocket.php
├── js/
│   ├── .tool-versions
│   ├── admin/
│   │   ├── ace/
│   │   │   ├── ace.js
│   │   │   ├── mode-twig.js
│   │   │   ├── theme-chrome.js
│   │   │   └── theme-github.js
│   │   ├── chosen/
│   │   │   ├── chosenImage.css
│   │   │   └── chosenImage.jquery.js
│   │   ├── cornify.js
│   │   ├── dc.js
│   │   ├── jquery-ui/
│   │   │   └── css/
│   │   │       └── smoothness/
│   │   │           └── jquery-ui.css
│   │   ├── spectrum/
│   │   │   ├── spectrum.css
│   │   │   └── spectrum.js
│   │   ├── template.js
│   │   └── tools/
│   │       └── useragent.js
│   ├── package.json
│   ├── src/
│   │   ├── admin/
│   │   │   ├── dashboard_asset_validation.js
│   │   │   ├── dashboard_feed_validation.js
│   │   │   ├── episode.js
│   │   │   ├── episode_asset_settings.js
│   │   │   ├── feed_settings.js
│   │   │   ├── jobs.js
│   │   │   ├── jquery.count_characters.js
│   │   │   ├── license.js
│   │   │   ├── md5.js
│   │   │   ├── media.js
│   │   │   ├── podlove_data_table.js
│   │   │   ├── post_title_autogenerate.js
│   │   │   ├── protected_feed.js
│   │   │   └── timeago.jquery.js
│   │   ├── admin.js
│   │   ├── analytics/
│   │   │   ├── common.js
│   │   │   ├── episode.js
│   │   │   └── totals.js
│   │   ├── app.js
│   │   ├── components/
│   │   │   ├── AnalyticsDatePicker.vue
│   │   │   ├── JobsDashboard.vue
│   │   │   ├── Shownotes.vue
│   │   │   ├── ShownotesEntry.vue
│   │   │   ├── Slacknotes.vue
│   │   │   ├── icons/
│   │   │   │   ├── CheveronDown.vue
│   │   │   │   ├── CheveronUp.vue
│   │   │   │   ├── Close.vue
│   │   │   │   ├── DotsVertical.vue
│   │   │   │   ├── Edit.vue
│   │   │   │   ├── Eye.vue
│   │   │   │   ├── EyeOff.vue
│   │   │   │   ├── Image.vue
│   │   │   │   ├── Link.vue
│   │   │   │   ├── Menu.vue
│   │   │   │   ├── Refresh.vue
│   │   │   │   └── Type.vue
│   │   │   ├── shownotes/
│   │   │   │   ├── link-compact.vue
│   │   │   │   ├── link-unfurling.vue
│   │   │   │   ├── link.vue
│   │   │   │   ├── sn-button.vue
│   │   │   │   ├── sn-card.vue
│   │   │   │   ├── suggestion.vue
│   │   │   │   └── topic.vue
│   │   │   └── temp.xml
│   │   └── lib/
│   │       ├── duration_errors.js
│   │       ├── guid.js
│   │       └── timestamp.js
│   └── webpack.mix.js
├── lib/
│   ├── ajax/
│   │   ├── ajax.analytics_global_total_downloads_by_show.html.php
│   │   ├── ajax.php
│   │   ├── file_controller.php
│   │   └── template_controller.php
│   ├── analytics/
│   │   ├── download_intent_cleanup.php
│   │   ├── download_sums_calculator.php
│   │   ├── episode_download_average.php
│   │   └── salt_shaker.php
│   ├── api/
│   │   ├── error.php
│   │   ├── permissions.php
│   │   ├── response.php
│   │   └── validation.php
│   ├── authentication.php
│   ├── cache/
│   │   ├── http_header_validator.php
│   │   └── template_cache.php
│   ├── chapters_manager.php
│   ├── comment/
│   │   └── comment.php
│   ├── cron.php
│   ├── custom_guid.php
│   ├── delete_head_requests.php
│   ├── dom_document_fragment.php
│   ├── downloads.php
│   ├── downloads_list_data.php
│   ├── downloads_list_table.php
│   ├── duplicate_post.php
│   ├── duration.php
│   ├── episode_asset_list_table.php
│   ├── feed_list_table.php
│   ├── feeds/
│   │   ├── base.php
│   │   ├── chapters.php
│   │   └── rss.php
│   ├── feeds.php
│   ├── file_type_list_table.php
│   ├── form/
│   │   └── input/
│   │       ├── builder.php
│   │       ├── div_wrapper.php
│   │       ├── table_wrapper.php
│   │       └── wrapper.php
│   ├── geo_ip.php
│   ├── has_page_documentation_trait.php
│   ├── helper.php
│   ├── http/
│   │   └── curl.php
│   ├── jobs/
│   │   ├── counting_job.php
│   │   ├── cron_job_runner.php
│   │   ├── download_intent_cleanup_job.php
│   │   ├── download_timed_aggregator_job.php
│   │   ├── job_cleaner.php
│   │   ├── job_trait.php
│   │   ├── request_id_rehash_job.php
│   │   ├── tools_section.php
│   │   ├── tools_section_cron_diagnostics.php
│   │   └── user_agent_refresh_job.php
│   ├── list_table.php
│   ├── log.php
│   ├── model/
│   │   ├── asset_assignment.php
│   │   ├── base.php
│   │   ├── download_intent.php
│   │   ├── download_intent_clean.php
│   │   ├── episode.php
│   │   ├── episode_asset.php
│   │   ├── feed.php
│   │   ├── file_type.php
│   │   ├── geo_area.php
│   │   ├── geo_area_name.php
│   │   ├── image.php
│   │   ├── job.php
│   │   ├── keeps_blog_reference_trait.php
│   │   ├── licensable.php
│   │   ├── license.php
│   │   ├── media_file.php
│   │   ├── network_trait.php
│   │   ├── podcast.php
│   │   ├── template.php
│   │   ├── template_assignment.php
│   │   └── user_agent.php
│   ├── modules/
│   │   ├── affiliate/
│   │   │   ├── affiliate.php
│   │   │   └── podcast_affiliate_settings_tab.php
│   │   ├── analytics_heartbeat/
│   │   │   ├── analytics_heartbeat.php
│   │   │   └── model/
│   │   │       └── heartbeat.php
│   │   ├── asset_validation/
│   │   │   └── asset_validation.php
│   │   ├── auphonic/
│   │   │   ├── api_wrapper.php
│   │   │   ├── auphonic.php
│   │   │   ├── episode_enhancer.php
│   │   │   ├── plus_file_transfer.php
│   │   │   └── rest_api.php
│   │   ├── automatic_numbering/
│   │   │   └── automatic_numbering.php
│   │   ├── base.php
│   │   ├── categories/
│   │   │   └── categories.php
│   │   ├── contributors/
│   │   │   ├── contributor_group_list_table.php
│   │   │   ├── contributor_list_table.php
│   │   │   ├── contributor_repair.php
│   │   │   ├── contributor_role_list_table.php
│   │   │   ├── contributors.php
│   │   │   ├── gender_stats.php
│   │   │   ├── jobs/
│   │   │   │   ├── podcast_import_contributor_episode_contributions_job.php
│   │   │   │   ├── podcast_import_contributor_groups_job.php
│   │   │   │   ├── podcast_import_contributor_roles_job.php
│   │   │   │   ├── podcast_import_contributor_show_contributions_job.php
│   │   │   │   └── podcast_import_contributors_job.php
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── model/
│   │   │   │   ├── contribution_gender_statistics.php
│   │   │   │   ├── contributor.php
│   │   │   │   ├── contributor_group.php
│   │   │   │   ├── contributor_role.php
│   │   │   │   ├── default_contribution.php
│   │   │   │   ├── episode_contribution.php
│   │   │   │   └── show_contribution.php
│   │   │   ├── rest_api.php
│   │   │   ├── settings/
│   │   │   │   ├── contributor_defaults.php
│   │   │   │   ├── contributor_settings.php
│   │   │   │   ├── generic_entity_settings.php
│   │   │   │   ├── podcast_contributors_settings_tab.php
│   │   │   │   └── tab/
│   │   │   │       ├── contributors.php
│   │   │   │       ├── defaults.php
│   │   │   │       ├── groups.php
│   │   │   │       └── roles.php
│   │   │   ├── shortcodes.php
│   │   │   ├── template/
│   │   │   │   ├── avatar.php
│   │   │   │   ├── contributor.php
│   │   │   │   └── contributor_group.php
│   │   │   ├── template_extensions.php
│   │   │   ├── templates/
│   │   │   │   ├── _contributor-table-flattr.twig
│   │   │   │   ├── _contributor-table-row.twig
│   │   │   │   ├── avatar.twig
│   │   │   │   ├── contributor-comma-separated.twig
│   │   │   │   ├── contributor-list.twig
│   │   │   │   ├── contributor-table.twig
│   │   │   │   ├── podcast-contributor-list.twig
│   │   │   │   └── podcast-contributor-table.twig
│   │   │   └── views/
│   │   │       └── form_table.php
│   │   ├── external_analytics/
│   │   │   └── external_analytics.php
│   │   ├── fyyd/
│   │   │   └── fyyd.php
│   │   ├── import_export/
│   │   │   ├── export/
│   │   │   │   ├── podcast_exporter.php
│   │   │   │   └── tracking_exporter.php
│   │   │   ├── import/
│   │   │   │   ├── podcast_import_assets_job.php
│   │   │   │   ├── podcast_import_episodes_job.php
│   │   │   │   ├── podcast_import_feeds_job.php
│   │   │   │   ├── podcast_import_filetypes_job.php
│   │   │   │   ├── podcast_import_job_table_trait.php
│   │   │   │   ├── podcast_import_job_trait.php
│   │   │   │   ├── podcast_import_mediafiles_job.php
│   │   │   │   ├── podcast_import_options_job.php
│   │   │   │   ├── podcast_import_templates_job.php
│   │   │   │   ├── podcast_import_tracking_area_job.php
│   │   │   │   ├── podcast_import_tracking_area_name_job.php
│   │   │   │   ├── podcast_import_user_agents_job.php
│   │   │   │   ├── podcast_importer.php
│   │   │   │   ├── podcast_importer_job.php
│   │   │   │   ├── tracking_importer.php
│   │   │   │   └── tracking_importer_job.php
│   │   │   ├── import_export.php
│   │   │   └── js/
│   │   │       └── import.js
│   │   ├── logging/
│   │   │   ├── log_table.php
│   │   │   ├── logging.php
│   │   │   ├── wpdbhandler.php
│   │   │   └── wpmail_handler.php
│   │   ├── networks/
│   │   │   ├── admin_bar_menu.php
│   │   │   ├── css/
│   │   │   │   └── admin.css
│   │   │   ├── model/
│   │   │   │   ├── network.php
│   │   │   │   └── podcast_list.php
│   │   │   ├── networks.php
│   │   │   ├── podcast_list_list_table.php
│   │   │   ├── podcast_list_table.php
│   │   │   ├── settings/
│   │   │   │   ├── dashboard.php
│   │   │   │   ├── podcast_lists.php
│   │   │   │   └── templates.php
│   │   │   └── template/
│   │   │       ├── network.php
│   │   │       └── podcast_list.php
│   │   ├── notifications/
│   │   │   ├── mailer_job.php
│   │   │   ├── notifications.php
│   │   │   └── settings_tab.php
│   │   ├── oembed/
│   │   │   └── oembed.php
│   │   ├── onboarding/
│   │   │   ├── css/
│   │   │   │   └── podlove-onboarding-banner.css
│   │   │   ├── onboarding.php
│   │   │   ├── rest_api.php
│   │   │   └── settings/
│   │   │       └── onboarding_page.php
│   │   ├── open_graph/
│   │   │   └── open_graph.php
│   │   ├── plus/
│   │   │   ├── api.php
│   │   │   ├── banner.html.php
│   │   │   ├── banner.php
│   │   │   ├── early_file_hosting_banner.php
│   │   │   ├── feed_proxy.php
│   │   │   ├── feed_pusher.php
│   │   │   ├── file_storage.php
│   │   │   ├── global_feed_settings.php
│   │   │   ├── growth_banner.php
│   │   │   ├── plus.php
│   │   │   ├── promotion_coordinator.php
│   │   │   ├── rest_api.php
│   │   │   └── settings_page.php
│   │   ├── podlove_web_player/
│   │   │   ├── media_tag_renderer.php
│   │   │   ├── player_printer_interface.php
│   │   │   ├── player_v3/
│   │   │   │   └── player_media_files.php
│   │   │   ├── player_v4/
│   │   │   │   ├── html5printer.php
│   │   │   │   ├── module.php
│   │   │   │   └── pwp4.js
│   │   │   ├── player_v5/
│   │   │   │   └── module.php
│   │   │   ├── podigee/
│   │   │   │   ├── html5printer.php
│   │   │   │   └── module.php
│   │   │   └── podlove_web_player.php
│   │   ├── protected_feed/
│   │   │   └── protected_feed.php
│   │   ├── pubsubhubbub/
│   │   │   └── pubsubhubbub.php
│   │   ├── readme.md
│   │   ├── related_episodes/
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── model/
│   │   │   │   └── episode_relation.php
│   │   │   ├── related_episodes.php
│   │   │   ├── shortcodes.php
│   │   │   ├── template_extensions.php
│   │   │   └── templates/
│   │   │       └── related-episodes-list.twig
│   │   ├── seasons/
│   │   │   ├── css/
│   │   │   │   └── admin.css
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── model/
│   │   │   │   ├── season.php
│   │   │   │   ├── season_map.php
│   │   │   │   ├── seasons_issue.php
│   │   │   │   └── seasons_validator.php
│   │   │   ├── podcast_import_seasons_job.php
│   │   │   ├── seasons.php
│   │   │   ├── settings/
│   │   │   │   ├── help/
│   │   │   │   │   └── settings.php
│   │   │   │   ├── season_list_table.php
│   │   │   │   └── settings.php
│   │   │   ├── template/
│   │   │   │   └── season.php
│   │   │   └── template_extensions.php
│   │   ├── shownotes/
│   │   │   ├── model/
│   │   │   │   └── entry.php
│   │   │   ├── rest_api.php
│   │   │   ├── shownotes.php
│   │   │   ├── template/
│   │   │   │   └── entry.php
│   │   │   ├── template_extensions.php
│   │   │   └── twig/
│   │   │       ├── plain-html-list-grouped.twig
│   │   │       ├── plain-html-list.twig
│   │   │       └── shownotes.twig
│   │   ├── shows/
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── model/
│   │   │   │   └── show.php
│   │   │   ├── rest_api.php
│   │   │   ├── settings/
│   │   │   │   ├── help/
│   │   │   │   │   └── settings.php
│   │   │   │   ├── settings.php
│   │   │   │   └── show_list_table.php
│   │   │   ├── shows.php
│   │   │   ├── template/
│   │   │   │   └── show.php
│   │   │   └── template_extensions.php
│   │   ├── slack_shownotes/
│   │   │   ├── message.php
│   │   │   ├── settings/
│   │   │   │   └── settings.php
│   │   │   └── slack_shownotes.php
│   │   ├── social/
│   │   │   ├── admin.css
│   │   │   ├── data/
│   │   │   │   └── services.yml
│   │   │   ├── jobs/
│   │   │   │   ├── podcast_import_contributor_services_job.php
│   │   │   │   ├── podcast_import_services_job.php
│   │   │   │   └── podcast_import_show_services_job.php
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── model/
│   │   │   │   ├── contributor_service.php
│   │   │   │   ├── service.php
│   │   │   │   └── show_service.php
│   │   │   ├── repair_social.php
│   │   │   ├── rest_api.php
│   │   │   ├── settings/
│   │   │   │   ├── podcast_settings_donation_tab.php
│   │   │   │   └── podcast_settings_social_tab.php
│   │   │   ├── shortcodes.php
│   │   │   ├── social.php
│   │   │   ├── template/
│   │   │   │   └── service.php
│   │   │   ├── template_extensions.php
│   │   │   └── templates/
│   │   │       ├── podcast-donations-list.twig
│   │   │       └── podcast-social-media-list.twig
│   │   ├── soundbite/
│   │   │   └── soundbite.php
│   │   ├── subscribe_button/
│   │   │   ├── button.php
│   │   │   ├── js/
│   │   │   │   └── admin.js
│   │   │   ├── subscribe_button.php
│   │   │   ├── template_extensions.php
│   │   │   └── widget.php
│   │   ├── title_migration/
│   │   │   ├── notices.php
│   │   │   ├── state.php
│   │   │   └── title_migration.php
│   │   ├── transcripts/
│   │   │   ├── jobs/
│   │   │   │   ├── import_transcripts_job.php
│   │   │   │   └── import_voice_assignments_job.php
│   │   │   ├── model/
│   │   │   │   ├── transcript.php
│   │   │   │   └── voice_assignment.php
│   │   │   ├── renderer.php
│   │   │   ├── rest_api.php
│   │   │   ├── template/
│   │   │   │   ├── group.php
│   │   │   │   └── line.php
│   │   │   ├── template_extensions.php
│   │   │   ├── transcripts.php
│   │   │   └── twig/
│   │   │       └── transcript.twig
│   │   ├── widgets/
│   │   │   ├── widgets/
│   │   │   │   ├── podcast_information.php
│   │   │   │   ├── podcast_license.php
│   │   │   │   ├── recent_episodes.php
│   │   │   │   └── render_template.php
│   │   │   └── widgets.php
│   │   └── wordpress_file_upload/
│   │       └── wordpress_file_upload.php
│   ├── network.php
│   ├── php/
│   │   ├── array.php
│   │   └── string.php
│   ├── php_deprecation_warning.php
│   ├── podcast_post_meta_box.php
│   ├── podcast_post_type.php
│   ├── repair.php
│   ├── settings/
│   │   ├── analytics.php
│   │   ├── dashboard/
│   │   │   ├── about.php
│   │   │   ├── file_validation.php
│   │   │   ├── news.php
│   │   │   └── statistics.php
│   │   ├── dashboard.php
│   │   ├── episode_asset.php
│   │   ├── expert/
│   │   │   ├── tab/
│   │   │   │   ├── file_types.php
│   │   │   │   ├── metadata.php
│   │   │   │   ├── redirects.php
│   │   │   │   ├── tracking.php
│   │   │   │   ├── web_player.php
│   │   │   │   └── website.php
│   │   │   ├── tab.php
│   │   │   └── tabs.php
│   │   ├── feed.php
│   │   ├── file_type.php
│   │   ├── help/
│   │   │   ├── analytics.php
│   │   │   ├── dashboard.php
│   │   │   ├── episode_asset.php
│   │   │   ├── feed.php
│   │   │   ├── modules.php
│   │   │   ├── podcast.php
│   │   │   ├── settings.php
│   │   │   ├── support.php
│   │   │   └── templates.php
│   │   ├── modules.php
│   │   ├── podcast/
│   │   │   ├── tab/
│   │   │   │   ├── description.php
│   │   │   │   ├── directory.php
│   │   │   │   ├── license.php
│   │   │   │   ├── media.php
│   │   │   │   └── player.php
│   │   │   └── tab.php
│   │   ├── podcast.php
│   │   ├── settings.php
│   │   ├── support.php
│   │   ├── templates.php
│   │   ├── tools/
│   │   │   └── user_agent_refresh.php
│   │   └── tools.php
│   ├── shortcodes.php
│   ├── slug_freeze.php
│   ├── system_report.php
│   ├── template/
│   │   ├── asset.php
│   │   ├── category.php
│   │   ├── chapter.php
│   │   ├── date_time.php
│   │   ├── duration.php
│   │   ├── episode.php
│   │   ├── episode_title.php
│   │   ├── feed.php
│   │   ├── file.php
│   │   ├── file_type.php
│   │   ├── image.php
│   │   ├── license.php
│   │   ├── podcast.php
│   │   ├── tag.php
│   │   ├── twig_date_extension.php
│   │   ├── twig_filter.php
│   │   ├── twig_loader_podlove_database.php
│   │   ├── twig_sandbox.php
│   │   └── wrapper.php
│   ├── tools.php
│   ├── tracking/
│   │   └── debug.php
│   ├── version.php
│   └── webhook/
│       └── webhook.php
├── license.txt
├── mise.toml
├── package.json
├── phpunit.xml.dist
├── plugin.php
├── podlove.php
├── readme.txt
├── templates/
│   ├── feed-rss2.php
│   ├── license.twig
│   ├── network/
│   │   └── network-bar.twig
│   └── shortcode/
│       ├── downloads-buttons.twig
│       ├── downloads-select.twig
│       ├── episode-list.twig
│       └── feed-list.twig
├── tests/
│   └── phpunit/
│       ├── bootstrap.php
│       ├── helpers/
│       │   ├── EpisodeFactory.php
│       │   ├── db.php
│       │   └── module.php
│       ├── integration/
│       │   ├── FeedHtmlOptimizationTest.php
│       │   ├── ModuleUninstallTest.php
│       │   ├── PluginActivationTest.php
│       │   ├── PlusFeedProxyTest.php
│       │   ├── SeasonsTest.php
│       │   ├── SeasonsValidatorTest.php
│       │   └── SlackMessageTest.php
│       └── rest/
│           └── EpisodesApiTest.php
├── vetur.config.js
└── views/
    ├── expert_settings/
    │   └── website/
    │       ├── blog_post_title.php
    │       ├── custom_episode_slug.php
    │       ├── episode_archive.php
    │       └── landing_page.php
    └── settings/
        └── dashboard/
            ├── about.php
            ├── dashboard.php
            ├── file_validation.php
            ├── news.php
            └── statistics.php
Download .txt
Showing preview only (287K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (3383 symbols across 451 files)

FILE: bin/template_ref.rb
  function templateRefClasses (line 4) | def templateRefClasses
  function renderDescription (line 9) | def renderDescription(s)

FILE: bin/wp-env-test-after-start.js
  function runWpEnv (line 9) | function runWpEnv(args) {

FILE: bootstrap/autoload.php
  function podlove_camelcase_to_snakecase (line 3) | function podlove_camelcase_to_snakecase($string)
  function podlove_camelsnakecase_to_camelcase (line 8) | function podlove_camelsnakecase_to_camelcase($string)
  function podlove_snakecase_to_camelsnakecase (line 13) | function podlove_snakecase_to_camelsnakecase($string)
  function podlove_autoloader (line 21) | function podlove_autoloader($class_name)

FILE: bootstrap/constants.php
  function get_plugin_header (line 25) | function get_plugin_header($tag_name)

FILE: client/src/lib/api.ts
  type ApiOptions (line 34) | interface ApiOptions {
  type PodloveApiClient (line 102) | interface PodloveApiClient {

FILE: client/src/lib/auphonic.api.ts
  function defaultProgressHandler (line 79) | function defaultProgressHandler(e: AxiosProgressEvent) {
  type AuphonicApiClient (line 117) | interface AuphonicApiClient {

FILE: client/src/lib/chapters.ts
  function parseMp4Chapters (line 4) | function parseMp4Chapters(input: string): PodloveChapter[] {
  function parseAudacityChapters (line 28) | function parseAudacityChapters(input: string): PodloveChapter[] {
  function parseHindeburgChapters (line 52) | function parseHindeburgChapters(input: string) {
  function parsePodloveChapters (line 84) | function parsePodloveChapters(input: string): PodloveChapter[] {

FILE: client/src/lib/errorHandling.ts
  type FileWithUrl (line 5) | interface FileWithUrl {
  type ErrorResponse (line 12) | interface ErrorResponse {

FILE: client/src/lib/license.ts
  function getLicenseUrl (line 18) | function getLicenseUrl(input: PodloveLicense): string | null {
  function getImageUrl (line 47) | function getImageUrl(input: PodloveLicense, baseUrl: string): string | n...
  function getLicenseFromUrl (line 64) | function getLicenseFromUrl(url: string): PodloveLicense {

FILE: client/src/lib/popper.ts
  function usePopper (line 4) | function usePopper(options: Partial<Options>) {

FILE: client/src/lib/statusHelpers.ts
  type ProcessingStatus (line 5) | type ProcessingStatus = 'completed' | 'completed_with_errors' | 'failed'
  type MigrationStatus (line 6) | type MigrationStatus = 'finished' | 'error'

FILE: client/src/lib/timestamp.ts
  class Timestamp (line 3) | class Timestamp {
    method constructor (line 4) | constructor(public totalMs: number) {}
    method totalSeconds (line 6) | get totalSeconds() {
    method totalMinutes (line 10) | get totalMinutes() {
    method totalHours (line 14) | get totalHours() {
    method milliseconds (line 18) | get milliseconds() {
    method seconds (line 22) | get seconds() {
    method minutes (line 26) | get minutes() {
    method hours (line 30) | get hours() {
    method pretty (line 34) | get pretty() {
    method prettyShort (line 46) | get prettyShort() {
    method pad (line 54) | pad(num: number, pad = '00') {
    method fromString (line 64) | static fromString(t: string | number) {

FILE: client/src/plugins/translations.ts
  type ComponentCustomProperties (line 5) | interface ComponentCustomProperties {
  method install (line 15) | install(app: App) {

FILE: client/src/sagas/admin.sagas.ts
  type AdminData (line 8) | interface AdminData {

FILE: client/src/sagas/auphonic.sagas.ts
  type PreparedFileSelection (line 276) | type PreparedFileSelection = {
  function getFileSelectionsForSingleTrack (line 296) | function getFileSelectionsForSingleTrack(state: State): PreparedFileSele...
  function getFileSelectionsForMultiTrack (line 303) | function getFileSelectionsForMultiTrack(state: State): PreparedFileSelec...
  function getTracksPayload (line 316) | function getTracksPayload(state: State): any {
  function getProductionPayload (line 373) | function getProductionPayload(state: State): object {
  function getSaveProductionPayload (line 420) | function getSaveProductionPayload(state: State): object {
  function uploadProductionImage (line 450) | async function uploadProductionImage(
  function shouldInlineChapterImage (line 483) | function shouldInlineChapterImage(imageUrl: string): boolean {
  function blobToBase64 (line 497) | async function blobToBase64(blob: Blob): Promise<string> {
  function inlineChapterImages (line 520) | async function inlineChapterImages(chapters: any[] | undefined): Promise...
  type WebhookConfig (line 707) | type WebhookConfig = {
  function getPendingFiles (line 863) | function getPendingFiles(transferQueue: any[], completedCount: number): ...
  function createProcessingFile (line 874) | function createProcessingFile(file: any): any {
  function updateFileResult (line 885) | function updateFileResult(result: any): any {

FILE: client/src/sagas/chapters.sagas.ts
  function generatePscDownload (line 83) | function generatePscDownload(chapters: PodloveChapter[]): string {
  function generateMp4Download (line 125) | function generateMp4Download(chapters: PodloveChapter[]): string {
  function download (line 149) | function download(name: string, data: any) {

FILE: client/src/sagas/episode.sagas.ts
  constant EPISODE_UPDATE (line 15) | let EPISODE_UPDATE: { [key: string]: any } = {}

FILE: client/src/sagas/helper.ts
  function sleep (line 28) | function sleep(sec: number): Promise<void> {
  type ProgressPayload (line 43) | type ProgressPayload = {
  type ProgressData (line 48) | interface ProgressData {

FILE: client/src/sagas/lifecycle.sagas.ts
  function lifecycleSaga (line 6) | function lifecycleSaga(): () => any {
  function clickListener (line 22) | function clickListener(eventName: string, selector: string) {

FILE: client/src/sagas/mediafiles.duration.sagas.ts
  function loadMeta (line 29) | async function loadMeta(audio: HTMLAudioElement) {
  function fetchDuration (line 33) | async function fetchDuration(src: string) {

FILE: client/src/sagas/mediafiles.fileselection.sagas.ts
  function rejectExistingFiles (line 45) | function rejectExistingFiles(files: File[], existingFiles: File[]): File...
  function extractSlugFromFilename (line 53) | function extractSlugFromFilename(fileName: string): string {

FILE: client/src/sagas/notification.saga.ts
  function errorSaga (line 6) | function errorSaga() {
  function wordPressError (line 22) | function wordPressError(
  function consoleError (line 40) | function consoleError({

FILE: client/src/sagas/podcast.sagas.ts
  type PodcastData (line 10) | interface PodcastData {
  constant PODCAST_UPDATE (line 23) | let PODCAST_UPDATE: { [key: string]: any } = {}
  function collectPodcastUpdate (line 40) | function collectPodcastUpdate(action: Action) {

FILE: client/src/sagas/relatedEpisodes.sagas.ts
  type EpisodeApiListItem (line 11) | type EpisodeApiListItem = {

FILE: client/src/sagas/wordpress.sagas.ts
  function getFeaturedImageIdFromEditor (line 35) | function getFeaturedImageIdFromEditor() {
  function getTitleFromEditor (line 48) | function getTitleFromEditor() {

FILE: client/src/store/admin.store.ts
  constant INIT (line 4) | const INIT = 'podlove/publisher/admin/INIT'
  constant SET (line 5) | const SET = 'podlove/publisher/admin/SET'
  constant UPDATE_TYPE (line 6) | const UPDATE_TYPE = 'podlove/publisher/admin/UPDATE_TYPE'
  type State (line 8) | type State = {

FILE: client/src/store/auphonic.store.ts
  type Service (line 3) | type Service = {
  type Metadata (line 12) | type Metadata = {
  type AuphonicChapter (line 29) | type AuphonicChapter = {
  type Production (line 39) | type Production = {
  type PlusTransferFile (line 66) | type PlusTransferFile = {
  type AuphonicInputFile (line 74) | type AuphonicInputFile = {
  type AuphonicOutputFile (line 88) | type AuphonicOutputFile = {
  type AuphonicTrackAlgorithms (line 99) | type AuphonicTrackAlgorithms = {
  type Preset (line 106) | type Preset = Production & {
  type AudioTrack (line 110) | type AudioTrack = {
  type FileSelection (line 122) | type FileSelection = {
  type PlusTransferStatus (line 129) | type PlusTransferStatus = {
  type State (line 136) | type State = {
  constant INIT (line 170) | const INIT = 'podlove/publisher/auphonic/INIT'
  constant INIT_DONE (line 171) | const INIT_DONE = 'podlove/publisher/auphonic/INIT_DONE'
  constant SET_TOKEN (line 172) | const SET_TOKEN = 'podlove/publisher/auphonic/SET_TOKEN'
  constant SET_PRODUCTION (line 173) | const SET_PRODUCTION = 'podlove/publisher/auphonic/SET_PRODUCTION'
  constant SET_PRODUCTIONS (line 174) | const SET_PRODUCTIONS = 'podlove/publisher/auphonic/SET_PRODUCTIONS'
  constant SET_SERVICES (line 175) | const SET_SERVICES = 'podlove/publisher/auphonic/SET_SERVICES'
  constant CREATE_PRODUCTION (line 176) | const CREATE_PRODUCTION = 'podlove/publisher/auphonic/CREATE_PRODUCTION'
  constant CREATE_MULTITRACK_PRODUCTION (line 177) | const CREATE_MULTITRACK_PRODUCTION =
  constant SAVE_PRODUCTION (line 179) | const SAVE_PRODUCTION = 'podlove/publisher/auphonic/SAVE_PRODUCTION'
  constant START_PRODUCTION (line 180) | const START_PRODUCTION = 'podlove/publisher/auphonic/START_PRODUCTION'
  constant DESELECT_PRODUCTION (line 181) | const DESELECT_PRODUCTION = 'podlove/publisher/auphonic/DESELECT_PRODUCT...
  constant SELECT_SERVICE (line 182) | const SELECT_SERVICE = 'podlove/publisher/auphonic/SELECT_SERVICE'
  constant SET_SERVICE_FILES (line 183) | const SET_SERVICE_FILES = 'podlove/publisher/auphonic/SET_SERVICE_FILES'
  constant SELECT_TRACKS (line 184) | const SELECT_TRACKS = 'podlove/publisher/auphonic/SELECT_TRACKS'
  constant ADD_TRACK (line 185) | const ADD_TRACK = 'podlove/publisher/auphonic/ADD_TRACK'
  constant REMOVE_TRACK (line 186) | const REMOVE_TRACK = 'podlove/publisher/auphonic/REMOVE_TRACK'
  constant UPDATE_TRACK (line 187) | const UPDATE_TRACK = 'podlove/publisher/auphonic/UPDATE_TRACK'
  constant SET_PRESETS (line 188) | const SET_PRESETS = 'podlove/publisher/auphonic/SET_PRESETS'
  constant SET_PRESET (line 189) | const SET_PRESET = 'podlove/publisher/auphonic/SET_PRESET'
  constant UPDATE_FILE_SELECTION (line 190) | const UPDATE_FILE_SELECTION = 'podlove/publisher/auphonic/UPDATE_FILE_SE...
  constant START_POLLING (line 191) | const START_POLLING = 'podlove/publisher/auphonic/START_POLLING'
  constant STOP_POLLING (line 192) | const STOP_POLLING = 'podlove/publisher/auphonic/STOP_POLLING'
  constant START_SAVING (line 193) | const START_SAVING = 'podlove/publisher/auphonic/START_SAVING'
  constant STOP_SAVING (line 194) | const STOP_SAVING = 'podlove/publisher/auphonic/STOP_SAVING'
  constant UPDATE_WEBHOOK (line 195) | const UPDATE_WEBHOOK = 'podlove/publisher/auphonic/UPDATE_WEBHOOK'
  constant SET_PLUS_TRANSFER_STATUS (line 196) | const SET_PLUS_TRANSFER_STATUS = 'podlove/publisher/auphonic/SET_PLUS_TR...
  constant TRIGGER_PLUS_TRANSFER (line 197) | const TRIGGER_PLUS_TRANSFER = 'podlove/publisher/auphonic/TRIGGER_PLUS_T...
  constant LOAD_PLUS_TRANSFER_STATUS (line 198) | const LOAD_PLUS_TRANSFER_STATUS = 'podlove/publisher/auphonic/LOAD_PLUS_...

FILE: client/src/store/chapters.store.ts
  type State (line 5) | type State = {
  constant INIT (line 15) | const INIT = 'podlove/publisher/chapter/INIT'
  constant UPDATE (line 16) | const UPDATE = 'podlove/publisher/chapter/UPDATE'
  constant SELECT (line 17) | const SELECT = 'podlove/publisher/chapter/SELECT'
  constant REMOVE (line 18) | const REMOVE = 'podlove/publisher/chapter/REMOVE'
  constant ADD (line 19) | const ADD = 'podlove/publisher/chapter/ADD'
  constant PARSE (line 20) | const PARSE = 'podlove/publisher/chapter/PARSE'
  constant PARSED (line 21) | const PARSED = 'podlove/publisher/chapter/PARSED'
  constant SET (line 22) | const SET = 'podlove/publisher/chapter/SET'
  constant DOWNLOAD (line 23) | const DOWNLOAD = 'podlove/publisher/chapter/DOWNLOAD'
  constant SELECT_IMAGE (line 24) | const SELECT_IMAGE = 'podlove/publisher/chapter/SELECT_IMAGE'
  constant SET_IMAGE (line 25) | const SET_IMAGE = 'podlove/publisher/chapter/SET_IMAGE'

FILE: client/src/store/contributors.store.ts
  type State (line 4) | type State = {
  constant INIT (line 16) | const INIT = 'podlove/publisher/contributors/INIT'
  constant SET_CONTRIBUTORS (line 17) | const SET_CONTRIBUTORS = 'podlove/publisher/contributors/SET_CONTRIBUTORS'
  constant SET_ROLES (line 18) | const SET_ROLES = 'podlove/publisher/contributors/SET_ROLES'
  constant SET_GROUPS (line 19) | const SET_GROUPS = 'podlove/publisher/contributors/SET_GROUPS'
  constant ADD_CONTRIBUTOR (line 20) | const ADD_CONTRIBUTOR = 'podlove/publisher/contributors/ADD'

FILE: client/src/store/episode.store.ts
  constant INIT (line 10) | const INIT = 'podlove/publisher/episode/INIT'
  constant UPDATE (line 11) | const UPDATE = 'podlove/publisher/episode/UPDATE'
  constant QUICKSAVE (line 12) | const QUICKSAVE = 'podlove/publisher/episode/QUICKSAVE'
  constant SAVED (line 13) | const SAVED = 'podlove/publisher/episode/SAVED'
  constant SLUG_CHANGED (line 14) | const SLUG_CHANGED = 'podlove/publisher/episode/SLUG_CHANGED'
  constant SET (line 15) | const SET = 'podlove/publisher/episode/SET'
  constant SET_POSTER (line 16) | const SET_POSTER = 'podlove/publisher/episode/SET_POSTER'
  constant SELECT_POSTER (line 17) | const SELECT_POSTER = 'podlove/publisher/episode/SELECT_POSTER'
  constant MOVE_CONTRIBUTION_UP (line 18) | const MOVE_CONTRIBUTION_UP = 'podlove/publisher/episode/MOVE_CONTRIBUTIO...
  constant MOVE_CONTRIBUTION_DOWN (line 19) | const MOVE_CONTRIBUTION_DOWN = 'podlove/publisher/episode/MOVE_CONTRIBUT...
  constant DELETE_CONTRIBUTION (line 20) | const DELETE_CONTRIBUTION = 'podlove/publisher/episode/DELETE_CONTRIBUTION'
  constant UPDATE_CONTRIBUTION (line 21) | const UPDATE_CONTRIBUTION = 'podlove/publisher/episode/UPDATE_CONTRIBUTION'
  constant ADD_CONTRIBUTION (line 22) | const ADD_CONTRIBUTION = 'podlove/publisher/episode/ADD_CONTRIBUTION'
  constant CREATE_CONTRIBUTION (line 23) | const CREATE_CONTRIBUTION = 'podlove/publisher/episode/CREATE_CONTRIBUTION'
  type State (line 25) | type State = {

FILE: client/src/store/index.ts
  type Window (line 2) | interface Window {
  type State (line 47) | interface State {

FILE: client/src/store/lifecycle.store.ts
  type State (line 3) | type State = {
  constant INIT (line 9) | const INIT = 'podlove/publisher/INIT'
  constant READY (line 10) | const READY = 'podlove/publisher/READY'
  constant SAVE (line 11) | const SAVE = 'podlove/publisher/SAVE'
  constant ERROR (line 12) | const ERROR = 'podlove/publisher/ERROR'

FILE: client/src/store/mediafiles.store.ts
  type MediaFile (line 3) | type MediaFile = {
  type FileInfo (line 12) | type FileInfo = {
  type State (line 19) | type State = {
  constant INIT (line 33) | const INIT = 'podlove/publisher/mediafiles/INIT'
  constant INIT_DONE (line 34) | const INIT_DONE = 'podlove/publisher/mediafiles/INIT_DONE'
  constant SET (line 35) | const SET = 'podlove/publisher/mediafiles/SET'
  constant UPDATE (line 36) | const UPDATE = 'podlove/publisher/mediafiles/UPDATE'
  constant ENABLE (line 37) | const ENABLE = 'podlove/publisher/mediafiles/ENABLE'
  constant DISABLE (line 38) | const DISABLE = 'podlove/publisher/mediafiles/DISABLE'
  constant VERIFY (line 39) | const VERIFY = 'podlove/publisher/mediafiles/VERIFY'
  constant VERIFY_ALL (line 40) | const VERIFY_ALL = 'podlove/publisher/mediafiles/VERIFY_ALL'
  constant UPLOAD_INTENT (line 41) | const UPLOAD_INTENT = 'podlove/publisher/mediafiles/UPLOAD_INTENT'
  constant PLUS_UPLOAD_INTENT (line 42) | const PLUS_UPLOAD_INTENT = 'podlove/publisher/mediafiles/PLUS_UPLOAD_INT...
  constant SET_UPLOAD_URL (line 43) | const SET_UPLOAD_URL = 'podlove/publisher/mediafiles/SET_UPLOAD_URL'
  constant ENABLE_SLUG_AUTOGEN (line 44) | const ENABLE_SLUG_AUTOGEN = 'podlove/publisher/mediafiles/ENABLE_SLUG_AU...
  constant DISABLE_SLUG_AUTOGEN (line 45) | const DISABLE_SLUG_AUTOGEN = 'podlove/publisher/mediafiles/DISABLE_SLUG_...
  constant FILE_SELECTED (line 46) | const FILE_SELECTED = 'podlove/publisher/mediafiles/FILE_SELECTED'
  constant SET_FILE_INFO (line 47) | const SET_FILE_INFO = 'podlove/publisher/mediafiles/SET_FILE_INFO'
  constant ADD_SELECTED_FILES (line 48) | const ADD_SELECTED_FILES = 'podlove/publisher/mediafiles/ADD_SELECTED_FI...
  constant REMOVE_SELECTED_FILE (line 49) | const REMOVE_SELECTED_FILE = 'podlove/publisher/mediafiles/REMOVE_SELECT...
  constant UNFREEZE_SLUG (line 50) | const UNFREEZE_SLUG = 'podlove/publisher/mediafiles/UNFREEZE_SLUG'

FILE: client/src/store/notification.store.ts
  constant NOTIFY (line 3) | const NOTIFY = 'podlove/publisher/NOTIFY'
  type State (line 6) | type State = {

FILE: client/src/store/plus.store.ts
  type PlusFeatures (line 4) | type PlusFeatures = {
  type State (line 9) | type State = {
  constant INIT (line 30) | const INIT = 'podlove/publisher/plus/INIT'
  constant SET_FEATURE (line 31) | const SET_FEATURE = 'podlove/publisher/plus/SET_FEATURE'
  constant GET_TOKEN (line 32) | const GET_TOKEN = 'podlove/publisher/plus/GET_TOKEN'
  constant SET_TOKEN (line 33) | const SET_TOKEN = 'podlove/publisher/plus/SET_TOKEN'
  constant SET_USER (line 34) | const SET_USER = 'podlove/publisher/plus/SET_USER'
  constant SAVE_TOKEN (line 35) | const SAVE_TOKEN = 'podlove/publisher/plus/SAVE_TOKEN'
  constant SET_LOADING (line 36) | const SET_LOADING = 'podlove/publisher/plus/SET_LOADING'
  constant SET_SAVING (line 37) | const SET_SAVING = 'podlove/publisher/plus/SET_SAVING'

FILE: client/src/store/plusFileMigration.store.ts
  type UploadState (line 4) | type UploadState = 'init' | 'ready' | 'in_progress' | 'finished' | 'error'
  type UploadFile (line 6) | type UploadFile = {
  type EpisodeWithFiles (line 13) | type EpisodeWithFiles = {
  type State (line 18) | type State = {
  constant INIT (line 38) | const INIT = 'podlove/publisher/plusFileMigration/INIT'
  constant SET_EPISODES_WITH_FILES (line 39) | const SET_EPISODES_WITH_FILES = 'podlove/publisher/plusFileMigration/SET...
  constant SET_TOTAL_STATE (line 40) | const SET_TOTAL_STATE = 'podlove/publisher/plusFileMigration/SET_TOTAL_S...
  constant START_MIGRATION (line 41) | const START_MIGRATION = 'podlove/publisher/plusFileMigration/START_MIGRA...
  constant SET_CURRENT_METADATA (line 42) | const SET_CURRENT_METADATA = 'podlove/publisher/plusFileMigration/SET_CU...
  constant SET_FILE_STATE (line 43) | const SET_FILE_STATE = 'podlove/publisher/plusFileMigration/SET_FILE_STATE'
  constant SET_PROGRESS (line 44) | const SET_PROGRESS = 'podlove/publisher/plusFileMigration/SET_PROGRESS'
  constant SET_MIGRATION_COMPLETE (line 45) | const SET_MIGRATION_COMPLETE = 'podlove/publisher/plusFileMigration/SET_...
  constant TOGGLE_MIGRATION_TOOL_MANUALLY (line 46) | const TOGGLE_MIGRATION_TOOL_MANUALLY = 'podlove/publisher/plusFileMigrat...

FILE: client/src/store/podcast.store.ts
  constant INIT (line 5) | const INIT = 'podlove/publisher/podcast/INIT'
  constant SET (line 6) | const SET = 'podlove/publisher/podcast/SET'
  constant SAVED (line 7) | const SAVED = 'podlove/publisher/podcasr/SAVED'
  constant UPDATE (line 8) | const UPDATE = 'podlove/publisher/podcast/UPDATE'
  type State (line 11) | type State = {

FILE: client/src/store/post.store.ts
  type State (line 6) | type State = {

FILE: client/src/store/progress.store.ts
  type ProgressStatus (line 3) | type ProgressStatus = 'init' | 'in_progress' | 'finished' | 'error'
  type ProgressItem (line 5) | type ProgressItem = {
  type State (line 11) | type State = {
  type SetProgressPayload (line 15) | type SetProgressPayload = {
  type SetProgressStatusPayload (line 22) | type SetProgressStatusPayload = {
  constant SET_PROGRESS (line 30) | const SET_PROGRESS = 'podlove/publisher/progress/SET_PROGRESS'
  constant SET_PROGRESS_STATUS (line 31) | const SET_PROGRESS_STATUS = 'podlove/publisher/progress/SET_PROGRESS_STA...
  constant RESET_PROGRESS (line 32) | const RESET_PROGRESS = 'podlove/publisher/progress/RESET_PROGRESS'

FILE: client/src/store/relatedEpisodes.store.ts
  type State (line 5) | type State = {
  constant INIT (line 15) | const INIT = 'podlove/publisher/relatedEpisodes/INIT'
  constant SET_EPISODE_LIST (line 16) | const SET_EPISODE_LIST = 'podlove/publisher/relatedEpisodes/SET_EPISODE_...
  constant SET_SELECTED_EPISODES (line 17) | const SET_SELECTED_EPISODES = 'podlove/publisher/relatedEpisodes/SET_SEL...
  constant UPDATE_RELATED_EPISODES (line 18) | const UPDATE_RELATED_EPISODES = 'podlove/publisher/relatedEpisodes/UPDAT...

FILE: client/src/store/runtime.store.ts
  type State (line 5) | type State = {

FILE: client/src/store/settings.store.ts
  type TrackingMode (line 5) | type TrackingMode = 'ptm_analytics'
  type TrackingWindow (line 6) | type TrackingWindow = 'daily'
  type State (line 8) | interface State {

FILE: client/src/store/shows.store.ts
  constant INIT (line 5) | const INIT = 'podlove/publisher/shows/INIT'
  constant SET (line 6) | const SET = 'podlove/publisher/shows/SET'
  constant SELECT (line 7) | const SELECT = 'podlove/publisher/shows/SELECT'
  type State (line 13) | type State = {

FILE: client/src/store/transcripts.store.ts
  constant INIT (line 5) | const INIT = 'podlove/publisher/transcript/INIT'
  constant SET_TRANSCRIPTS (line 6) | const SET_TRANSCRIPTS = 'podlove/publisher/transcript/SET_TRANSCRIPTS'
  constant SET_VOICES (line 7) | const SET_VOICES = 'podlove/publisher/transcript/SET_VOICES'
  constant UPDATE_VOICE (line 8) | const UPDATE_VOICE = 'podlove/publisher/transcript/UPDATE_VOICE'
  constant IMPORT_TRANSCRIPTS (line 9) | const IMPORT_TRANSCRIPTS = 'podlove/publisher/transcript/IMPORT_TRANSCRI...
  constant IMPORT_ASSET_TRANSCRIPTS (line 10) | const IMPORT_ASSET_TRANSCRIPTS = 'podlove/publisher/transcript/IMPORT_AS...
  constant DELETE_TRANSCRIPTS (line 11) | const DELETE_TRANSCRIPTS = 'podlove/publisher/transcript/DELETE_TRANSCRI...
  type State (line 21) | type State = {

FILE: client/src/store/vue.ts
  type AppSelector (line 6) | type AppSelector<T> = (state: State) => T
  type AppSelectorMap (line 7) | type AppSelectorMap = Record<string, AppSelector<unknown>>
  type AppSelection (line 9) | type AppSelection<T extends AppSelectorMap> = {
  type AppStore (line 13) | type AppStore = Store<State>
  type AppDispatch (line 14) | type AppDispatch = Dispatch<UnknownAction>

FILE: client/src/store/wordpress.store.ts
  constant UPDATE (line 4) | const UPDATE = 'podlove/publisher/wordpress/UPDATE'
  constant SELECT_MEDIA_FROM_LIBRARY (line 5) | const SELECT_MEDIA_FROM_LIBRARY = 'podlove/publisher/wordpress/SELECT_ME...

FILE: client/src/types/chapters.types.ts
  type PodloveChapter (line 1) | interface PodloveChapter {

FILE: client/src/types/contributors.types.ts
  type PodloveContributor (line 1) | interface PodloveContributor {
  type PodloveRole (line 17) | interface PodloveRole {
  type PodloveGroup (line 23) | interface PodloveGroup {

FILE: client/src/types/episode.types.ts
  type PodloveEpisode (line 1) | interface PodloveEpisode {
  type PodloveEpisodeContribution (line 11) | interface PodloveEpisodeContribution {

FILE: client/src/types/license.types.ts
  type PodloveLicenseVersion (line 1) | enum PodloveLicenseVersion {
  type PodloveLicenseOptionCommercial (line 8) | enum PodloveLicenseOptionCommercial {
  type PodloveLicenseOptionModification (line 13) | enum PodloveLicenseOptionModification {
  type PodloveLicenseScope (line 19) | enum PodloveLicenseScope {
  type PodloveJurisdicationObject (line 24) | type PodloveJurisdicationObject = {
  type PodloveLicense (line 93) | interface PodloveLicense {

FILE: client/src/types/relatedEpisodes.types.ts
  type PodloveEpisodeList (line 1) | interface PodloveEpisodeList {

FILE: client/src/types/shows.types.ts
  type PodloveShow (line 1) | interface PodloveShow {

FILE: client/src/types/transcripts.types.ts
  type PodloveTranscript (line 1) | interface PodloveTranscript {
  type PodloveTranscriptVoice (line 10) | interface PodloveTranscriptVoice {

FILE: client/typings/podlove.d.ts
  type Chapter (line 1) | interface Chapter {

FILE: includes/about.php
  function podlove_maybe_redirect_to_about_page (line 12) | function podlove_maybe_redirect_to_about_page()
  function podlove_should_see_about_page (line 24) | function podlove_should_see_about_page()
  function podlove_about_page_init (line 43) | function podlove_about_page_init()
  function podlove_about_page (line 64) | function podlove_about_page($_)

FILE: includes/api/admin/onboarding.php
  class WP_REST_PodloveOnboarding_Controller (line 12) | class WP_REST_PodloveOnboarding_Controller extends \WP_REST_Controller
    method __construct (line 17) | public function __construct()
    method register_routes (line 26) | public function register_routes()
    method get_item_permissions_check (line 60) | public function get_item_permissions_check($request)
    method get_onboarding_options (line 69) | public function get_onboarding_options($request)
    method update_item_permissions_check (line 85) | public function update_item_permissions_check($request)
    method update_onboarding_options (line 94) | public function update_onboarding_options($request)

FILE: includes/api/admin/plus.php
  class WP_REST_PodlovePlus_Controller (line 14) | class WP_REST_PodlovePlus_Controller extends \WP_REST_Controller
    method __construct (line 16) | public function __construct()
    method register_routes (line 22) | public function register_routes()
    method get_episodes_for_migration (line 73) | public function get_episodes_for_migration($request)
    method get_features (line 111) | public function get_features($request)
    method set_feature (line 121) | public function set_feature($request)
    method get_token (line 154) | public function get_token($request)
    method validate_token (line 164) | public function validate_token($request)
    method save_token (line 191) | public function save_token($request)
    method get_item_permissions_check (line 204) | public function get_item_permissions_check($request)

FILE: includes/api/analytics.php
  function podlove_analytics_api_init (line 7) | function podlove_analytics_api_init()
  function podlove_api_analytics_permission_callback (line 57) | function podlove_api_analytics_permission_callback($request)
  function podlove_api_csv_response (line 70) | function podlove_api_csv_response($data)
  function podlove_api_analytics_episodes (line 87) | function podlove_api_analytics_episodes(WP_REST_Request $request)
  function podlove_api_analytics_episodes_selected (line 100) | function podlove_api_analytics_episodes_selected(WP_REST_Request $request)
  function podlove_api_analytics_episode (line 123) | function podlove_api_analytics_episode(WP_REST_Request $request)
  function podlove_api_analytics_prepare_episode (line 149) | function podlove_api_analytics_prepare_episode($item)

FILE: includes/api/chapters.php
  class WP_REST_PodloveChapters_Controller (line 16) | class WP_REST_PodloveChapters_Controller extends \WP_REST_Controller
    method __construct (line 18) | public function __construct()
    method register_routes (line 24) | public function register_routes()
    method get_item_permissions_check (line 120) | public function get_item_permissions_check($request)
    method get_item (line 125) | public function get_item($request)
    method create_item_permissions_check (line 144) | public function create_item_permissions_check($request)
    method create_item (line 153) | public function create_item($request)
    method update_item_permissions_check (line 209) | public function update_item_permissions_check($request)
    method update_item (line 218) | public function update_item($request)
    method delete_item_permissions_check (line 274) | public function delete_item_permissions_check($request)
    method delete_item (line 283) | public function delete_item($request)

FILE: includes/api/episodes.php
  function api_init (line 14) | function api_init()
  function list_api (line 35) | function list_api()
  function episodes_api (line 56) | function episodes_api($request)
  function update_episode_permission_check (line 93) | function update_episode_permission_check($request)
  function episodes_update_api (line 106) | function episodes_update_api($request)
  function chapters (line 143) | function chapters($episode = null)
  class WP_REST_PodloveEpisode_Controller (line 157) | class WP_REST_PodloveEpisode_Controller extends \WP_REST_Controller
    method __construct (line 159) | public function __construct()
    method register_routes (line 165) | public function register_routes()
    method get_items_permissions_check (line 485) | public function get_items_permissions_check($request)
    method get_items (line 495) | public function get_items($request)
    method get_item_permissions_check (line 563) | public function get_item_permissions_check($request)
    method get_item (line 587) | public function get_item($request)
    method get_item_media (line 643) | public function get_item_media($request)
    method get_item_tags (line 680) | public function get_item_tags($request)
    method create_item_permissions_check (line 715) | public function create_item_permissions_check($request)
    method create_item (line 724) | public function create_item($request)
    method build_slug (line 753) | public function build_slug($request)
    method freeze_slug (line 772) | public function freeze_slug($request)
    method unfreeze_slug (line 792) | public function unfreeze_slug($request)
    method update_item_permissions_check (line 812) | public function update_item_permissions_check($request)
    method update_item (line 821) | public function update_item($request)
    method update_item_media_enable (line 968) | public function update_item_media_enable($request)
    method update_item_media_disable (line 1005) | public function update_item_media_disable($request)
    method update_item_media_verify (line 1032) | public function update_item_media_verify($request)
    method update_item_tags (line 1068) | public function update_item_tags($request)
    method delete_item_permissions_check (line 1102) | public function delete_item_permissions_check($request)
    method delete_item (line 1111) | public function delete_item($request)
    method delete_item_tags (line 1130) | public function delete_item_tags($request)
    method get_episode_from_request (line 1155) | private function get_episode_from_request($request)
    method enrich_with_season (line 1171) | private function enrich_with_season($data, Episode $episode)

FILE: includes/api/episodes/contributions.php
  class WP_REST_PodloveEpisodeContributions_Controller (line 12) | class WP_REST_PodloveEpisodeContributions_Controller extends \WP_REST_Co...
    method __construct (line 14) | public function __construct()
    method register_routes (line 20) | public function register_routes()
    method get_item (line 109) | public function get_item($request)
    method get_contribution (line 152) | public function get_contribution($request)
    method get_item_permissions_check (line 191) | public function get_item_permissions_check($request)
    method create_item (line 196) | public function create_item($request)
    method create_item_permissions_check (line 219) | public function create_item_permissions_check($request)
    method update_item (line 228) | public function update_item($request)
    method update_contribution (line 298) | public function update_contribution($request)
    method update_item_permissions_check (line 353) | public function update_item_permissions_check($request)
    method delete_item (line 362) | public function delete_item($request)
    method delete_contribution (line 386) | public function delete_contribution($request)
    method delete_item_permissions_check (line 406) | public function delete_item_permissions_check($request)
    method isContributorDefault (line 415) | private function isContributorDefault($id)
    method isContributorVisible (line 420) | private function isContributorVisible($id)
    method isNotEmpty (line 427) | private function isNotEmpty($var)

FILE: includes/api/episodes/related_episodes.php
  class WP_REST_PodloveEpisodeRelated_Controller (line 8) | class WP_REST_PodloveEpisodeRelated_Controller extends \WP_REST_Controller
    method __construct (line 10) | public function __construct()
    method register_routes (line 16) | public function register_routes()
    method get_items (line 105) | public function get_items($request)
    method get_item (line 164) | public function get_item($request)
    method get_items_permissions_check (line 209) | public function get_items_permissions_check($request)
    method update_items (line 219) | public function update_items($request)
    method update_item (line 260) | public function update_item($request)
    method update_items_permissions_check (line 292) | public function update_items_permissions_check($request)
    method create_item (line 301) | public function create_item($request)
    method create_item_permissions_check (line 331) | public function create_item_permissions_check($request)
    method delete_items (line 340) | public function delete_items($request)
    method delete_item (line 364) | public function delete_item($request)
    method delete_items_permissions_check (line 386) | public function delete_items_permissions_check($request)
    method create_episode_relation (line 395) | private function create_episode_relation($id, $related_id)

FILE: includes/api/feeds.php
  class WP_REST_PodloveFeed_Controller (line 12) | class WP_REST_PodloveFeed_Controller extends \WP_REST_Controller
    method __construct (line 14) | public function __construct()
    method register_routes (line 20) | public function register_routes()
    method get_items_permissions_check (line 38) | public function get_items_permissions_check($request)
    method get_items (line 51) | public function get_items($request)
    method get_feeds (line 59) | public static function get_feeds($taxonomy = null, $term_id = null)

FILE: includes/api/podcast.php
  class WP_REST_Podlove_Controller (line 12) | class WP_REST_Podlove_Controller extends \WP_REST_Controller
    method __construct (line 17) | public function __construct()
    method register_routes (line 26) | public function register_routes()
    method get_item_permissions_check (line 119) | public function get_item_permissions_check($request)
    method update_item_permissions_check (line 129) | public function update_item_permissions_check($request)
    method get_item (line 138) | public function get_item($request)
    method update_item (line 183) | public function update_item($request)
    method getCategoryKey (line 267) | private function getCategoryKey($category)
    method getCategoryName (line 277) | private function getCategoryName($category_key)
    method getLanguageName (line 289) | private function getLanguageName($language_key)

FILE: includes/api/show.php
  function api_init (line 9) | function api_init()
  function show_api (line 19) | function show_api()

FILE: includes/api/tools.php
  class WP_REST_Podlove_Tools_Controller (line 10) | class WP_REST_Podlove_Tools_Controller extends \WP_REST_Controller
    method __construct (line 12) | public function __construct()
    method register_routes (line 18) | public function register_routes()
    method clear_caches (line 27) | public function clear_caches($request)
    method clear_caches_permission_check (line 37) | public function clear_caches_permission_check()

FILE: includes/auto_post_titles.php
  function podlove_maybe_override_post_titles (line 11) | function podlove_maybe_override_post_titles($original_title, $post_id = ...
  function podlove_maybe_override_rss_post_titles (line 34) | function podlove_maybe_override_rss_post_titles($original_title)
  function podlove_generated_post_title (line 73) | function podlove_generated_post_title($post_id)
  function podlove_generated_feed_post_title (line 80) | function podlove_generated_feed_post_title($post_id)
  function podlove_get_episode_title_by_template (line 87) | function podlove_get_episode_title_by_template($post_id, $template)
  function podlove_override_post_title_script (line 112) | function podlove_override_post_title_script()
  function podlove_get_mnemonic (line 135) | function podlove_get_mnemonic($post_id = null)
  function podlove_is_title_autogen_enabled (line 142) | function podlove_is_title_autogen_enabled()

FILE: includes/cache.php
  function podlove_clear_feed_cache_for_post (line 21) | function podlove_clear_feed_cache_for_post($post_id)

FILE: includes/capabilities.php
  function podlove_init_capabilities (line 14) | function podlove_init_capabilities()
  function podlove_add_capability_to_roles (line 28) | function podlove_add_capability_to_roles($capability, $roles = [])

FILE: includes/compatibility.php
  function podlove_detect_plugin_updates (line 13) | function podlove_detect_plugin_updates($upgrader_object, $options)
  function podlove_do_flush_rewrite_rules (line 32) | function podlove_do_flush_rewrite_rules()

FILE: includes/db_migration.php
  function podlove_do_migration_query (line 8) | function podlove_do_migration_query($sql)
  function podlove_show_database_migration_error (line 26) | function podlove_show_database_migration_error()
  function podlove_hide_migration_error_url (line 64) | function podlove_hide_migration_error_url()

FILE: includes/deprecations.php
  function podlove_init_deprecation_checker (line 4) | function podlove_init_deprecation_checker()
  function podlove_get_template_deprecations (line 19) | function podlove_get_template_deprecations()
  function podlove_get_episodes_deprecations (line 56) | function podlove_get_episodes_deprecations()
  function podlove_get_deprecations (line 89) | function podlove_get_deprecations()
  function podlove_get_deprecation_context (line 99) | function podlove_get_deprecation_context($context)
  function podlove_render_deprecations (line 126) | function podlove_render_deprecations($deprecations)
  function podlove_get_deprecated_shortcodes (line 153) | function podlove_get_deprecated_shortcodes()
  function podlove_get_deprecated_template_tags (line 173) | function podlove_get_deprecated_template_tags()

FILE: includes/detect_duplicate_slugs.php
  function podlove_check_for_duplicate_episode_slug (line 6) | function podlove_check_for_duplicate_episode_slug()
  function podlove_get_duplicate_episode_id (line 23) | function podlove_get_duplicate_episode_id(Episode $current_episode)
  function podlove_duplicate_episode_slug_notice (line 48) | function podlove_duplicate_episode_slug_notice(Episode $episode, $duplic...

FILE: includes/donation_banner.php
  function podlove_donation_banner (line 9) | function podlove_donation_banner()
  function podlove_donation_banner_hide (line 35) | function podlove_donation_banner_hide()
  function podlove_donation_count_published_episodes (line 40) | function podlove_donation_count_published_episodes()

FILE: includes/downloads.php
  function podlove_get_query_var (line 9) | function podlove_get_query_var($var_name)
  function podlove_get_remote_addr (line 18) | function podlove_get_remote_addr()
  function ga_track_download (line 30) | function ga_track_download($request_id, $media_file, $ua_string, $ptm_co...
  function matomo_track_download (line 107) | function matomo_track_download($request_id, $media_file, $ua_string, $pt...
  function podlove_handle_media_file_tracking (line 173) | function podlove_handle_media_file_tracking(Podlove\Model\MediaFile $med...
  function podlove_handle_media_file_download (line 228) | function podlove_handle_media_file_download()

FILE: includes/episode_number_column.php
  function podlove_add_episodeno_column_to_episodes_table (line 6) | function podlove_add_episodeno_column_to_episodes_table($columns)
  function podlove_add_episodeno_column_content_to_episodes_table (line 17) | function podlove_add_episodeno_column_content_to_episodes_table($column_...

FILE: includes/episode_number_quick_edit_form.php
  function podlove_episodeno_quickedit_form (line 8) | function podlove_episodeno_quickedit_form($column_name)
  function podlove_episodeno_quickedit_save (line 25) | function podlove_episodeno_quickedit_save($post_id)
  function podlove_episodeno_quickedit_populate_form (line 41) | function podlove_episodeno_quickedit_populate_form()
  function podlove_episodeno_quickedit_extend_action_items (line 72) | function podlove_episodeno_quickedit_extend_action_items($actions, $post)

FILE: includes/feed_discovery.php
  function podlove_add_feed_discoverability (line 10) | function podlove_add_feed_discoverability()

FILE: includes/http.php
  function podlove_is_resolved_and_reachable_http_status (line 12) | function podlove_is_resolved_and_reachable_http_status($status)

FILE: includes/images.php
  function podlove_validate_image_cache (line 18) | function podlove_validate_image_cache()
  function podlove_refetch_cached_image (line 48) | function podlove_refetch_cached_image($url, $filename)
  function podlove_handle_cache_files (line 74) | function podlove_handle_cache_files()

FILE: includes/jetpack.php
  function podlove_jetpack_enable_publicize (line 6) | function podlove_jetpack_enable_publicize()
  function podlove_jetpack_remove_rss_icon (line 14) | function podlove_jetpack_remove_rss_icon()

FILE: includes/license.php
  function podlove_episode_license_extend_form (line 8) | function podlove_episode_license_extend_form($form_data, $episode)

FILE: includes/no_enclosure_autodiscovery.php
  function podlove_no_enclosure_autodiscovery (line 14) | function podlove_no_enclosure_autodiscovery($meta_id, $post_id, $meta_ke...

FILE: includes/permalinks.php
  function podlove_and_wordpress_permastructs_are_equal (line 15) | function podlove_and_wordpress_permastructs_are_equal()
  function podlove_add_podcast_rewrite_rules (line 29) | function podlove_add_podcast_rewrite_rules()
  function podlove_add_podcast_episode_rules_to_post_rules (line 85) | function podlove_add_podcast_episode_rules_to_post_rules($post_rewrite)
  function podlove_podcast_permalink_proxy (line 114) | function podlove_podcast_permalink_proxy($query_vars)
  function podlove_no_verbose_page_rules (line 144) | function podlove_no_verbose_page_rules()
  function podlove_generate_custom_post_link (line 158) | function podlove_generate_custom_post_link($post_link, $id, $leavename =...

FILE: includes/podlove-web-player-5.php
  function podlove_pwp5_init (line 11) | function podlove_pwp5_init()
  function podlove_pwp5_attributes (line 16) | function podlove_pwp5_attributes($attributes)
  function podlove_pwp5_audio_files (line 77) | function podlove_pwp5_audio_files($episode, $context)
  function podlove_pwp5_files (line 104) | function podlove_pwp5_files($episode, $context)

FILE: includes/podlove_data_js_adapter.php
  function podlove_init_js_adapter (line 17) | function podlove_init_js_adapter()
  function podlove_init_js_content (line 31) | function podlove_init_js_content()
  function podlove_js_adapter_inject_settings (line 60) | function podlove_js_adapter_inject_settings($data)

FILE: includes/redirects.php
  function podlove_handle_user_redirects (line 9) | function podlove_handle_user_redirects()
  function podlove_handle_episode_redirects (line 78) | function podlove_handle_episode_redirects($value = '')

FILE: includes/request_id_rehash.php
  function podlove_rehash_init_tools_section (line 6) | function podlove_rehash_init_tools_section()
  function podlove_rehash_process_actions (line 25) | function podlove_rehash_process_actions()
  class PodloveSilentProgressBar (line 40) | class PodloveSilentProgressBar
    method __construct (line 42) | public function __construct($x = null) {}
    method display (line 44) | public function display() {}
    method progress (line 46) | public function progress() {}
    method end (line 48) | public function end() {}
  function podlove_rehash_tracking_request_ids (line 63) | function podlove_rehash_tracking_request_ids($blog_id = null)
  function podlove_rehash_replace_request_id (line 119) | function podlove_rehash_replace_request_id($table, $request_id)
  function podlove_rehash_fetch_some_request_ids (line 138) | function podlove_rehash_fetch_some_request_ids($table, $limit = null)
  function podlove_rehash_unsalted_time (line 169) | function podlove_rehash_unsalted_time()
  function podlove_rehash_total_remaining (line 177) | function podlove_rehash_total_remaining($table)
  function podlove_rehash_progress_class (line 190) | function podlove_rehash_progress_class()
  function podlove_rehash_log (line 199) | function podlove_rehash_log($message)
  function podlove_rehash_get_random_string (line 206) | function podlove_rehash_get_random_string()
  function podlove_rehash_func (line 219) | function podlove_rehash_func($old_hash, $salt)
  function podlove_rehash_prefix (line 232) | function podlove_rehash_prefix()

FILE: includes/screen_options.php
  function podlove_episodes_per_page_option_name (line 3) | function podlove_episodes_per_page_option_name()

FILE: includes/scripts_and_styles.php
  function add_type_attribute (line 3) | function add_type_attribute($tag, $handle, $src)

FILE: includes/search.php
  function podlove_is_search_query (line 3) | function podlove_is_search_query($query)

FILE: includes/setup.php
  function podlove_setup_database_tables (line 11) | function podlove_setup_database_tables()
  function podlove_setup_file_types (line 27) | function podlove_setup_file_types()
  function podlove_setup_podcast (line 72) | function podlove_setup_podcast()
  function podlove_setup_modules (line 88) | function podlove_setup_modules()
  function podlove_setup_expert_settings (line 113) | function podlove_setup_expert_settings()
  function podlove_setup_default_template (line 129) | function podlove_setup_default_template()
  function podlove_setup_default_media (line 162) | function podlove_setup_default_media()
  function podlove_setup_default_asset_assignments (line 186) | function podlove_setup_default_asset_assignments()

FILE: includes/setup_wizard.php
  function maybe_redirect_to_wizard_page (line 11) | function maybe_redirect_to_wizard_page()
  function wizard_page (line 43) | function wizard_page()

FILE: includes/system_report.php
  function podlove_run_system_report (line 6) | function podlove_run_system_report()

FILE: includes/template_pages.php
  function intercept_template (line 9) | function intercept_template()

FILE: includes/templates.php
  function podlove_autoinsert_templates_into_content (line 11) | function podlove_autoinsert_templates_into_content($content)
  function podlove_autoinsert_templates_head (line 40) | function podlove_autoinsert_templates_head()
  function podlove_autoinsert_templates_footer (line 53) | function podlove_autoinsert_templates_footer()
  function podlove_autoinsert_templates_header (line 66) | function podlove_autoinsert_templates_header()

FILE: includes/theme_helper.php
  function get_episode (line 12) | function get_episode($id = null)
  function get_podcast (line 36) | function get_podcast($blog_id = null)
  function get_network (line 48) | function get_network()

FILE: includes/trash.php
  function podlove_remove_trash_posts_from_the_posts (line 11) | function podlove_remove_trash_posts_from_the_posts($posts, $wp_query)

FILE: includes/verify_itunes_category.php
  function podlove_verify_itunes_category (line 4) | function podlove_verify_itunes_category()

FILE: includes/webhooks.php
  function podlove_fire_webhook (line 26) | function podlove_fire_webhook($event, $method, $payload, $url)
  function podlove_init_webhooks (line 36) | function podlove_init_webhooks($config)

FILE: includes/wp_rocket.php
  function podlove_fix_wprocket_excluded_external_js (line 9) | function podlove_fix_wprocket_excluded_external_js($external_js)

FILE: js/admin/ace/ace.js
  function o (line 1) | function o(n){var i=e;n&&(e[n]||(e[n]={}),i=e[n]);if(!i.define||!i.defin...
  function o (line 1) | function o(e){return(e.global?"g":"")+(e.ignoreCase?"i":"")+(e.multiline...
  function u (line 1) | function u(e,t,n){if(Array.prototype.indexOf)return e.indexOf(t,n);for(v...
  function r (line 1) | function r(){}
  function w (line 1) | function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentine...
  function H (line 1) | function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-...
  function B (line 1) | function B(e){var t=typeof e;return e===null||t==="undefined"||t==="bool...
  function j (line 1) | function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="funct...
  function e (line 1) | function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}
  function o (line 1) | function o(e,t,n){var o=s(t);if(!i.isMac&&u){if(u[91]||u[92])o|=8;if(u.a...
  function f (line 1) | function f(e){u=Object.create(null)}
  function i (line 1) | function i(e){n&&n(e),r&&r(e),t.removeListener(document,"mousemove",n,!0...
  function b (line 1) | function b(e){if(h)return;h=!0;if(k)t=0,r=e?0:n.value.length-1;else var ...
  function w (line 1) | function w(){if(h)return;n.value=f,i.isWebKit&&y.schedule()}
  function R (line 1) | function R(){clearTimeout(q),q=setTimeout(function(){p&&(n.style.cssText...
  function u (line 1) | function u(e){e.$clickSelection=null;var t=e.editor;t.setDefaultHandler(...
  function a (line 1) | function a(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}
  function f (line 1) | function f(e,t){if(e.start.row==e.end.row)var n=2*t.column-e.start.colum...
  function s (line 1) | function s(e){this.isOpen=!1,this.$element=null,this.$parentNode=e}
  function u (line 1) | function u(e){function l(){var r=u.getDocumentPosition().row,s=n.$annota...
  function a (line 1) | function a(e){o.call(this,e)}
  function f (line 1) | function f(e){function T(e,n){var r=Date.now(),i=!n||e.row!=n.row,s=!n||...
  function l (line 1) | function l(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}
  function o (line 1) | function o(e){typeof console!="undefined"&&console.warn&&console.warn.ap...
  function u (line 1) | function u(e,t){var n=new Error(e);n.data=t,typeof console=="object"&&co...
  function f (line 1) | function f(r){a.packaged=r||e.packaged||n.packaged||u.define&&define.pac...
  function l (line 1) | function l(e){return e.replace(/-(.)/g,function(e,t){return t.toUpperCas...
  function r (line 1) | function r(e){e.on("click",function(t){var n=t.getDocumentPosition(),r=e...
  function i (line 1) | function i(s){var o=r[s];o.processed=!0;for(var u=0;u<o.length;u++){var ...
  function r (line 1) | function r(e){var n=/\w{4}/g;for(var r in e)t.packages[r]=e[r].replace(n...
  function w (line 1) | function w(e){for(var t=n;t<=r;t++)e(i.getLine(t),t)}
  function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
  function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
  function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
  function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
  function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
  function i (line 1) | function i(e,t){this.foldData=e,Array.isArray(t)?this.folds=t:t=this.fol...
  function u (line 1) | function u(e,t){e.row-=t.row,e.row==0&&(e.column-=t.column)}
  function a (line 1) | function a(e,t){u(e.start,t),u(e.end,t)}
  function f (line 1) | function f(e,t){e.row==0&&(e.column+=t.column),e.row+=t.row}
  function l (line 1) | function l(e,t){f(e.start,t),f(e.end,t)}
  function u (line 1) | function u(){this.getFoldAt=function(e,t,n){var r=this.getFoldLine(e);if...
  function s (line 1) | function s(){this.findMatchingBracket=function(e,t){if(e.column==0)retur...
  function m (line 1) | function m(e){return e<4352?!1:e>=4352&&e<=4447||e>=4515&&e<=4519||e>=46...
  function r (line 1) | function r(e){return t?e.action!=="insert":e.action==="insert"}
  function g (line 1) | function g(){var t=0;if(m===0)return t;if(h)for(var n=0;n<e.length;n++){...
  function y (line 1) | function y(t){var n=e.slice(a,t),r=n.length;n.join("").replace(/12/g,fun...
  function o (line 1) | function o(e,t){this.platform=t||(i.isMac?"mac":"win"),this.commands={},...
  function u (line 1) | function u(e,t){o.call(this,e,t),this.$singleCommand=!1}
  function e (line 1) | function e(e){return typeof e=="object"&&e.bindKey&&e.bindKey.position||0}
  function o (line 1) | function o(e,t){return{win:e,mac:t}}
  function e (line 1) | function e(e){return e[e.length-1]}
  function e (line 1) | function e(e){return{action:e.action,start:e.start,end:e.end,lines:e.lin...
  function t (line 1) | function t(e){return{action:e.action,start:e.start,end:e.end,lines:e.lin...
  function n (line 1) | function n(e,t){var n=new Array(e.length);for(var r=0;r<e.length;r++){va...
  function e (line 1) | function e(e,t,n,r){return(e?1:0)|(t?2:0)|(n?4:0)|(r?8:0)}
  function i (line 1) | function i(e,t,n){var i=0,s=0;while(s+e[i].value.length<t){s+=e[i].value...
  function o (line 1) | function o(r){if(n.$themeId!=e)return t&&t();if(!r.cssClass)return;i.imp...
  function s (line 1) | function s(e,t){return e.row==t.row&&e.column==t.column}
  function o (line 1) | function o(e){var t=e.domEvent,n=t.altKey,o=t.shiftKey,u=t.ctrlKey,a=e.g...
  function h (line 1) | function h(e,t,n){return c.$options.wrap=!0,c.$options.needle=t,c.$optio...
  function v (line 1) | function v(e,t){return e.row==t.row&&e.column==t.column}
  function m (line 1) | function m(e){if(e.$multiselectOnSessionChange)return;e.$onAddRange=e.$o...
  function g (line 1) | function g(e){function r(t){n&&(e.renderer.setMouseCursor(""),n=!1)}var ...
  function u (line 1) | function u(e){return a.stringRepeat(" ",e)}
  function f (line 1) | function f(e){return e[2]?u(i)+e[2]+u(s-e[2].length+o)+e[4].replace(/^([...
  function l (line 1) | function l(e){return e[2]?u(i+s-e[2].length)+e[2]+u(o," ")+e[4].replace(...
  function c (line 1) | function c(e){return e[2]?u(i)+e[2]+u(o)+e[4].replace(/^([=:])\s+/,"$1 "...
  function o (line 1) | function o(e){this.session=e,this.session.widgetManager=this,this.sessio...
  function o (line 1) | function o(e,t,n){var r=0,i=e.length-1;while(r<=i){var s=r+i>>1,o=n(t,e[...
  function u (line 1) | function u(e,t,n){var r=e.getAnnotations().sort(s.comparePoints);if(!r.l...

FILE: js/admin/ace/mode-twig.js
  function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
  function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
  function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
  function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...

FILE: js/admin/chosen/chosenImage.jquery.js
  function cssObj (line 37) | function cssObj(imgSrc) {

FILE: js/admin/dc.js
  function _dc (line 19) | function _dc(d3, crossfilter) {

FILE: js/admin/spectrum/spectrum.js
  function contains (line 66) | function contains( str, substr ) {
  function paletteTemplate (line 133) | function paletteTemplate (p, color, className, opts) {
  function hideAll (line 157) | function hideAll() {
  function instanceOptions (line 165) | function instanceOptions(o, callbackContext) {
  function spectrum (line 178) | function spectrum(element, o) {
  function getOffset (line 981) | function getOffset(picker, input) {
  function noop (line 1007) | function noop() {
  function stopPropagation (line 1014) | function stopPropagation(e) {
  function bind (line 1022) | function bind(func, obj) {
  function draggable (line 1034) | function draggable(element, onmove, onstart, onstop) {
  function throttle (line 1121) | function throttle(func, wait, debounce) {
  function inputTypeColorSupport (line 1134) | function inputTypeColorSupport() {
  function inputToRGB (line 1497) | function inputToRGB(color) {
  function rgbToRgb (line 1558) | function rgbToRgb(r, g, b){
  function rgbToHsl (line 1570) | function rgbToHsl(r, g, b) {
  function hslToRgb (line 1601) | function hslToRgb(h, s, l) {
  function rgbToHsv (line 1635) | function rgbToHsv(r, g, b) {
  function hsvToRgb (line 1665) | function hsvToRgb(h, s, v) {
  function rgbToHex (line 1688) | function rgbToHex(r, g, b, allow3Char) {
  function rgbaToHex (line 1707) | function rgbaToHex(r, g, b, a) {
  function desaturate (line 1739) | function desaturate(color, amount) {
  function saturate (line 1747) | function saturate(color, amount) {
  function greyscale (line 1755) | function greyscale(color) {
  function lighten (line 1759) | function lighten (color, amount) {
  function brighten (line 1767) | function brighten(color, amount) {
  function darken (line 1776) | function darken (color, amount) {
  function spin (line 1786) | function spin(color, amount) {
  function complement (line 1798) | function complement(color) {
  function triad (line 1804) | function triad(color) {
  function tetrad (line 1814) | function tetrad(color) {
  function splitcomplement (line 1825) | function splitcomplement(color) {
  function analogous (line 1835) | function analogous(color, results, slices) {
  function monochromatic (line 1850) | function monochromatic(color, results) {
  function flip (line 2131) | function flip(o) {
  function boundAlpha (line 2142) | function boundAlpha(a) {
  function bound01 (line 2153) | function bound01(n, max) {
  function clamp01 (line 2174) | function clamp01(val) {
  function parseIntFromHex (line 2179) | function parseIntFromHex(val) {
  function isOnePointZero (line 2185) | function isOnePointZero(n) {
  function isPercentage (line 2190) | function isPercentage(n) {
  function pad2 (line 2195) | function pad2(c) {
  function convertToPercentage (line 2200) | function convertToPercentage(n) {
  function convertDecimalToHex (line 2209) | function convertDecimalToHex(d) {
  function convertHexToDecimal (line 2213) | function convertHexToDecimal(h) {
  function stringInputToObject (line 2250) | function stringInputToObject(color) {

FILE: js/src/admin.js
  function convert_to_slug (line 42) | function convert_to_slug(string) {
  function auto_fill_form (line 54) | function auto_fill_form(id, title_id) {
  function clean_up_input (line 103) | function clean_up_input() {
  function init_contextual_help_links (line 177) | function init_contextual_help_links() {

FILE: js/src/admin/dashboard_asset_validation.js
  function enable_validation (line 11) | function enable_validation() {

FILE: js/src/admin/dashboard_feed_validation.js
  function enable_validation (line 11) | function enable_validation() {
  function enable_information (line 49) | function enable_information() {

FILE: js/src/admin/episode_asset_settings.js
  function make_asset_list_table_sortable (line 11) | function make_asset_list_table_sortable() {
  function filter_file_formats_by_asset_type (line 56) | function filter_file_formats_by_asset_type() {
  function slugify (line 79) | function slugify(text) {
  function generate_default_episode_asset_title (line 90) | function generate_default_episode_asset_title() {
  function generate_live_preview (line 110) | function generate_live_preview() {

FILE: js/src/admin/feed_settings.js
  function make_feed_list_table_sortable (line 11) | function make_feed_list_table_sortable() {
  function generate_slug_live_preview (line 56) | function generate_slug_live_preview() {
  function generate_title_live_preview (line 62) | function generate_title_live_preview() {
  function manage_redirect_url_display (line 73) | function manage_redirect_url_display() {
  function slugify (line 83) | function slugify(text) {

FILE: js/src/admin/md5.js
  function p (line 14) | function p(a,k,b,h,l,j,m){a=a+(k&b|~k&h)+l+m;return(a<<j|a>>>32-j)+k}
  function m (line 14) | function m(a,k,b,h,l,j,m){a=a+(k&h|b&~h)+l+m;return(a<<j|a>>>32-j)+k}
  function l (line 14) | function l(a,k,b,h,l,j,m){a=a+(k^b^h)+l+m;return(a<<j|a>>>32-j)+k}
  function n (line 14) | function n(a,k,b,h,l,j,m){a=a+(b^(k|~h))+l+m;return(a<<j|a>>>32-j)+k}

FILE: js/src/admin/media.js
  function get_gravatar (line 79) | function get_gravatar(email) {

FILE: js/src/admin/podlove_data_table.js
  function fetch_object (line 30) | function fetch_object(object_id) {
  function add_object_row (line 38) | function add_object_row(object_index, object, entry, initializing) {

FILE: js/src/admin/post_title_autogenerate.js
  function podlove_init_title_override (line 8) | function podlove_init_title_override() {
  function podlove_update_episode_title (line 25) | function podlove_update_episode_title() {

FILE: js/src/admin/timeago.jquery.js
  function substitute (line 96) | function substitute(stringOrFunction, number) {
  function refresh (line 180) | function refresh() {
  function prepareData (line 200) | function prepareData(element) {
  function inWords (line 214) | function inWords(date) {
  function distance (line 218) | function distance(date) {

FILE: js/src/analytics/episode.js
  function render_episode_performance_chart (line 53) | function render_episode_performance_chart(options) {
  function load_episode_performance_chart (line 526) | function load_episode_performance_chart(options) {

FILE: js/src/analytics/totals.js
  function render_episode_performance_chart (line 26) | function render_episode_performance_chart() {
  function load_episode_performance_chart (line 189) | function load_episode_performance_chart() {
  function getLineChart (line 222) | function getLineChart(chart, dimension, group, caption, color) {
  function getBarChart (line 231) | function getBarChart(chart, dimension, group, caption) {
  function aboReduceAdd (line 240) | function aboReduceAdd(key) {
  function aboReduceRemove (line 248) | function aboReduceRemove(key) {
  function aboReduceInit (line 256) | function aboReduceInit(key) {
  function render_abo_total (line 260) | function render_abo_total() {
  function emptyToNull (line 316) | function emptyToNull(value) {
  function load_abo_total (line 320) | function load_abo_total() {

FILE: js/src/lib/guid.js
  function guid (line 1) | function guid() {

FILE: js/src/lib/timestamp.js
  class Timestamp (line 3) | class Timestamp {
    method constructor (line 4) | constructor(totalMs) {
    method totalSeconds (line 8) | get totalSeconds() {
    method totalMinutes (line 12) | get totalMinutes() {
    method totalHours (line 16) | get totalHours() {
    method milliseconds (line 20) | get milliseconds() {
    method seconds (line 24) | get seconds() {
    method minutes (line 28) | get minutes() {
    method hours (line 32) | get hours() {
    method pretty (line 36) | get pretty() {
    method prettyShort (line 40) | get prettyShort() {
    method pad (line 48) | pad(num, pad = "00") {
    method fromString (line 58) | static fromString(t) {

FILE: lib/ajax/ajax.php
  class Ajax (line 9) | class Ajax
    method __construct (line 16) | public function __construct()
    method job_create (line 63) | public function job_create()
    method job_get (line 99) | public function job_get()
    method job_delete (line 114) | public function job_delete()
    method jobs_get (line 138) | public function jobs_get()
    method admin_news (line 189) | public function admin_news()
    method analytics_episode_average_downloads_per_hour (line 196) | public function analytics_episode_average_downloads_per_hour()
    method analytics_downloads_per_day (line 264) | public function analytics_downloads_per_day()
    method analytics_episode_downloads_per_hour (line 315) | public function analytics_episode_downloads_per_hour()
    method analytics_total_downloads_per_day (line 421) | public function analytics_total_downloads_per_day()
    method analytics_settings_tiles_update (line 475) | public static function analytics_settings_tiles_update()
    method analytics_settings_avg_update (line 489) | public static function analytics_settings_avg_update()
    method analytics_csv_episodes_table (line 499) | public static function analytics_csv_episodes_table()
    method respond_with_json (line 522) | public static function respond_with_json($result)
    method analytics_global_assets (line 566) | public static function analytics_global_assets()
    method analytics_global_clients (line 604) | public static function analytics_global_clients()
    method analytics_global_sources (line 641) | public static function analytics_global_sources()
    method analytics_global_systems (line 677) | public static function analytics_global_systems()
    method analytics_global_downloads_per_month (line 715) | public static function analytics_global_downloads_per_month()
    method analytics_global_total_downloads (line 751) | public static function analytics_global_total_downloads()
    method analytics_global_total_downloads_by_show (line 772) | public static function analytics_global_total_downloads_by_show()
    method analytics_global_top_episodes (line 791) | public static function analytics_global_top_episodes()
    method podcast (line 832) | public function podcast()
    method get_new_guid (line 843) | public function get_new_guid()
    method validate_url (line 853) | public function validate_url()
    method update_asset_position (line 880) | public function update_asset_position()
    method update_feed_position (line 898) | public function update_feed_position()
    method hide_teaser (line 916) | public function hide_teaser()
    method banner_hide (line 921) | public function banner_hide()
    method onboarding_acknowledge (line 931) | public function onboarding_acknowledge()
    method get_license_url (line 942) | public function get_license_url()
    method get_license_name (line 947) | public function get_license_name()
    method get_license_parameters_from_url (line 952) | public function get_license_parameters_from_url()
    method analytics_date_condition (line 957) | private static function analytics_date_condition()
    method analytics_date_cache_key (line 976) | private static function analytics_date_cache_key()
    method parse_get_parameter_into_url_array (line 983) | private function parse_get_parameter_into_url_array()

FILE: lib/ajax/file_controller.php
  class FileController (line 7) | class FileController
    method init (line 9) | public static function init()
    method update (line 20) | public static function update()
    method create (line 55) | public static function create()
    method simulate_temporary_episode_slug (line 77) | private static function simulate_temporary_episode_slug($slug)

FILE: lib/ajax/template_controller.php
  class TemplateController (line 8) | class TemplateController
    method init (line 10) | public static function init()
    method activate_network_scope (line 27) | public static function activate_network_scope()
    method get (line 32) | public static function get()
    method update (line 53) | public static function update()
    method create (line 87) | public static function create()
    method delete (line 105) | public static function delete()

FILE: lib/analytics/download_intent_cleanup.php
  class DownloadIntentCleanup (line 10) | class DownloadIntentCleanup
    method init (line 12) | public static function init()
    method schedule_crons (line 19) | public static function schedule_crons()
    method cleanup_download_intents (line 26) | public static function cleanup_download_intents()

FILE: lib/analytics/download_sums_calculator.php
  class DownloadSumsCalculator (line 7) | class DownloadSumsCalculator
    method init (line 9) | public static function init()
    method schedule_crons (line 17) | public static function schedule_crons()
    method calc_hourly_download_sums (line 28) | public static function calc_hourly_download_sums()
    method calc_daily_download_sums (line 35) | public static function calc_daily_download_sums()

FILE: lib/analytics/episode_download_average.php
  class EpisodeDownloadAverage (line 13) | class EpisodeDownloadAverage
    method init (line 17) | public static function init()
    method schedule_crons (line 24) | public static function schedule_crons()
    method recalculate_episode_download_average (line 31) | public static function recalculate_episode_download_average()
    method get_downloads_per_hour_for_episode (line 62) | private static function get_downloads_per_hour_for_episode($episode_id)
    method add_missing_hours (line 109) | private static function add_missing_hours($data, $release_date)

FILE: lib/analytics/salt_shaker.php
  class SaltShaker (line 10) | class SaltShaker
    method init (line 12) | public static function init()
    method schedule_crons (line 19) | public static function schedule_crons()
    method cleanup_download_intents (line 27) | public static function cleanup_download_intents()

FILE: lib/api/error.php
  class ForbiddenAccess (line 5) | class ForbiddenAccess extends \WP_Error
    method __construct (line 13) | public function __construct($code = '', $message = '')
  class NotFound (line 25) | class NotFound extends \WP_Error
    method __construct (line 33) | public function __construct($code = '', $message = '')
  class NotFoundEpisode (line 45) | class NotFoundEpisode extends \WP_Error
    method __construct (line 52) | public function __construct($episode_id)
  class NotSupported (line 59) | class NotSupported extends \WP_Error
    method __construct (line 67) | public function __construct($code = '', $message = '')
  class ArgumentError (line 79) | class ArgumentError extends \WP_Error
    method __construct (line 87) | public function __construct($code = '', $message = '')
  class InternalServerError (line 99) | class InternalServerError extends \WP_Error
    method __construct (line 107) | public function __construct($code = '', $message = '')

FILE: lib/api/permissions.php
  class Permissons (line 5) | class Permissons
    method authorization_status_code (line 7) | public static function authorization_status_code()

FILE: lib/api/response.php
  class OkResponse (line 5) | class OkResponse extends \WP_REST_Response
    method __construct (line 12) | public function __construct($data = null, array $headers = [])
  class CreateResponse (line 18) | class CreateResponse extends \WP_REST_Response
    method __construct (line 25) | public function __construct($data = null, array $headers = [])

FILE: lib/api/validation.php
  class Validation (line 11) | class Validation
    method timestamp (line 13) | public static function timestamp($param, $request, $key)
    method url (line 27) | public static function url($param, $request, $key)
    method episodeCover (line 40) | public static function episodeCover($param, $request, $key)
    method maxLength255 (line 57) | public static function maxLength255($param, $request, $key)
    method chapters (line 68) | public static function chapters($param, $request, $key)
    method isContributorIdExist (line 98) | public static function isContributorIdExist($param, $request, $key)
    method isContributorGroupIdExist (line 111) | public static function isContributorGroupIdExist($param, $request, $key)
    method isContributorRoleIdExist (line 124) | public static function isContributorRoleIdExist($param, $request, $key)
    method isContributorDefaultIdExist (line 137) | public static function isContributorDefaultIdExist($param, $request, $...

FILE: lib/authentication.php
  class Authentication (line 14) | class Authentication
    method application_password (line 20) | public static function application_password()

FILE: lib/cache/http_header_validator.php
  class HttpHeaderValidator (line 16) | class HttpHeaderValidator
    method __construct (line 24) | public function __construct($url, $etag = null, $last_modified = null)
    method validate (line 31) | public function validate()
    method hasChanged (line 60) | public function hasChanged()

FILE: lib/cache/template_cache.php
  class TemplateCache (line 30) | class TemplateCache
    method __construct (line 43) | protected function __construct()
    method __clone (line 50) | private function __clone() {}
    method get_instance (line 57) | public static function get_instance()
    method is_enabled (line 66) | public static function is_enabled()
    method handle_model_change (line 71) | public function handle_model_change($model)
    method taint (line 91) | public function taint()
    method maybe_purge (line 96) | public function maybe_purge()
    method setup_purge (line 106) | public function setup_purge()
    method setup_purge_in_all_blogs (line 116) | public function setup_purge_in_all_blogs()
    method setup_global_purge (line 128) | public function setup_global_purge()
    method purge (line 150) | public function purge()
    method cache_for (line 170) | public function cache_for($cache_key, $callback, $expiration = DAY_IN_...
    method delete_cache_for (line 190) | public function delete_cache_for($cache_key)
    method expiration_for (line 197) | public function expiration_for($cache_key)
    method memorize_cache_key (line 204) | private function memorize_cache_key($cache_key)
    method generate_cache_key (line 233) | private function generate_cache_key($cache_key)

FILE: lib/chapters_manager.php
  class ChaptersManager (line 13) | class ChaptersManager
    method __construct (line 19) | public function __construct(Model\Episode $episode)
    method get (line 31) | public function get($format = 'object')
    method get_raw_chapters_string (line 63) | private function get_raw_chapters_string()
    method get_chapters_object (line 92) | private function get_chapters_object()

FILE: lib/comment/comment.php
  class Comment (line 5) | class Comment
    method __construct (line 17) | public function __construct($comment)
    method parse (line 22) | public function parse()
    method getTitle (line 49) | public function getTitle()
    method getDescription (line 59) | public function getDescription()
    method getTags (line 71) | public function getTags($tagName = null)
    method getTag (line 91) | public function getTag($tagName)
    method assert (line 96) | private function assert($condition, $message = '')
    method removeFirstLine (line 101) | private function removeFirstLine($c)
    method removeLastLine (line 109) | private function removeLastLine($c)
    method removeLeadingStars (line 117) | private function removeLeadingStars($c)
    method removeOneLeadingWhitespace (line 125) | private function removeOneLeadingWhitespace($c)
    method extractTags (line 132) | private function extractTags()
    method extractDescription (line 156) | private function extractDescription()

FILE: lib/cron.php
  function unschedule_events (line 10) | function unschedule_events($hook)

FILE: lib/custom_guid.php
  class Custom_Guid (line 11) | class Custom_Guid
    method init (line 16) | public static function init()
    method meta_box (line 25) | public static function meta_box()
    method meta_box_callback (line 39) | public static function meta_box_callback()
    method save_form (line 87) | public static function save_form($post_id, $form_data)
    method generate_guid_for_episodes (line 102) | public static function generate_guid_for_episodes($post_id, $post)
    method guid_for_post (line 123) | public static function guid_for_post($post)
    method override_wordpress_guid (line 138) | public static function override_wordpress_guid($guid, $post_id = null)
    method find_duplicate_guids (line 147) | public static function find_duplicate_guids()

FILE: lib/delete_head_requests.php
  class DeleteHeadRequests (line 10) | class DeleteHeadRequests
    method init (line 12) | public static function init()
    method ajax_delete (line 22) | public static function ajax_delete()
    method show_admin_notice (line 86) | public static function show_admin_notice()

FILE: lib/dom_document_fragment.php
  class DomDocumentFragment (line 15) | class DomDocumentFragment extends \DOMDocument
    method __construct (line 17) | public function __construct($version = '1.0', $encoding = 'UTF-8')
    method __toString (line 22) | public function __toString()

FILE: lib/downloads.php
  class Downloads (line 5) | class Downloads
    method init (line 10) | public static function init()
    method add_column_to_episodes_table (line 43) | public static function add_column_to_episodes_table($columns)
    method add_column_content_to_episodes_table (line 54) | public static function add_column_content_to_episodes_table($column_name)

FILE: lib/downloads_list_data.php
  class Downloads_List_Data (line 5) | class Downloads_List_Data
    method get_columns (line 7) | public static function get_columns()
    method get_data (line 31) | public static function get_data($orderby = 'post_date', $order = 'desc')

FILE: lib/downloads_list_table.php
  class Downloads_List_Table (line 8) | class Downloads_List_Table extends \Podlove\List_Table
    method __construct (line 10) | public function __construct()
    method column_episode (line 22) | public function column_episode($episode)
    method column_downloads (line 35) | public function column_downloads($episode)
    method column_cb (line 40) | public function column_cb($item)
    method column_default (line 50) | public function column_default($item, $column_name)
    method get_number_or_dash (line 71) | public static function get_number_or_dash($value)
    method get_columns (line 80) | public function get_columns()
    method get_sortable_columns (line 105) | public function get_sortable_columns()
    method aggregation_columns (line 129) | public static function aggregation_columns()
    method single_row (line 137) | public function single_row($item)
    method prepare_items (line 152) | public function prepare_items()
    method extra_tablenav (line 185) | protected function extra_tablenav($which)
    method data_age (line 215) | private function data_age()

FILE: lib/duplicate_post.php
  class DuplicatePost (line 8) | class DuplicatePost
    method init (line 10) | public static function init()
    method meta_keys_filter (line 20) | public static function meta_keys_filter($keys)
    method regenerate_guid (line 30) | public static function regenerate_guid($new_post_id, $old_post_object)
    method clone_contributors (line 36) | public static function clone_contributors($new_post_id, $old_post_object)

FILE: lib/duration.php
  class Duration (line 10) | class Duration
    method __construct (line 26) | public function __construct($duration)
    method get (line 41) | public function get($format = 'full')
    method format (line 116) | public function format($hours = true, $minutes = true, $seconds = true...
    method normalize (line 145) | private function normalize()
  function rfill (line 167) | function rfill($string, $length, $fillchar = ' ')
  function lfill (line 185) | function lfill($string, $length, $fillchar = ' ')

FILE: lib/episode_asset_list_table.php
  class Episode_Asset_List_Table (line 5) | class Episode_Asset_List_Table extends \Podlove\List_Table
    method __construct (line 7) | public function __construct()
    method column_title (line 19) | public function column_title($episode_asset)
    method column_file_type (line 47) | public function column_file_type($episode_asset)
    method column_downloadable (line 54) | public function column_downloadable($episode_asset)
    method column_move (line 59) | public function column_move($episode_asset)
    method get_columns (line 64) | public function get_columns()
    method prepare_items (line 74) | public function prepare_items()

FILE: lib/feed_list_table.php
  class Feed_List_Table (line 7) | class Feed_List_Table extends \Podlove\List_Table
    method __construct (line 9) | public function __construct()
    method column_name (line 21) | public function column_name($feed)
    method column_limit (line 36) | public function column_limit($feed)
    method column_discoverable (line 61) | public function column_discoverable($feed)
    method column_protected (line 66) | public function column_protected($feed)
    method column_url (line 71) | public function column_url($feed)
    method column_media (line 87) | public function column_media($feed)
    method column_move (line 94) | public function column_move($feed)
    method get_columns (line 99) | public function get_columns()
    method prepare_items (line 113) | public function prepare_items()

FILE: lib/feeds.php
  function register_podcast_feeds (line 14) | function register_podcast_feeds()
  function handle_feed_proxy_redirects (line 31) | function handle_feed_proxy_redirects()
  function is_page_in_feed (line 56) | function is_page_in_feed()
  function get_canonical_feed_url (line 68) | function get_canonical_feed_url()
  function is_debug_view (line 88) | function is_debug_view()
  function should_redirect_to_proxy (line 98) | function should_redirect_to_proxy()
  function get_feed (line 118) | function get_feed()
  function maybe_redirect_to_canonical_url (line 137) | function maybe_redirect_to_canonical_url()
  function generate_podcast_feed (line 166) | function generate_podcast_feed()
  function check_for_and_do_compression (line 173) | function check_for_and_do_compression($content_type = 'application/rss+x...
  function remove_podPress_hooks (line 232) | function remove_podPress_hooks()
  function remove_powerPress_hooks (line 247) | function remove_powerPress_hooks()

FILE: lib/feeds/base.php
  function get_description (line 7) | function get_description()
  function override_feed_title (line 28) | function override_feed_title($feed)
  function override_feed_description (line 35) | function override_feed_description($feed)
  function override_feed_language (line 52) | function override_feed_language($feed)
  function get_episode_title (line 61) | function get_episode_title($post = 0)
  function prepare_for_feed (line 79) | function prepare_for_feed($content)
  function strip_style_tags_from_feed_content (line 84) | function strip_style_tags_from_feed_content($content)
  function get_optimized_content_encoded_allowed_html (line 89) | function get_optimized_content_encoded_allowed_html()
  function prepare_content_encoded (line 110) | function prepare_content_encoded($content, $optimize = false)
  function get_xml_text_node (line 121) | function get_xml_text_node($tag_name, $content)
  function get_xml_cdata_node (line 131) | function get_xml_cdata_node($tag_name, $content)
  function get_xml_cdata_text (line 141) | function get_xml_cdata_text($content)
  function get_xml_itunesimage_node (line 149) | function get_xml_itunesimage_node($url)
  function get_xml_podcast_funding_node (line 164) | function get_xml_podcast_funding_node($url, $label)
  function get_xml_podcast_license_node (line 181) | function get_xml_podcast_license_node($identifier, $url)
  function add_itunes_category (line 198) | function add_itunes_category($category_html, $categories, $category_id)
  function override_feed_head (line 232) | function override_feed_head($hook, $podcast, $feed, $format)
  function override_feed_entry (line 380) | function override_feed_entry($hook, $podcast, $feed, $format)

FILE: lib/feeds/chapters.php
  class Chapters (line 10) | class Chapters
    method __construct (line 14) | public function __construct(Model\Episode $episode)
    method render (line 24) | public function render($style = 'link')
    method render_inline (line 29) | public function render_inline()
    method render_link (line 34) | public function render_link()

FILE: lib/feeds/rss.php
  class RSS (line 9) | class RSS
    method prepare_feed (line 11) | public static function prepare_feed($feed_slug)
    method wp_404 (line 161) | public static function wp_404()
    method render (line 172) | public static function render()

FILE: lib/file_type_list_table.php
  class File_Type_List_Table (line 5) | class File_Type_List_Table extends \Podlove\List_Table
    method __construct (line 7) | public function __construct()
    method column_name (line 19) | public function column_name($file_type)
    method column_id (line 24) | public function column_id($file_type)
    method column_file_type (line 29) | public function column_file_type($file_type)
    method column_mime (line 34) | public function column_mime($file_type)
    method column_extension (line 39) | public function column_extension($file_type)
    method get_columns (line 44) | public function get_columns()
    method prepare_items (line 55) | public function prepare_items()

FILE: lib/form/input/builder.php
  class Builder (line 5) | class Builder
    method __construct (line 29) | public function __construct($object, $context)
    method get_field_name (line 35) | public function get_field_name()
    method get_field_id (line 40) | public function get_field_id()
    method get_extra_html_attributes (line 53) | public function get_extra_html_attributes()
    method string (line 68) | public function string($object_key, $arguments)
    method color (line 77) | public function color($object_key, $arguments)
    method hidden (line 86) | public function hidden($object_key, $arguments)
    method password (line 95) | public function password($object_key, $arguments)
    method text (line 104) | public function text($object_key, $arguments)
    method checkbox (line 113) | public function checkbox($object_key, $arguments)
    method select (line 121) | public function select($object_key, $arguments)
    method multiselect (line 147) | public function multiselect($object_key, $arguments)
    method radio (line 190) | public function radio($object_key, $arguments)
    method image (line 200) | public function image($object_key, $arguments)
    method upload (line 230) | public function upload($object_key, $arguments)
    method callback (line 270) | public function callback($object_key, $arguments)
    method fields_for (line 283) | public function fields_for($object, $args, $callback)
    method build_input_values (line 297) | private function build_input_values($object_key, $arguments)

FILE: lib/form/input/div_wrapper.php
  class DivWrapper (line 5) | class DivWrapper extends Wrapper
    method do_template (line 7) | public function do_template($object_key, $field_name, $field_id, $fiel...
    method subheader (line 26) | public function subheader($title, $description = '')

FILE: lib/form/input/table_wrapper.php
  class TableWrapper (line 5) | class TableWrapper extends Wrapper
    method do_template (line 7) | public function do_template($object_key, $field_name, $field_id, $fiel...
    method subheader (line 29) | public function subheader($title, $description = '')

FILE: lib/form/input/wrapper.php
  class Wrapper (line 5) | abstract class Wrapper
    method __construct (line 14) | public function __construct($builder)
    method __call (line 25) | public function __call($name, $arguments = [])
    method do_template (line 58) | abstract public function do_template($object_key, $field_name, $field_...
    method subheader (line 60) | abstract public function subheader($title, $description = '');

FILE: lib/geo_ip.php
  class Geo_Ip (line 5) | class Geo_Ip
    method init (line 14) | public static function init()
    method register_updater_cron (line 23) | public static function register_updater_cron()
    method stop_updater_cron (line 30) | public static function stop_updater_cron()
    method cron_add_monthly (line 35) | public static function cron_add_monthly($schedules)
    method is_enabled (line 54) | public static function is_enabled()
    method disable_tracking (line 61) | public static function disable_tracking()
    method enable_tracking (line 66) | public static function enable_tracking()
    method is_db_valid (line 71) | public static function is_db_valid()
    method get_upload_file_path (line 82) | public static function get_upload_file_path()
    method get_upload_file_dir (line 87) | public static function get_upload_file_dir()
    method update_database (line 94) | public static function update_database()

FILE: lib/has_page_documentation_trait.php
  type HasPageDocumentationTrait (line 34) | trait HasPageDocumentationTrait
    method init_page_documentation (line 36) | public function init_page_documentation($pagehook)
    method add_help_tabs (line 41) | public function add_help_tabs()
    method help_file (line 60) | public static function help_file()
    method inheriting_class_file (line 72) | public static function inheriting_class_file()
    method get_help_tabs (line 79) | private function get_help_tabs()

FILE: lib/helper.php
  function load_template (line 5) | function load_template($path, $vars = [])
  function maybe_encode_emoji (line 27) | function maybe_encode_emoji($string)
  function esc_like (line 43) | function esc_like($text)
  function format_bytes (line 48) | function format_bytes($size, $decimals = 2)
  function get_blog_prefix (line 62) | function get_blog_prefix()
  function get_help_link (line 73) | function get_help_link($tab_id, $title = '<sup>?</sup>')
  function is_image (line 86) | function is_image($file, $filename = '')
  function get_image_type (line 114) | function get_image_type($file)
  function get_image_mime_type (line 124) | function get_image_mime_type($image_type)
  function get_setting_defaults (line 129) | function get_setting_defaults()
  function get_setting (line 184) | function get_setting($namespace, $name)
  function save_setting (line 198) | function save_setting($namespace, $name, $values)
  function is_options_save_page (line 213) | function is_options_save_page()
  function is_podlove_settings_screen (line 226) | function is_podlove_settings_screen()
  function is_episode_edit_screen (line 238) | function is_episode_edit_screen()
  function get_landing_page_url (line 252) | function get_landing_page_url()
  function get_webplayer_defaults (line 287) | function get_webplayer_defaults()
  function get_webplayer_settings (line 314) | function get_webplayer_settings()
  function get_webplayer_setting (line 322) | function get_webplayer_setting($name)
  function slugify (line 328) | function slugify($slug)
  function prepare_episode_slug_for_url (line 340) | function prepare_episode_slug_for_url($slug)
  function with_blog_scope (line 349) | function with_blog_scope($blog_id, $callback)
  function relative_time_steps (line 364) | function relative_time_steps($time)
  function episode_types (line 389) | function episode_types()
  function download_external_image_to_media (line 398) | function download_external_image_to_media($url, $name, $curl_args = [])
  function build_for (line 469) | function build_for($object, $args, $callback)
  function version_per_country_cc (line 545) | function version_per_country_cc()
  function locales_cc (line 614) | function locales_cc()
  function locales (line 685) | function locales()
  function categories (line 907) | function categories($prefix_subcategories = true)

FILE: lib/http/curl.php
  class Curl (line 10) | class Curl
    method __construct (line 20) | public function __construct()
    method request (line 25) | public function request($url, $params = [])
    method isSuccessful (line 58) | public function isSuccessful()
    method get_response (line 66) | public function get_response()
    method user_agent (line 76) | public static function user_agent()
    method resolve_redirects (line 103) | public static function resolve_redirects($url, $maximum_redirects = 5)
    method curl_can_follow_redirects (line 142) | public static function curl_can_follow_redirects()

FILE: lib/jobs/counting_job.php
  class CountingJob (line 11) | class CountingJob
    method setup (line 15) | public function setup()
    method title (line 20) | public static function title()
    method description (line 25) | public static function description()
    method defaults (line 30) | public static function defaults()
    method get_total_steps (line 39) | public function get_total_steps()
    method do_step (line 44) | protected function do_step()

FILE: lib/jobs/cron_job_runner.php
  class CronJobRunner (line 17) | class CronJobRunner
    method init (line 24) | public static function init()
    method add_cron_schedules (line 34) | public static function add_cron_schedules($schedules)
    method create_job (line 50) | public static function create_job($job_class, $args = [])
    method work_jobs (line 72) | public static function work_jobs($ignore_lock = false)
    method max_seconds_per_request (line 109) | public static function max_seconds_per_request()
    method lock_duration_buffer (line 127) | public static function lock_duration_buffer()
    method run_job (line 134) | public static function run_job($job_id, $spawn_time)
    method should_run_another_job (line 192) | public static function should_run_another_job()
    method is_process_running (line 205) | protected static function is_process_running()
    method lock_process (line 221) | protected static function lock_process()
    method unlock_process (line 235) | protected static function unlock_process()

FILE: lib/jobs/download_intent_cleanup_job.php
  class DownloadIntentCleanupJob (line 7) | class DownloadIntentCleanupJob
    method setup (line 11) | public function setup()
    method title (line 17) | public static function title()
    method description (line 22) | public static function description()
    method mode (line 27) | public static function mode($args)
    method init_job (line 36) | public function init_job()
    method defaults (line 46) | public static function defaults()
    method get_max_intent_id (line 55) | public static function get_max_intent_id()
    method get_max_clean_intent_id (line 64) | public static function get_max_clean_intent_id()
    method get_total_steps (line 73) | public function get_total_steps()
    method purge_cache (line 78) | public static function purge_cache()
    method do_step (line 83) | protected function do_step()

FILE: lib/jobs/download_timed_aggregator_job.php
  class DownloadTimedAggregatorJob (line 10) | class DownloadTimedAggregatorJob
    method setup (line 14) | public function setup()
    method title (line 19) | public static function title()
    method description (line 24) | public static function description()
    method mode (line 29) | public static function mode($args)
    method defaults (line 38) | public static function defaults()
    method setup_state (line 45) | public function setup_state()
    method get_total_steps (line 58) | public function get_total_steps()
    method current_time_group (line 74) | public static function current_time_group($item)
    method get_hidden_groups_key (line 111) | public static function get_hidden_groups_key()
    method get_hidden_groups (line 116) | public static function get_hidden_groups()
    method groupings (line 121) | public static function groupings()
    method do_step (line 144) | protected function do_step()
    method should_calculate_grouping (line 168) | private function should_calculate_grouping($episode_id, $group_key, $g...
    method calculate_single_aggregation (line 189) | private function calculate_single_aggregation($episode, $grouping_key,...

FILE: lib/jobs/job_cleaner.php
  class JobCleaner (line 7) | class JobCleaner
    method init (line 9) | public static function init()
    method podlove_jobs_clean (line 18) | public static function podlove_jobs_clean()

FILE: lib/jobs/job_trait.php
  type JobTrait (line 7) | trait JobTrait
    method __construct (line 16) | public function __construct($args = [], $job = null)
    method setup (line 28) | public function setup() {}
    method title (line 35) | public static function title()
    method mode (line 49) | public static function mode($args)
    method description (line 59) | public static function description()
    method defaults (line 69) | public static function defaults()
    method is_unique (line 81) | public static function is_unique()
    method init (line 93) | public function init()
    method get_job_id (line 117) | public function get_job_id()
    method is_finished (line 122) | public function is_finished()
    method save_job (line 127) | public function save_job()
    method get_job (line 141) | public function get_job()
    method get_total_steps (line 151) | abstract public function get_total_steps();
    method step (line 156) | public function step()
    method get_status_percent (line 171) | protected function get_status_percent()
    method get_status_text (line 180) | protected function get_status_text()
    method do_step (line 197) | abstract protected function do_step();
    method log_active_run_time (line 199) | private function log_active_run_time($time_ms)

FILE: lib/jobs/request_id_rehash_job.php
  class RequestIdRehashJob (line 5) | class RequestIdRehashJob
    method title (line 9) | public static function title()
    method description (line 14) | public static function description()
    method defaults (line 19) | public static function defaults()
    method get_total_steps (line 27) | public function get_total_steps()
    method do_step (line 32) | protected function do_step()
    method downloads_table_name (line 48) | private static function downloads_table_name()

FILE: lib/jobs/tools_section.php
  class ToolsSection (line 5) | class ToolsSection
    method init (line 7) | public static function init()
    method render_jobs_overview (line 17) | public static function render_jobs_overview()

FILE: lib/jobs/tools_section_cron_diagnostics.php
  class ToolsSectionCronDiagnostics (line 5) | class ToolsSectionCronDiagnostics
    method init (line 7) | public static function init()
    method diagnosis_start (line 16) | public static function diagnosis_start()
    method diagnosis_check (line 33) | public static function diagnosis_check()
    method register_cron_executed (line 52) | public static function register_cron_executed()
    method view (line 57) | public static function view()

FILE: lib/jobs/user_agent_refresh_job.php
  class UserAgentRefreshJob (line 8) | class UserAgentRefreshJob
    method setup (line 12) | public function setup()
    method title (line 21) | public static function title()
    method description (line 26) | public static function description()
    method defaults (line 31) | public static function defaults()
    method get_total_steps (line 39) | public function get_total_steps()
    method delete_bots_from_clean_downloadintents (line 44) | public static function delete_bots_from_clean_downloadintents()
    method do_step (line 55) | protected function do_step()

FILE: lib/list_table.php
  class List_Table (line 12) | class List_Table extends \WP_List_Table
    method no_items (line 19) | public function no_items()
    method no_items_content (line 28) | public function no_items_content()

FILE: lib/log.php
  class Log (line 27) | class Log
    method __construct (line 32) | private function __construct()
    method __call (line 48) | public function __call($name, $arguments)
    method __clone (line 70) | public function __clone()
    method __wakeup (line 75) | public function __wakeup()
    method get (line 80) | public static function get()
    method get_log_level (line 89) | public function get_log_level()
    method is_debug_enabled (line 98) | public function is_debug_enabled()

FILE: lib/model/asset_assignment.php
  class AssetAssignment (line 8) | class AssetAssignment
    method __construct (line 24) | protected function __construct()
    method __clone (line 29) | private function __clone() {}
    method __set (line 31) | public function __set($name, $value)
    method __get (line 40) | public function __get($name)
    method get_instance (line 52) | public static function get_instance()
    method has_property (line 64) | public function has_property($name)
    method property_names (line 74) | public function property_names()
    method property (line 86) | public static function property($name)
    method save (line 98) | public function save()
    method full_title (line 110) | public function full_title()
    method set_property (line 121) | private function set_property($name, $value)
    method get_property (line 126) | private function get_property($name)
    method properties (line 140) | private function properties()
    method fetch (line 152) | private function fetch()

FILE: lib/model/base.php
  class Base (line 5) | abstract class Base
    method __set (line 21) | public function __set($name, $value)
    method __get (line 30) | public function __get($name)
    method __callStatic (line 44) | public static function __callStatic($name, $arguments)
    method property (line 84) | public static function property($name, $type, $args = [])
    method has_property (line 115) | public static function has_property($name)
    method property_names (line 125) | public static function property_names()
    method has_entries (line 137) | public static function has_entries()
    method count (line 147) | public static function count()
    method find_by_id (line 156) | public static function find_by_id($id)
    method cache_key (line 177) | public static function cache_key($id)
    method find_all_by_property (line 182) | public static function find_all_by_property($property, $value)
    method find_one_by_property (line 189) | public static function find_one_by_property($property, $value)
    method find_all_by_where (line 196) | public static function find_all_by_where($where)
    method find_one_by_where (line 203) | public static function find_one_by_where($where)
    method first (line 215) | public static function first()
    method last (line 222) | public static function last()
    method all (line 236) | public static function all($sql_suffix = '')
    method is_new (line 246) | public function is_new()
    method flag_as_not_new (line 251) | public function flag_as_not_new()
    method update_attributes (line 265) | public function update_attributes($attributes)
    method update_attribute (line 296) | public function update_attribute($attribute, $value)
    method create (line 315) | public static function create($attributes = [])
    method save (line 334) | public function save()
    method default_values (line 379) | public function default_values()
    method delete (line 384) | public function delete()
    method build (line 409) | public static function build()
    method build_indices (line 436) | public static function build_indices()
    method table_name (line 464) | public static function table_name()
    method table_exists (line 471) | public static function table_exists()
    method name (line 482) | public static function name()
    method destroy (line 495) | public static function destroy()
    method delete_all (line 501) | public static function delete_all($reset_autoincrement = true)
    method find_one_by_sql (line 511) | public static function find_one_by_sql($sql)
    method find_all_by_sql (line 532) | public static function find_all_by_sql($sql)
    method to_array (line 557) | public function to_array()
    method set_property (line 570) | private function set_property($name, $value)
    method get_property (line 575) | private function get_property($name)
    method properties (line 589) | private static function properties()
    method set_defaults (line 605) | private function set_defaults()
    method property_name_to_sql_update_statement (line 621) | private function property_name_to_sql_update_statement($p)
    method property_name_to_sql_value (line 632) | private function property_name_to_sql_value($p)

FILE: lib/model/download_intent.php
  class DownloadIntent (line 11) | class DownloadIntent extends Base
    method add_geo_data (line 13) | public function add_geo_data($ip_string)

FILE: lib/model/download_intent_clean.php
  class DownloadIntentClean (line 8) | class DownloadIntentClean extends Base
    method build (line 10) | public static function build()
    method episode_age_in_hours (line 32) | public static function episode_age_in_hours($episode_id)
    method actual_episode_age_in_hours (line 51) | public static function actual_episode_age_in_hours($episode_id)
    method top_episode_ids (line 66) | public static function top_episode_ids($start, $end = 'now', $limit = 3)
    method peak_download_by_episode_id (line 99) | public static function peak_download_by_episode_id($episode_id)
    method total_by_episode_id (line 122) | public static function total_by_episode_id($episode_id, $start = null,...
    method prev_month_downloads (line 142) | public static function prev_month_downloads()
    method last_7days_downloads (line 174) | public static function last_7days_downloads()
    method last_24hours_downloads (line 183) | public static function last_24hours_downloads()
    method total_downloads (line 192) | public static function total_downloads()
    method total_downloads_by_show (line 206) | public static function total_downloads_by_show($where)
    method sql_condition_from_time_strings (line 245) | private static function sql_condition_from_time_strings($start = null,...

FILE: lib/model/episode.php
  class Episode (line 11) | class Episode extends Base implements Licensable
    method __construct (line 15) | public function __construct()
    method find_all_by_time (line 20) | public static function find_all_by_time($args = [])
    method count_published (line 58) | public static function count_published()
    method latest (line 76) | public static function latest()
    method days_since_release (line 102) | public function days_since_release()
    method hours_since_release (line 112) | public function hours_since_release()
    method title (line 120) | public function title()
    method title_with_fallback (line 130) | public function title_with_fallback()
    method post_title (line 139) | public function post_title()
    method full_title (line 153) | public function full_title()
    method number_padded (line 164) | public function number_padded()
    method description (line 174) | public function description()
    method slug (line 194) | public function slug(): string
    method post (line 199) | public function post()
    method permalink (line 206) | public function permalink()
    method meta (line 213) | public function meta($meta_key, $single = true)
    method tags (line 220) | public function tags($args = [])
    method categories (line 227) | public function categories($args = [])
    method explicit_text (line 237) | public function explicit_text()
    method media_files (line 247) | public function media_files($args = [])
    method find_or_create_by_post_id (line 267) | public static function find_or_create_by_post_id($post_id)
    method enclosure_url (line 282) | public function enclosure_url($episode_asset, $source = 'feed', $conte...
    method cover_art_with_fallback (line 287) | public function cover_art_with_fallback()
    method cover_art (line 298) | public function cover_art()
    method get_chapters (line 351) | public function get_chapters($format = 'object')
    method refetch_files (line 358) | public function refetch_files()
    method get_duration (line 377) | public function get_duration($format = 'HH:MM:SS')
    method delete_caches (line 385) | public function delete_caches()
    method is_valid (line 411) | public function is_valid()
    method is_published (line 432) | public function is_published()
    method get_license (line 441) | public function get_license()
    method get_license_picture_url (line 449) | public function get_license_picture_url()
    method get_license_html (line 454) | public function get_license_html()
    method get_soundbite_start (line 459) | public function get_soundbite_start($format = 'HH:MM:SS')
    method get_soundbite_duration (line 464) | public function get_soundbite_duration($format = 'HH:MM:SS')
    method get_next_episode_number (line 469) | public static function get_next_episode_number($show_slug = null)
    method is_slug_frozen (line 504) | public function is_slug_frozen()
    method freeze_slug (line 512) | public function freeze_slug()
    method unfreeze_slug (line 523) | public function unfreeze_slug()

FILE: lib/model/episode_asset.php
  class EpisodeAsset (line 5) | class EpisodeAsset extends Base
    method __construct (line 9) | public function __construct()
    method save (line 14) | public function save()
    method file_type (line 33) | public function file_type()
    method media_files (line 45) | public function media_files()
    method active_media_files (line 59) | public function active_media_files()
    method title (line 70) | public function title()
    method is_connected_to_web_player (line 84) | public function is_connected_to_web_player()
    method maybe_connect_to_web_player (line 100) | public function maybe_connect_to_web_player()
    method is_connected_to_feed (line 130) | public function is_connected_to_feed()
    method has_active_media_files (line 142) | public function has_active_media_files()
    method has_asset_assignments (line 152) | public function has_asset_assignments()
    method is_deletable (line 170) | public function is_deletable()
    method delete (line 181) | public function delete()

FILE: lib/model/feed.php
  class Feed (line 7) | class Feed extends Base
    method __construct (line 15) | public function __construct()
    method save (line 20) | public function save()
    method get_subscribe_url (line 43) | public function get_subscribe_url($taxonomy = null, $term_id = null)
    method get_contextual_subscribe_url (line 61) | public function get_contextual_subscribe_url()
    method get_subscribe_link (line 80) | public function get_subscribe_link()
    method get_redirect_url (line 87) | public function get_redirect_url()
    method get_redirect_http_status_code (line 96) | public function get_redirect_http_status_code()
    method is_redirect_enabled (line 106) | public function is_redirect_enabled()
    method get_title (line 114) | public function get_title()
    method title_for_discovery (line 136) | public function title_for_discovery()
    method episode_asset (line 164) | public function episode_asset()
    method post_ids (line 176) | public function post_ids()
    method get_content_type (line 209) | public function get_content_type()
    method get_feed_self_link (line 214) | public function get_feed_self_link()
    method get_alternate_links (line 232) | public function get_alternate_links()
    method get_link_tag (line 250) | public static function get_link_tag($args = [])
    method get_post_limit_sql (line 282) | public function get_post_limit_sql($posts_per_page = false)
    method find_duplicate_slugs (line 312) | public static function find_duplicate_slugs()

FILE: lib/model/file_type.php
  class FileType (line 5) | class FileType extends Base
    method title (line 7) | public function title()
    method get_types (line 12) | public static function get_types()

FILE: lib/model/geo_area.php
  class GeoArea (line 5) | class GeoArea extends Base {}

FILE: lib/model/geo_area_name.php
  class GeoAreaName (line 5) | class GeoAreaName extends Base {}

FILE: lib/model/image.php
  class Image (line 27) | class Image
    method __construct (line 54) | public function __construct($url, $file_name = '')
    method source_url (line 78) | public function source_url()
    method cache_dir (line 83) | public static function cache_dir()
    method flush_cache (line 91) | public static function flush_cache()
    method setCrop (line 118) | public function setCrop($crop)
    method setWidth (line 125) | public function setWidth($width)
    method setHeight (line 136) | public function setHeight($height)
    method setRetina (line 147) | public function setRetina($retina)
    method url (line 163) | public function url()
    method image (line 254) | public function image($args = [])
    method file_name (line 293) | public function file_name($size_slug)
    method source_exists (line 302) | public function source_exists()
    method original_file (line 307) | public function original_file()
    method resized_file (line 312) | public function resized_file()
    method generate_resized_copy (line 317) | public function generate_resized_copy()
    method redownload_source (line 374) | public function redownload_source()
    method download_source (line 380) | public function download_source()
    method create_basedir (line 456) | public function create_basedir()
    method move_as_original_file (line 468) | public function move_as_original_file($file)
    method copy_as_original_file (line 482) | public function copy_as_original_file($file)
    method download_url (line 510) | public static function download_url($url, $timeout = 300, $extra_args ...
    method srcset (line 552) | private function srcset()
    method cache_file (line 596) | private function cache_file()
    method original_url (line 601) | private function original_url()
    method resized_url (line 606) | private function resized_url()
    method size_slug (line 611) | private function size_slug()
    method delete_resized_versions (line 622) | private function delete_resized_versions()
    method add_donotbackup_dotfile (line 628) | private function add_donotbackup_dotfile()
    method save_cache_data (line 641) | private function save_cache_data($response = [])
    method extract_file_extension (line 657) | private function extract_file_extension()

FILE: lib/model/job.php
  class Job (line 5) | class Job extends Base
    method __construct (line 9) | public function __construct()
    method is_finished (line 14) | public function is_finished()
    method find_one_recent_job (line 19) | public static function find_one_recent_job($job_class)
    method find_one_recent_finished_job (line 38) | public static function find_one_recent_finished_job($job_class)
    method find_one_recent_unfinished_job (line 57) | public static function find_one_recent_unfinished_job($job_class)
    method find_next_in_queue (line 76) | public static function find_next_in_queue()
    method find_recently_finished_jobs (line 92) | public static function find_recently_finished_jobs($limit = 10)
    method find_running_jobs (line 108) | public static function find_running_jobs()
    method load (line 123) | public static function load($id)
    method clean (line 143) | public static function clean()
    method update_state (line 169) | public function update_state($attribute, $value)
    method increase_wakeup_count (line 179) | public function increase_wakeup_count()
    method increase_sleep_count (line 189) | public function increase_sleep_count()

FILE: lib/model/keeps_blog_reference_trait.php
  type KeepsBlogReferenceTrait (line 16) | trait KeepsBlogReferenceTrait
    method set_blog_id (line 20) | public function set_blog_id($blog_id = null)
    method get_blog_id (line 25) | public function get_blog_id()
    method with_blog_scope (line 30) | public function with_blog_scope($callback)

FILE: lib/model/licensable.php
  type Licensable (line 5) | interface Licensable
    method get_license (line 7) | public function get_license();
    method get_license_picture_url (line 9) | public function get_license_picture_url();
    method get_license_html (line 11) | public function get_license_html();

FILE: lib/model/license.php
  class License (line 5) | class License
    method __construct (line 19) | public function __construct($scope, $attributes)
    method getIdentifier (line 33) | public function getIdentifier()
    method getLicenseType (line 77) | public function getLicenseType($url, $name)
    method getName (line 90) | public function getName()
    method getUrl (line 95) | public function getUrl()
    method isCreativeCommons (line 100) | public function isCreativeCommons()
    method getHtml (line 105) | public function getHtml()
    method getPictureUrl (line 143) | public function getPictureUrl()
    method is_cc_license (line 167) | public static function is_cc_license($license_url, $license_name)
    method get_license_from_url (line 180) | public static function get_license_from_url($url)
    method get_name_from_license (line 216) | public static function get_name_from_license($license)
    method get_url_from_license (line 254) | public static function get_url_from_license($license)
    method getAllowModificationId (line 285) | private function getAllowModificationId()
    method getAllowCommercialUseId (line 308) | private function getAllowCommercialUseId()
    method getURLSlug (line 313) | private function getURLSlug($allow_modifications, $allow_commercial_use)
    method get_modification_state (line 346) | private static function get_modification_state($parameter_string)

FILE: lib/model/media_file.php
  class MediaFile (line 7) | class MediaFile extends Base
    method __construct (line 11) | public function __construct()
    method save (line 23) | public function save($determine_size = true)
    method episode_asset (line 37) | public function episode_asset()
    method find_example (line 50) | public static function find_example()
    method find_or_create_by_episode_id_and_episode_asset_id (line 79) | public static function find_or_create_by_episode_id_and_episode_asset_...
    method find_by_episode_id_and_episode_asset_id (line 100) | public static function find_by_episode_id_and_episode_asset_id($episod...
    method find_any_by_episode_id_and_episode_asset_id (line 117) | public static function find_any_by_episode_id_and_episode_asset_id($ep...
    method is_valid (line 133) | public function is_valid()
    method get_public_file_url (line 150) | public function get_public_file_url($source, $context = null)
    method add_ptm_routing (line 198) | public function add_ptm_routing($path, $params)
    method add_ptm_parameters (line 213) | public function add_ptm_parameters($path, $params)
    method get_file_url (line 242) | public function get_file_url()
    method episode (line 266) | public function episode()
    method get_file_name (line 273) | public function get_file_name()
    method get_download_file_name (line 287) | public function get_download_file_name()
    method determine_file_size (line 297) | public function determine_file_size()
    method curl_get_header (line 329) | public function curl_get_header()
    method curl_get_header_for_url (line 345) | public static function curl_get_header_for_url($url, $etag = null)
    method validate_request (line 395) | private function validate_request($response)
    method urlencode_path_segments (line 470) | private function urlencode_path_segments($path)

FILE: lib/model/network_trait.php
  type NetworkTrait (line 5) | trait NetworkTrait
    method table_name (line 12) | public static function table_name()
    method activate_network_scope (line 28) | public static function activate_network_scope()
    method deactivate_network_scope (line 36) | public static function deactivate_network_scope()
    method with_network_scope (line 54) | public static function with_network_scope($callback)

FILE: lib/model/podcast.php
  class Podcast (line 8) | class Podcast implements Licensable
    method __construct (line 26) | protected function __construct($blog_id)
    method __clone (line 32) | public function __clone() {}
    method __set (line 34) | public function __set($name, $value)
    method __get (line 43) | public function __get($name)
    method get (line 52) | public static function get($blog_id = null)
    method name (line 57) | public static function name()
    method has_property (line 69) | public function has_property($name)
    method property_names (line 79) | public function property_names()
    method property (line 91) | public static function property($name)
    method save (line 103) | public function save()
    method full_title (line 120) | public function full_title()
    method get_license (line 131) | public function get_license()
    method get_license_picture_url (line 139) | public function get_license_picture_url()
    method get_license_html (line 144) | public function get_license_html()
    method get_url_template (line 149) | public function get_url_template()
    method get_feed_episode_title_variant (line 156) | public function get_feed_episode_title_variant()
    method get_feed_episode_title_template (line 165) | public function get_feed_episode_title_template()
    method get_media_file_base_uri (line 174) | public function get_media_file_base_uri()
    method feeds (line 199) | public function feeds($args = [])
    method landing_page_url (line 224) | public function landing_page_url()
    method cover_art (line 231) | public function cover_art()
    method has_cover_art (line 236) | public function has_cover_art()
    method default_copyright_claim (line 241) | public function default_copyright_claim()
    method explicit_text (line 246) | public function explicit_text()
    method episodes (line 280) | public function episodes($args = [])
    method set_property (line 428) | private function set_property($name, $value)
    method get_property (line 433) | private function get_property($name)
    method properties (line 447) | private function properties()
    method fetch (line 459) | private function fetch()

FILE: lib/model/template.php
  class Template (line 8) | class Template extends Base
    method all_globally (line 19) | public static function all_globally()
    method find_one_by_title_with_fallback (line 45) | public static function find_one_by_title_with_fallback($template_id)

FILE: lib/model/template_assignment.php
  class TemplateAssignment (line 8) | class TemplateAssignment
    method __construct (line 31) | protected function __construct()
    method __clone (line 36) | private function __clone() {}
    method __set (line 38) | public function __set($name, $value)
    method __get (line 47) | public function __get($name)
    method get_instance (line 61) | public static function get_instance()
    method name (line 70) | public static function name()
    method has_property (line 82) | public function has_property($name)
    method property_names (line 92) | public function property_names()
    method property (line 104) | public function property($name)
    method save (line 116) | public function save()
    method full_title (line 131) | public function full_title()
    method set_property (line 142) | private function set_property($name, $value)
    method get_property (line 147) | private function get_property($name)
    method properties (line 161) | private function properties()
    method fetch (line 173) | private function fetch()

FILE: lib/model/user_agent.php
  class UserAgent (line 8) | class UserAgent extends Base
    method reparse_all (line 13) | public static function reparse_all()
    method parse (line 21) | public function parse()
    method find_or_create_by_uastring (line 68) | public static function find_or_create_by_uastring($ua_string)
    method normalizeOS (line 87) | public static function normalizeOS($os_name)
    method parse_by_device_detector (line 105) | private function parse_by_device_detector()
    method counts_as_bot (line 161) | private function counts_as_bot($client)

FILE: lib/modules/affiliate/affiliate.php
  class Affiliate (line 5) | class Affiliate extends \Podlove\Modules\Base
    method is_core (line 11) | public static function is_core()
    method was_activated (line 17) | public function was_activated($module_name = 'affiliate') {}
    method load (line 19) | public function load()
    method podcast_settings_tabs (line 24) | public static function podcast_settings_tabs($tabs)
    method get_tracking_id (line 31) | public static function get_tracking_id($site)
    method apply_amazon_de_affiliate (line 36) | public static function apply_amazon_de_affiliate($url)
    method apply_thomann_de_affiliate (line 47) | public static function apply_thomann_de_affiliate($url)

FILE: lib/modules/affiliate/podcast_affiliate_settings_tab.php
  class PodcastAffiliateSettingsTab (line 7) | class PodcastAffiliateSettingsTab extends Tab
    method init (line 11) | public function init()
    method process_form (line 17) | public function process_form()
    method register_page (line 38) | public function register_page()
    method get_setting (line 67) | public static function get_setting()

FILE: lib/modules/analytics_heartbeat/analytics_heartbeat.php
  class Analytics_Heartbeat (line 7) | class Analytics_Heartbeat extends \Podlove\Modules\Base
    method is_core (line 13) | public static function is_core()
    method load (line 18) | public function load()
    method schedule_crons (line 28) | public function schedule_crons()
    method uninstall (line 35) | public function uninstall()
    method check_analytics_status (line 40) | public function check_analytics_status()

FILE: lib/modules/analytics_heartbeat/model/heartbeat.php
  class Heartbeat (line 7) | class Heartbeat extends Base {}

FILE: lib/modules/asset_validation/asset_validation.php
  class Asset_Validation (line 8) | class Asset_Validation extends \Podlove\Modules\Base
    method load (line 14) | public function load()
    method schedule_crons (line 38) | public function schedule_crons()
    method was_activated (line 45) | public function was_activated($module_name)
    method was_deactivated (line 50) | public function was_deactivated($module_name)
    method do_valiations (line 58) | public function do_valiations()
    method validate_post (line 78) | private function validate_post(\WP_Post $post)
    method get_aged_posts_needing_validation (line 96) | private function get_aged_posts_needing_validation()
    method get_adolescent_posts_needing_validation (line 136) | private function get_adolescent_posts_needing_validation()
    method get_new_posts_needing_validation (line 176) | private function get_new_posts_needing_validation()

FILE: lib/modules/auphonic/api_wrapper.php
  class API_Wrapper (line 8) | class API_Wrapper
    method __construct (line 13) | public function __construct(Auphonic $module)
    method fetch_authorized_user (line 19) | public function fetch_authorized_user()
    method fetch_presets (line 53) | public function fetch_presets()
    method token_debug (line 78) | private static function token_debug($token)
    method cache_for (line 93) | private static function cache_for($cache_key, $callback, $duration = 3...

FILE: lib/modules/auphonic/auphonic.php
  class Auphonic (line 7) | class Auphonic extends \Podlove\Modules\Base
    method load (line 28) | public function load()
    method api_init (line 54) | public function api_init()
    method register_settings (line 60) | public function register_settings()
    method render_settings_errors (line 91) | public function render_settings_errors()
    method intercept_settings_save (line 100) | public function intercept_settings_save($new_value, $old_value)
    method shows_module_append_preset_option (line 151) | public function shows_module_append_preset_option($wrapper)
    method auphonic_webhook (line 166) | public function auphonic_webhook()
    method update_production_data (line 230) | public function update_production_data($post_id)
    method initiate_plus_file_transfers (line 289) | public function initiate_plus_file_transfers($post_id)
    method get_plus_transfer_queue (line 301) | public function get_plus_transfer_queue($post_id)
    method transfer_single_plus_file (line 314) | public function transfer_single_plus_file($post_id, $file_data)
    method set_plus_transfer_final_status (line 328) | public function set_plus_transfer_final_status($post_id, $status, $fil...
    method convert_chapters_to_string (line 333) | public function convert_chapters_to_string($chapters)
    method ajax_refresh_presets (line 358) | public function ajax_refresh_presets()
    method get_presets_list (line 366) | public function get_presets_list()
    method ajax_add_episode_for_auphonic_webhook (line 390) | public function ajax_add_episode_for_auphonic_webhook()
    method fetch_authorized_user (line 428) | public function fetch_authorized_user()
    method fetch_production (line 466) | public function fetch_production($uuid)
    method fetch_presets (line 491) | public function fetch_presets()
    method check_code (line 521) | public function check_code()
    method get_authorization_description (line 582) | private function get_authorization_description()
    method validate_api_key (line 627) | private function validate_api_key($token)
    method clear_auphonic_cache (line 647) | private function clear_auphonic_cache()

FILE: lib/modules/auphonic/episode_enhancer.php
  class EpisodeEnhancer (line 10) | class EpisodeEnhancer
    method __construct (line 14) | public function __construct(Auphonic $module)
    method auphonic_episodes (line 23) | public function auphonic_episodes($form_data, $episode)
    method auphonic_episodes_form (line 37) | public function auphonic_episodes_form()

FILE: lib/modules/auphonic/plus_file_transfer.php
  class PlusFileTransfer (line 5) | class PlusFileTransfer
    method __construct (line 12) | public function __construct(Auphonic $auphonic_module)
    method initiate_transfers (line 22) | public function initiate_transfers($post_id)
    method generate_filename (line 59) | public static function generate_filename($original_filename, $episode)
    method get_transfer_queue (line 84) | public function get_transfer_queue($post_id)
    method transfer_single_file (line 118) | public function transfer_single_file($post_id, $file_data)
    method set_final_transfer_status (line 134) | public function set_final_transfer_status($post_id, $status, $files = ...
    method get_production_data (line 170) | private function get_production_data($post_id)
    method get_matching_files (line 194) | private function get_matching_files($output_files, $post_id)
    method get_episode (line 216) | private function get_episode($post_id)
    method process_file_transfers (line 241) | private function process_file_transfers($matching_files, $episode, $po...
    method handle_transfer_results (line 262) | private function handle_transfer_results($transfer_results, $post_id, ...
    method get_configured_asset_extensions (line 294) | private function get_configured_asset_extensions()
    method filter_output_files_by_extensions (line 319) | private function filter_output_files_by_extensions($output_files, $con...
    method transfer_file_to_plus (line 343) | private function transfer_file_to_plus($plus_module, $file_data, $post...
    method find_matching_asset_for_filename (line 379) | private static function find_matching_asset_for_filename($original_fil...
    method set_transfer_status (line 435) | private function set_transfer_status($post_id, $status, $error_message...

FILE: lib/modules/auphonic/rest_api.php
  class REST_API (line 5) | class REST_API
    method __construct (line 12) | public function __construct($module)
    method register_routes (line 17) | public function register_routes()
    method get_token (line 116) | public function get_token()
    method init_plus_file_transfer (line 123) | public function init_plus_file_transfer($request)
    method transfer_single_file (line 190) | public function transfer_single_file($request)
    method set_plus_transfer_status (line 221) | public function set_plus_transfer_status($request)
    method permission_check (line 261) | public function permission_check()
    method verify_post_production_relationship (line 278) | private function verify_post_production_relationship($post_id, $produc...

FILE: lib/modules/automatic_numbering/automatic_numbering.php
  class Automatic_Numbering (line 7) | class Automatic_Numbering extends \Podlove\Modules\Base
    method load (line 13) | public function load()
    method override_episode_attribute_defaults (line 18) | public function override_episode_attribute_defaults(array $defaults, $...
    method append_episode_number_to_defaults (line 27) | public function append_episode_number_to_defaults(array $defaults, Mod...

FILE: lib/modules/base.php
  class Base (line 5) | abstract class Base
    method __construct (line 14) | protected function __construct() {}
    method __clone (line 16) | private function __clone() {}
    method instance (line 21) | public static function instance()
    method load (line 40) | abstract public function load();
    method uninstall (line 47) | public function uninstall() {}
    method get_all_module_names (line 54) | public static function get_all_module_names()
    method get_active_module_names (line 76) | public static function get_active_module_names()
    method get_core_module_names (line 93) | public static function get_core_module_names()
    method get_class_by_module_name (line 109) | public static function get_class_by_module_name($module_name)
    method is_active (line 117) | public static function is_active($module_name)
    method activate (line 128) | public static function activate($module_name)
    method is_core (line 148) | public static function is_core()
    method is_visible (line 160) | public static function is_visible()
    method get_module_url (line 165) | public function get_module_url()
    method deactivate (line 170) | public static function deactivate($module_name)
    method get_module_name (line 188) | public function get_module_name()
    method get_module_description (line 198) | public function get_module_description()
    method get_module_group (line 208) | public function get_module_group()
    method get_module_options_name (line 218) | public function get_module_options_name()
    method get_module_options (line 228) | public function get_module_options()
    method get_module_option (line 241) | public function get_module_option($name, $default = null)
    method update_module_option (line 254) | public function update_module_option($name, $value)
    method register_option (line 261) | public function register_option($name, $input_type, $args)
    method get_registered_options (line 269) | public function get_registered_options()
    method is_module_settings_page (line 274) | public static function is_module_settings_page()
    method get_module_class_name (line 289) | protected function get_module_class_name()
    method get_module_namespace_name (line 296) | protected function get_module_namespace_name()
    method get_module_directory_name (line 301) | protected function get_module_directory_name()

FILE: lib/modules/categories/categories.php
  class Categories (line 5) | class Categories extends \Podlove\Modules\Base
    method load (line 11) | public function load()

FILE: lib/modules/contributors/contributor_group_list_table.php
  class Contributor_Group_List_Table (line 5) | class Contributor_Group_List_Table extends \Podlove\List_Table
    method __construct (line 7) | public function __construct()
    method column_title (line 19) | public function column_title($group)
    method column_slug (line 33) | public function column_slug($role)
    method get_columns (line 38) | public function get_columns()
    method prepare_items (line 46) | public function prepare_items()

FILE: lib/modules/contributors/contributor_list_table.php
  class Contributor_List_Table (line 5) | class Contributor_List_Table extends \Podlove\List_Table
    method __construct (line 7) | public function __construct()
    method column_avatar (line 19) | public function column_avatar($contributor)
    method column_realname (line 28) | public function column_realname($contributor)
    method column_identifier (line 45) | public function column_identifier($contributor)
    method column_gender (line 50) | public function column_gender($contributor)
    method column_affiliation (line 59) | public function column_affiliation($contributor)
    method column_privateemail (line 69) | public function column_privateemail($contributor)
    method column_default (line 74) | public function column_default($contributor, $column_name)
    method column_visibility (line 79) | public function column_visibility($contributor)
    method column_episodes (line 84) | public function column_episodes($contributor)
    method column_social (line 89) | public function column_social($contributor)
    method column_donation (line 94) | public function column_donation($contributor)
    method get_columns (line 99) | public function get_columns()
    method search_form (line 115) | public function search_form()
    method get_sortable_columns (line 124) | public function get_sortable_columns()
    method display (line 140) | public function display()
    method prepare_items (line 156) | public function prepare_items()
    method no_items (line 225) | public function no_items()
    method get_episodes_link (line 239) | private function get_episodes_link($contributor, $title)
    method service_column_templates (line 248) | private function service_column_templates($contributor, $type = 'social')

FILE: lib/modules/contributors/contributor_repair.php
  class ContributorRepair (line 7) | class ContributorRepair
    method init (line 9) | public static function init()
    method description (line 15) | public static function description($descriptions)
    method fix_duplicate_contributions (line 20) | public static function fix_duplicate_contributions()
    method find_duplicate_episode_contributions (line 57) | private static function find_duplicate_episode_contributions()

FILE: lib/modules/contributors/contributor_role_list_table.php
  class Contributor_Role_List_Table (line 5) | class Contributor_Role_List_Table extends \Podlove\List_Table
    method __construct (line 7) | public function __construct()
    method column_title (line 19) | public function column_title($role)
    method column_slug (line 33) | public function column_slug($role)
    method get_columns (line 38) | public function get_columns()
    method prepare_items (line 46) | public function prepare_items()

FILE: lib/modules/contributors/contributors.php
  class Contributors (line 15) | class Contributors extends \Podlove\Modules\Base
    method load (line 21) | public function load()
    method uninstall (line 101) | public function uninstall()
    method get_index_contributors_url (line 111) | public static function get_index_contributors_url()
    method get_create_contributor_url (line 116) | public static function get_create_contributor_url()
    method get_edit_contributor_url (line 121) | public static function get_edit_contributor_url($contributor_id)
    method add_to_admin_bar_podcast (line 126) | public function add_to_admin_bar_podcast($wp_admin_bar, $podcast)
    method must_have_uri (line 139) | public function must_have_uri($contributions, $feed)
    method must_match_feed_role_and_group (line 146) | public function must_match_feed_role_and_group($contributions, $feed)
    method cache_tainting_classes (line 161) | public function cache_tainting_classes($classes)
    method orderContributions (line 181) | public static function orderContributions($contributions, $args)
    method filterContributions (line 228) | public static function filterContributions($contributions, $args)
    method expandExportFile (line 301) | public function expandExportFile(\SimpleXMLElement $xml)
    method expandImport (line 315) | public function expandImport($jobs)
    method feed_head_contributors (line 326) | public function feed_head_contributors()
    method feed_item_contributors (line 344) | public function feed_item_contributors($podcast, $episode, $feed, $for...
    method filter_by_contributor (line 359) | public function filter_by_contributor($query)
    method was_activated (line 379) | public function was_activated($module_name)
    method migrate_contributors (line 389) | public function migrate_contributors($module_name)
    method get_additional_settings_for_migration (line 438) | public static function get_additional_settings_for_migration($term_id)
    method update_contributors (line 448) | public function update_contributors($post_id)
    method contributors_form_for_episode (line 491) | public function contributors_form_for_episode($form_data)
    method podcast_settings_tab (line 516) | public function podcast_settings_tab($tabs)
    method save_setting (line 529) | public function save_setting($old, $new)
    method contributors_form_table (line 569) | public static function contributors_form_table($current_contributions ...
    method add_new_podcast_columns (line 673) | public function add_new_podcast_columns($columns)
    method manage_podcast_columns (line 684) | public function manage_podcast_columns($column_name)
    method delete_podcast_contributor (line 711) | public function delete_podcast_contributor()
    method delete_default_contributor (line 733) | public function delete_default_contributor()
    method feed_settings (line 755) | public function feed_settings($wrapper)
    method feed_process (line 806) | public function feed_process($feed_id, $action)
    method api_init (line 819) | public function api_init()
    method apply_default_contributors (line 829) | public function apply_default_contributors(Episode $episode)
    method prepare_contributions_for_feed (line 856) | private function prepare_contributions_for_feed($raw_contributions, $f...
    method getContributorXML (line 881) | private function getContributorXML($contributor)
    method getPodcastindexContributorXML (line 920) | private function getPodcastindexContributorXML($contributor, $contribu...

FILE: lib/modules/contributors/gender_stats.php
  class GenderStats (line 9) | class GenderStats
    method init (line 11) | public static function init()
    method dashboard_gender_statistics (line 17) | public static function dashboard_gender_statistics()
    method dashboard_gender_statistics_widget (line 29) | public static function dashboard_gender_statistics_widget($post)
    method dashboard_network_statistics_row (line 71) | public static function dashboard_network_statistics_row($genders)
    method get_percentage (line 108) | private static function get_percentage($value, $relative_to)
    method group_or_role_stats_table (line 117) | private static function group_or_role_stats_table($context, $numbers)
    method fetch_contributors_for_dashboard_statistics (line 150) | private static function fetch_contributors_for_dashboard_statistics()

FILE: lib/modules/contributors/jobs/podcast_import_contributor_episode_contributions_job.php
  class PodcastImportContributorEpisodeContributionsJob (line 9) | class PodcastImportContributorEpisodeContributionsJob
    method title (line 17) | public static function title()
    method description (line 22) | public static function description()
    method get_import_table_class (line 27) | protected static function get_import_table_class()
    method get_import_item_name (line 32) | protected static function get_import_item_name()

FILE: lib/modules/contributors/jobs/podcast_import_contributor_groups_job.php
  class PodcastImportContributorGroupsJob (line 9) | class PodcastImportContributorGroupsJob
    method title (line 17) | public static function title()
    method description (line 22) | public static function description()
    method get_import_table_class (line 27) | protected static function get_import_table_class()
    method get_import_item_name (line 32) | protected static function get_import_item_name()

FILE: lib/modules/contributors/jobs/podcast_import_contributor_roles_job.php
  class PodcastImportContributorRolesJob (line 9) | class PodcastImportContributorRolesJob
    method title (line 17) | public static function title()
    method description (line 22) | public static function description()
    method get_import_table_class (line 27) | protected static function get_import_table_class()
    method get_import_item_name (line 32) | protected static function get_import_item_name()

FILE: lib/modules/contributors/jobs/podcast_import_contributor_show_contributions_job.php
  class PodcastImportContributorShowContributionsJob (line 9) | class PodcastImportContributorShowContributionsJob
    method title (line 17) | public static function title()
    method description (line 22) | public static function description()
    method get_import_table_class (line 27) | protected static function get_import_table_class()
    method get_import_item_name (line 32) | protected static function get_import_item_name()

FILE: lib/modules/contributors/jobs/podcast_import_contributors_job.php
  class PodcastImportContributorsJob (line 9) | class PodcastImportContributorsJob
    method title (line 17) | public static function title()
    method description (line 22) | public static function description()
    method get_import_table_class (line 27) | protected static function get_import_table_class()
    method get_import_item_name (line 32) | protected static function get_import_item_name()

FILE: lib/modules/contributors/model/contribution_gender_statistics.php
  class ContributionGenderStatistics (line 14) | class ContributionGenderStatistics
    method __construct (line 18) | public function __construct()
    method get (line 38) | public function get()
    method filter_by (line 55) | private function filter_by($filter_key, $filter_value)
    method role_ids (line 62) | private function role_ids()
    method group_ids (line 73) | private function group_ids()
    method count_contributions (line 84) | private static function count_contributions($contributions)

FILE: lib/modules/contributors/model/contributor.php
  class Contributor (line 9) | class Contributor extends Base
    method __construct (line 13) | public function __construct()
    method byGroup (line 18) | public static function byGroup($groupSlug)
    method byRole (line 44) | public static function byRole($roleSlug)
    method byGroupAndRole (line 70) | public static function byGroupAndRole($groupSlug = null, $roleSlug = n...
    method getName (line 110) | public function getName()
    method avatar (line 122) | public function avatar()
    method getContributions (line 138) | public function getContributions()
    method episodes (line 163) | public function episodes($args = [])
    method getPublishedContributionCount (line 238) | public function getPublishedContributionCount()
    method getShowContributions (line 264) | public function getShowContributions()
    method getDefaultContributions (line 269) | public function getDefaultContributions()
    method calcContributioncount (line 277) | public function calcContributioncount()
    method getMailAddress (line 292) | public function getMailAddress()
    method delete (line 311) | public function delete()
    method getGravatarUrl (line 338) | private function getGravatarUrl($s = 80, $email = null)

FILE: lib/modules/contributors/model/contributor_group.php
  class ContributorGroup (line 7) | class ContributorGroup extends Base
    method selectOptions (line 9) | public static function selectOptions()

FILE: lib/modules/contributors/model/contributor_role.php
  class ContributorRole (line 7) | class ContributorRole extends Base
    method selectOptions (line 9) | public static function selectOptions()

FILE: lib/modules/contributors/model/default_contribution.php
  class DefaultContribution (line 10) | class DefaultContribution extends Base
    method getRole (line 12) | public function getRole()
    method getGroup (line 17) | public function getGroup()
    method getContributor (line 22) | public function getContributor()
    method hasRole (line 27) | public function hasRole()
    method hasGroup (line 32) | public function hasGroup()

FILE: lib/modules/contributors/model/episode_contribution.php
  class EpisodeContribution (line 10) | class EpisodeContribution extends Base
    method __construct (line 14) | public function __construct()
    method getRole (line 19) | public function getRole()
    method getGroup (line 26) | public function getGroup()
    method getContributor (line 33) | public function getContributor()
    method hasRole (line 38) | public function hasRole()
    method hasGroup (line 43) | public function hasGroup()
    method getEpisode (line 48) | public function getEpisode()
    method save (line 53) | public function save()
    method delete (line 61) | public function delete()
    method sortByComment (line 69) | public static function sortByComment($a, $b)
    method sortByPosition (line 74) | public static function sortByPosition($a, $b)

FILE: lib/modules/contributors/model/show_contribution.php
  class ShowContribution (line 10) | class ShowContribution extends Base
    method getRole (line 12) | public function getRole()
    method getGroup (line 17) | public function getGroup()
    method getContributor (line 22) | public function getContributor()
    method hasRole (line 27) | public function hasRole()
    method hasGroup (line 32) | public function hasGroup()

FILE: lib/modules/contributors/rest_api.php
  class REST_API (line 11) | class REST_API
    method register_routes (line 20) | public function register_routes()
    method get_contributors (line 71) | public function get_contributors()
    method get_groups (line 87) | public function get_groups()
    method get_roles (line 98) | public function get_roles()
    method get_contributor (line 109) | public function get_contributor($request)
    method get_episode (line 125) | public function get_episode($request)
    method filter_contributor (line 140) | private function filter_contributor($contributor)
  class WP_REST_PodloveContributors_Controller (line 158) | class WP_REST_PodloveContributors_Controller extends \WP_REST_Controller
    method __construct (line 160) | public function __construct()
    method register_routes (line 166) | public function register_routes()
    method get_items (line 439) | public function get_items($request)
    method get_items_group (line 467) | public function get_items_group($request)
    method get_item_group (line 481) | public function get_item_group($request)
    method get_items_role (line 498) | public function get_items_role($request)
    method get_item_role (line 512) | public function get_item_role($request)
    method get_items_default (line 529) | public function get_items_default($request)
    method get_item_default (line 543) | public function get_item_default($request)
    method get_item (line 564) | public function get_item($request)
    method get_item_permissions_check (line 596) | public function get_item_permissions_check($request)
    method create_item (line 614) | public function create_item($request)
    method create_item_group (line 627) | public function create_item_group($request)
    method create_item_role (line 638) | public function create_item_role($request)
    method create_item_default (line 649) | public function create_item_default($request)
    method create_item_permissions_check (line 660) | public function create_item_permissions_check($request)
    method update_item (line 669) | public function update_item($request)
    method update_item_group (line 749) | public function update_item_group($request)
    method update_item_role (line 775) | public function update_item_role($request)
    method update_item_default (line 801) | public function update_item_default($request)
    method update_item_permissions_check (line 842) | public function update_item_permissions_check($request)
    method delete_item (line 851) | public function delete_item($request)
    method delete_item_group (line 867) | public function delete_item_group($request)
    method delete_item_role (line 883) | public function delete_item_role($request)
    method delete_item_default (line 899) | public function delete_item_default($request)
    method delete_item_permissions_check (line 915) | public function delete_item_permissions_check($request)
    method get_episodes (line 924) | public function get_episodes($request)
    method get_contributor_data (line 940) | private function get_contributor_data($contributor)
    method get_contributor_data_full (line 957) | private function get_contributor_data_full($contributor)

FILE: lib/modules/contributors/settings/contributor_defaults.php
  class ContributorDefaults (line 9) | class ContributorDefaults
    method __construct (line 13) | public function __construct($handle)
    method process_form (line 18) | public function process_form()
    method save_setting (line 31) | public function save_setting()
    method page (line 64) | public function page()
    method default_contrib_form (line 82) | private function default_contrib_form()

FILE: lib/modules/contributors/settings/contributor_settings.php
  class ContributorSettings (line 7) | class ContributorSettings
    method __construct (line 13) | public function __construct($handle)
    method page (line 53) | public function page()

FILE: lib/modules/contributors/settings/generic_entity_settings.php
  class GenericEntitySettings (line 11) | class GenericEntitySettings
    method __construct (line 23) | public function __construct($entity_slug, $entity_class)
    method enable_tabs (line 41) | public function enable_tabs($tab_slug)
    method set_labels (line 47) | public function set_labels($labels)
    method set_form (line 52) | public function set_form($form_callback)
    method process_form (line 57) | public function process_form()
    method page (line 74) | public function page()
    method get_action_link (line 123) | public static function get_action_link($entity_slug, $id, $title, $act...
    method save (line 143) | protected function save()
    method create (line 177) | protected function create()
    method delete (line 196) | protected function delete()
    method new_template (line 208) | protected function new_template()
    method edit_template (line 219) | protected function edit_template()
    method view_template (line 229) | protected function view_template()
    method redirect (line 246) | protected function redirect($action, $entity_id = null)
    method get_entity_slug (line 257) | private function get_entity_slug()
    method get_entity_class (line 262) | private function get_entity_class()
    method form_template (line 267) | private function form_template($entity, $action)

FILE: lib/modules/contributors/settings/podcast_contributors_settings_tab.php
  class PodcastContributorsSettingsTab (line 8) | class PodcastContributorsSettingsTab extends Tab
    method init (line 12) | public function init()
    method process_form (line 18) | public function process_form()
    method register_page (line 38) | public function register_page()
    method podcast_form_extension_form (line 67) | public static function podcast_form_extension_form()

FILE: lib/modules/contributors/settings/tab/contributors.php
  class Contributors (line 8) | class Contributors extends Tab
    method get_slug (line 14) | public function get_slug()
    method init (line 19) | public function init()
    method add_contributors_screen_options (line 26) | public function add_contributors_screen_options()
    method register_page (line 37) | public function register_page()
    method getObject (line 43) | public function getObject()
    method createObject (line 52) | public function createObject()
    method contributor_form (line 86) | private function contributor_form($form_args, $contributor, $action)

FILE: lib/modules/contributors/settings/tab/defaults.php
  class Defaults (line 7) | class Defaults extends Tab
    method get_slug (line 11) | public function get_slug()
    method init (line 16) | public function init()
    method register_page (line 22) | public function register_page()
    method getObject (line 28) | public function getObject()

FILE: lib/modules/contributors/settings/tab/groups.php
  class Groups (line 7) | class Groups extends Tab
    method get_slug (line 11) | public function get_slug()
    method init (line 16) | public function init()
    method register_page (line 22) | public function register_page()
    method getObject (line 28) | public function getObject()
    method createObject (line 37) | public function createObject()

FILE: lib/modules/contributors/settings/tab/roles.php
  class Roles (line 7) | class Roles extends Tab
    method get_slug (line 11) | public function get_slug()
    method init (line 16) | public function init()
    method register_page (line 22) | public function register_page()
    method getObject (line 28) | public function getObject()
    method createObject (line 37) | public function createObject()

FILE: lib/modules/contributors/shortcodes.php
  class Shortcodes (line 10) | class Shortcodes
    method __construct (line 22) | public function __construct()
    method shortcode_defaults (line 32) | public static function shortcode_defaults()
    method global_contributor_list (line 49) | public function global_contributor_list($attributes)
    method podlove_contributors (line 69) | public function podlove_contributors($attributes)
    method podlove_contributor_list (line 96) | public function podlove_contributor_list($attributes)
    method podlove_podcast_contributor_list (line 127) | public function podlove_podcast_contributor_list($attributes)

FILE: lib/modules/contributors/template/avatar.php
  class Avatar (line 17) | class Avatar extends Wrapper
    method __construct (line 21) | public function __construct($contributor)
    method url (line 40) | public function url($size = 50)
    method html (line 57) | public function html($args = [])
    method getExtraFilterArgs (line 68) | protected function getExtraFilterArgs()

FILE: lib/modules/contributors/template/contributor.php
  class Contributor (line 17) | class Contributor extends Wrapper
    method __construct (line 22) | public function __construct(Model\Contributor $contributor, $contribut...
    method visible (line 37) | public function visible()
    method name (line 50) | public function name()
    method realname (line 62) | public function realname()
    method nickname (line 72) | public function nickname()
    method id (line 82) | public function id()
    method uri (line 92) | public function uri()
    method publicname (line 104) | public function publicname()
    method gender (line 116) | public function gender()
    method role (line 129) | public function role()
    method group (line 146) | public function group()
    method comment (line 160) | public function comment()
    method avatar (line 182) | public function avatar($size = 50)
    method image (line 194) | public function image()
    method contactemail (line 204) | public function contactemail()
    method organisation (line 214) | public function organisation()
    method department (line 224) | public function department()
    method jobtitle (line 234) | public function jobtitle()
    method episodes (line 263) | public function episodes($args = [])
    method getExtraFilterArgs (line 270) | protected function getExtraFilterArgs()

FILE: lib/modules/contributors/template/contributor_group.php
  class ContributorGroup (line 14) | class ContributorGroup extends Wrapper
    method __construct (line 19) | public function __construct($group, $contributions = null)
    method title (line 34) | public function title()
    method slug (line 44) | public function slug()
    method contributors (line 58) | public function contributors()
    method getExtraFilterArgs (line 65) | protected function getExtraFilterArgs()

FILE: lib/modules/contributors/template_extensions.php
  class TemplateExtensions (line 10) | class TemplateExtensions
    method accessorEpisodeContributors (line 64) | public static function accessorEpisodeContributors($return, $method_na...
    method accessorPodcastContributors (line 135) | public static function accessorPodcastContributors($return, $method_na...
    method accessorPodcastContributor (line 227) | public static function accessorPodcastContributor($return, $method_nam...

FILE: lib/modules/external_analytics/external_analytics.php
  class External_Analytics (line 5) | class External_Analytics extends \Podlove\Modules\Base
    method load (line 11) | public function load()
    method register_hooks (line 17) | public function register_hooks()
    method register_module_option (line 31) | public function register_module_option()

FILE: lib/modules/fyyd/fyyd.php
  class fyyd (line 5) | class fyyd extends \Podlove\Modules\Base
    method load (line 11) | public function load()
    method register_hooks (line 17) | public function register_hooks()
    method register_module_option (line 28) | public function register_module_option()

FILE: lib/modules/import_export/export/podcast_exporter.php
  class PodcastExporter (line 5) | class PodcastExporter
    method __construct (line 10) | public function __construct()
    method init (line 26) | public static function init()
    method enableCompression (line 47) | public function enableCompression()
    method isCompressionEnabled (line 52) | public function isCompressionEnabled()
    method download (line 57) | public function download()
    method exportEpisodes (line 70) | public function exportEpisodes(\SimpleXMLElement $xml)
    method exportAssets (line 75) | public function exportAssets(\SimpleXMLElement $xml)
    method exportFeeds (line 80) | public function exportFeeds(\SimpleXMLElement $xml)
    method exportFileType (line 85) | public function exportFileType(\SimpleXMLElement $xml)
    method exportMediaFile (line 90) | public function exportMediaFile(\SimpleXMLElement $xml)
    method exportTemplates (line 95) | public function exportTemplates(\SimpleXMLElement $xml)
    method exportTracking (line 100) | public function exportTracking(\SimpleXMLElement $xml)
    method exportOptions (line 107) | public function exportOptions(\SimpleXMLElement $xml)
    method exportTable (line 139) | public static function exportTable(\SimpleXMLElement $xml, $group_name...
    method getXml (line 155) | public function getXml()
    method getDownloadFileName (line 181) | private function getDownloadFileName()
    method setDownloadHeaders (line 198) | private function setDownloadHeaders()

FILE: lib/modules/import_export/export/tracking_exporter.php
  class TrackingExporter (line 5) | class TrackingExporter
    method init (line 7) | public static function init()
    method init_download (line 15) | public static function init_download()
    method get_tracking_export_file_path (line 43) | public static function get_tracking_export_file_path()
    method export_tracking (line 50) | public static function export_tracking()
    method export_tracking_status (line 114) | public static function export_tracking_status()
    method getDownloadFileName (line 124) | private static function getDownloadFileName()

FILE: lib/modules/import_export/import/podcast_import_assets_job.php
  class PodcastImportAssetsJob (line 7) | class PodcastImportAssetsJob
    method title (line 15) | public static function title()
    method description (line 20) | public static function description()
    method get_import_table_class (line 25) | protected static function get_import_table_class()
    method get_import_item_name (line 30) | protected static function get_import_item_name()

FILE: lib/modules/import_export/import/podcast_import_episodes_job.php
  class PodcastImportEpisodesJob (line 9) | class PodcastImportEpisodesJob
    method setup (line 14) | public function setup()
    method title (line 20) | public static function title()
    method description (line 25) | public static function description()
    method init_job (line 30) | public function init_job()
    method get_total_steps (line 36) | public function get_total_steps()
    method do_step (line 41) | protected function do_step()
    method getNewPostId (line 78) | private function getNewPostId($old_post_id)

FILE: lib/modules/import_export/import/podcast_import_feeds_job.php
  class PodcastImportFeedsJob (line 7) | class PodcastImportFeedsJob
    method title (line 15) | public static function title()
    method description (line 20) | public static function description()
    method get_import_table_class (line 25) | protected static function get_import_table_class()
    method get_import_item_name (line 30) | protected static function get_import_item_name()

FILE: lib/modules/import_export/import/podcast_import_filetypes_job.php
  class PodcastImportFiletypesJob (line 7) | class PodcastImportFiletypesJob
    method title (line 15) | public static function title()
    method description (line 20) | public static function description()
    method get_import_table_class (line 25) | protected static function get_import_table_class()
    method get_import_item_name (line 30) | protected static function get_import_item_name()

FILE: lib/modules/import_export/import/podcast_import_job_table_trait.php
  type PodcastImportJobTableTrait (line 5) | trait PodcastImportJobTableTrait
    method setup (line 7) | public function setup()
    method init_job (line 13) | public function init_job()
    method get_total_steps (line 21) | public function get_total_steps()
    method get_import_table_class (line 33) | abstract protected static function get_import_table_class();
    method get_import_item_name (line 42) | abstract protected static function get_import_item_name();
    method get_xml_group (line 44) | protected function get_xml_group()
    method do_step (line 49) | protected function do_step()

FILE: lib/modules/import_export/import/podcast_import_job_trait.php
  type PodcastImportJobTrait (line 7) | trait PodcastImportJobTrait
    method setupXml (line 11) | private function setupXml()
    method gzfilesize (line 24) | private static function gzfilesize($filename)
    method escape (line 41) | private static function escape($value)

FILE: lib/modules/import_export/import/podcast_import_mediafiles_job.php
  class PodcastImportMediafilesJob (line 7) | class PodcastImportMediafilesJob
    method title (line 15) | public static function title()
    method description (line 20) | public static function description()
    method get_import_table_class (line 25) | protected static function get_import_table_class()
    method get_import_item_name (line 30) | protected static function get_import_item_name()

FILE: lib/modules/import_export/import/podcast_import_options_job.php
  class PodcastImportOptionsJob (line 8) | class PodcastImportOptionsJob
    method setup (line 13) | public function setup()
    method title (line 20) | public static function title()
    method description (line 25) | public static function description()
    method init_job (line 30) | public function init_job()
    method init_additional_jobs (line 41) | public function init_additional_jobs()
    method get_total_steps (line 52) | public function get_total_steps()
    method do_step (line 57) | protected function do_step()

FILE: lib/modules/import_export/import/podcast_import_templates_job.php
  class PodcastImportTemplatesJob (line 7) | class PodcastImportTemplatesJob
    method title (line 15) | public static function title()
    method description (line 20) | public static function description()
    method get_import_table_class (line 25) | protected static function get_import_table_class()
    method get_import_item_name (line 30) | protected static function get_import_item_name()

FILE: lib/modules/import_export/import/podcast_import_tracking_area_job.php
  class PodcastImportTrackingAreaJob (line 7) | class PodcastImportTrackingAreaJob
    method title (line 15) | public static function title()
    method description (line 20) | public static function description()
    method get_import_table_class (line 25) | protected static function get_import_table_class()
    method get_import_item_name (line 30) | protected static function get_import_item_name()

FILE: lib/modules/import_export/import/podcast_import_tracking_area_name_job.php
  class PodcastImportTrackingAreaNameJob (line 7) | class PodcastImportTrackingAreaNameJob
    method title (line 15) | public static function title()
    method description (line 20) | public static function description()
    method get_import_table_class (line 25) | protected static function get_import_table_class()
    method get_import_item_name (line 30) | protected static function get_import_item_name()

FILE: lib/modules/import_export/import/podcast_import_user_agents_job.php
  class PodcastImportUserAgentsJob (line 7) | class PodcastImportUserAgentsJob
    method title (line 15) | public static function title()
    method description (line 20) | public static function description()
    method get_import_table_class (line 25) | protected static function get_import_table_class()
    method get_import_item_name (line 30) | protected static function get_import_item_name()

FILE: lib/modules/import_export/import/podcast_importer.php
  class PodcastImporter (line 9) | class PodcastImporter
    method init (line 17) | public static function init()
    method get_import_job_classes (line 99) | public static function get_import_job_classes()
    method ajax_render_status (line 117) | public static function ajax_render_status()
    method render_import_progress (line 123) | public static function render_import_progress()
    method render_import_progress_jobs (line 147) | public static function render_import_progress_jobs()

FILE: lib/modules/import_export/import/podcast_importer_job.php
  class PodcastImporterJob (line 7) | class PodcastImporterJob
    method setup (line 17) | public function setup()
    method title (line 22) | public static function title()
    method description (line 27) | public static function description()
    method init_job (line 32) | public function init_job()
    method get_total_steps (line 53) | public function get_total_steps()
    method do_step (line 58) | protected function do_step()

FILE: lib/modules/import_export/import/tracking_importer.php
  class TrackingImporter (line 7) | class TrackingImporter
    method __construct (line 12) | public function __construct($file)
    method init (line 17) | public static function init()
    method print_notice (line 72) | public static function print_notice()

FILE: lib/modules/import_export/import/tracking_importer_job.php
  class TrackingImporterJob (line 9) | class TrackingImporterJob
    method setup (line 13) | public function setup()
    method title (line 19) | public static function title()
    method description (line 24) | public static function description()
    method recalculate_analytics (line 29) | public function recalculate_analytics()
    method init_job (line 41) | public function init_job()
    method get_total_steps (line 51) | public function get_total_steps()
    method do_step (line 56) | protected function do_step()
    method save_batch_to_db (line 114) | private static function save_batch_to_db($batch)
    method get_file (line 135) | private function get_file()
    method get_lines_in_file (line 140) | private function get_lines_in_file()

FILE: lib/modules/import_export/import_export.php
  class Import_Export (line 5) | class Import_Export extends \Podlove\Modules\Base
    method load (line 11) | public function load()
    method register_tools (line 58) | public function register_tools()
    method tools_podcast_export (line 106) | public function tools_podcast_export()
    method tools_tracking_export (line 130) | public function tools_tracking_export()
    method tools_podcast_import (line 206) | public function tools_podcast_import()
    method tools_tracking_import (line 222) | public function tools_tracking_import()
    method get_maximum_upload_size_text (line 234) | public static function get_maximum_upload_size_text()

FILE: lib/modules/logging/log_table.php
  class LogTable (line 7) | class LogTable extends Model\Base
    method cleanup (line 12) | public static function cleanup()

FILE: lib/modules/logging/logging.php
  class Logging (line 8) | class Logging extends \Podlove\Modules\Base
    method get_module_description (line 13) | public function get_module_description()
    method load (line 20) | public function load()
    method uninstall (line 33) | public function uninstall()
    method schedule_crons (line 38) | public static function schedule_crons()
    method cleanup_logging_table (line 45) | public static function cleanup_logging_table()
    method was_activated (line 50) | public function was_activated($module_name)
    method register_database_logger (line 55) | public function register_database_logger()
    method dashoard_template (line 66) | public function dashoard_template()

FILE: lib/modules/logging/wpdbhandler.php
  class WPDBHandler (line 8) | class WPDBHandler extends AbstractProcessingHandler
    method __construct (line 12) | public function __construct($wpdb, $level = Logger::DEBUG, $bubble = t...
    method write (line 18) | protected function write(array $record): void

FILE: lib/modules/logging/wpmail_handler.php
  class WPMailHandler (line 14) | class WPMailHandler extends MailHandler
    method __construct (line 28) | public function __construct($to, $subject, $level = Logger::ERROR, $bu...
    method send (line 35) | protected function send($content, array $records)

FILE: lib/modules/networks/admin_bar_menu.php
  class AdminBarMenu (line 7) | class AdminBarMenu
    method init (line 9) | public static function init()
    method maybe_print_styles (line 19) | public static function maybe_print_styles()
    method print_styles (line 31) | public static function print_styles()
    method enhance_admin_bar (line 40) | public static function enhance_admin_bar($wp_admin_bar)
    method get_styles (line 46) | private static function get_styles()
    method add_network_entries (line 88) | private static function add_network_entries($wp_admin_bar)
    method is_publisher_plugin_active_for_network (line 111) | private static function is_publisher_plugin_active_for_network()
    method add_podcast_list (line 120) | private static function add_podcast_list($wp_admin_bar)
    method add_podcast (line 127) | private static function add_podcast($podcast_id, $wp_admin_bar)
    method podcast_ids (line 151) | private static function podcast_ids()
    method podcast_toolbar_id (line 158) | private static function podcast_toolbar_id($podcast_id, $suffix = '')

FILE: lib/modules/networks/model/network.php
  class Network (line 7) | class Network
    method blog_ids (line 9) | public static function blog_ids()
    method podcast_blog_ids (line 25) | public static function podcast_blog_ids()
    method podcasts (line 40) | public static function podcasts($sortby = 'title', $sort = 'ASC')

FILE: lib/modules/networks/model/podcast_list.php
  class PodcastList (line 11) | class PodcastList extends Base
    method podcasts (line 18) | public function podcasts()
    method latest_episodes (line 42) | public function latest_episodes($number_of_episodes = 10, $orderby = '...

FILE: lib/modules/networks/networks.php
  class Networks (line 8) | class Networks extends \Podlove\Modules\Base
    method is_core (line 14) | public static function is_core()
    method load (line 19) | public function load()
    method add_system_report_validations (line 52) | public function add_system_report_validations($fields)
    method twig_template_filter (line 70) | public function twig_template_filter($context)
    method was_activated (line 79) | public function was_activated($module_name = 'networks')
    method uninstall (line 90) | public function uninstall()
    method create_network_menu (line 98) | public function create_network_menu()
    method scripts_and_styles (line 125) | public function scripts_and_styles()
    method is_active_in_main_blog (line 136) | private static function is_active_in_main_blog()

FILE: lib/modules/networks/podcast_list_list_table.php
  class PodcastList_List_Table (line 7) | class PodcastList_List_Table extends \Podlove\List_Table
    method __construct (line 9) | public function __construct()
    method column_title (line 21) | public function column_title($list)
    method column_logo (line 35) | public function column_logo($list)
    method column_url (line 44) | public function column_url($list)
    method column_podcasts (line 49) | public function column_podcasts($list)
    method podcast_admin_link (line 56) | public function podcast_admin_link($podcast)
    method get_columns (line 65) | public function get_columns()
    method display (line 78) | public function display()
    method prepare_items (line 88) | public function prepare_items()
    method no_items (line 106) | public function no_items()

FILE: lib/modules/networks/podcast_list_table.php
  class Podcast_List_Table (line 9) | class Podcast_List_Table extends \Podlove\List_Table
    method __construct (line 11) | public function __construct()
    method no_items_content (line 23) | public function no_items_content()
    method column_title (line 32) | public function column_title($podcast)
    method column_logo (line 43) | public function column_logo($podcast)
    method column_episodes (line 52) | public function column_episodes($podcast)
    method column_downloads (line 59) | public function column_downloads($podcast)
    method column_latest_episode (line 70) | public function column_latest_episode($podcast)
    method get_columns (line 84) | public function get_columns()
    method search_form (line 95) | public function search_form()
    method display (line 107) | public function display()
    method prepare_items (line 117) | public function prepare_items()

FILE: lib/modules/networks/settings/dashboard.php
  class Dashboard (line 5) | class Dashboard
    method __construct (line 9) | public function __construct()
    method settings_page (line 42) | public static function settings_page()
    method right_now (line 96) | public static function right_now()
    method podcast_overview (line 253) | public static function podcast_overview()
    method list_overview (line 262) | public static function list_overview()

FILE: lib/modules/networks/settings/podcast_lists.php
  class PodcastLists (line 8) | class PodcastLists
    method __construct (line 14) | public function __construct($handle)
    method process_form (line 34) | public function process_form()
    method get_action_link (line 57) | public static function get_action_link($list, $title, $action = 'edit'...
    method page (line 69) | public function page()
    method save (line 114) | private function save()
    method create (line 138) | private function create()
    method delete (line 160) | private function delete()
    method redirect (line 179) | private function redirect($action, $list_id = null)
    method view_template (line 189) | private function view_template()
    method new_template (line 198) | private function new_template()
    method edit_template (line 208) | private function edit_template()
    method form_template (line 217) | private function form_template($list, $action, $button_text = null)

FILE: lib/modules/networks/settings/templates.php
  class Templates (line 7) | class Templates
    method __construct (line 11) | public function __construct($handle)
    method scripts_and_styles (line 30) | public function scripts_and_styles()
    method page (line 55) | public function page()
    method view_template (line 65) | private function view_template()

FILE: lib/modules/networks/template/network.php
  class Network (line 16) | class Network extends Wrapper
    method __construct (line 18) | public function __construct() {}
    method lists (line 48) | public function lists($args = [])
    method getExtraFilterArgs (line 68) | protected function getExtraFilterArgs()

FILE: lib/modules/networks/template/podcast_list.php
  class PodcastList (line 14) | class PodcastList extends Wrapper
    method __construct (line 21) | public function __construct($list)
    method title (line 35) | public function title()
    method subtitle (line 45) | public function subtitle()
    method summary (line 55) | public function summary()
    method description (line 65) | public function description()
    method logo (line 75) | public function logo()
    method url (line 91) | public function url()
    method podcasts (line 101) | public function podcasts()
    method episodes (line 121) | public function episodes($args = [])
    method getExtraFilterArgs (line 130) | protected function getExtraFilterArgs()

FILE: lib/modules/notifications/mailer_job.php
  class MailerJob (line 10) | class MailerJob
    method setup (line 14) | public function setup()
    method title (line 19) | public static function title()
    method description (line 24) | public static function description()
    method mode (line 29) | public static function mode($args)
    method get_total_steps (line 38) | public function get_total_steps()
    method init_job (line 43) | public function init_job()
    method log_mailer_errors (line 51) | public static function log_mailer_errors($wp_error)
    method getReceiver (line 59) | public function getReceiver(Contributor $contributor)
    method isDebug (line 68) | public function isDebug()
    method getSubject (line 73) | public function getSubject()
    method getHeaders (line 85) | public function getHeaders()
    method getMessage (line 93) | public function getMessage()
    method getSenderAddress (line 100) | public static function getSenderAddress()
    method do_step (line 119) | protected function do_step()
    method register_log_mailer_errors (line 135) | private static function register_log_mailer_errors()
    method deregister_log_mailer_errors (line 140) | private static function deregister_log_mailer_errors()
    method prepare_and_send_mail (line 145) | private function prepare_and_send_mail(Contributor $contributor)

FILE: lib/modules/notifications/notifications.php
  class Notifications (line 11) | class Notifications extends \Podlove\Modules\Base
    method load (line 17) | public function load()
    method send_test_notifications (line 35) | public function send_test_notifications()
    method maybe_send_notifications (line 71) | public function maybe_send_notifications($post_id, $post)
    method start_mailer (line 115) | public function start_mailer($args)
    method mark_existing_episodes_as_sent (line 126) | public function mark_existing_episodes_as_sent()
    method notifications_sent (line 149) | public function notifications_sent($post_id)
    method mark_notifications_sent (line 159) | public function mark_notifications_sent($post_id)
    method get_contributors_to_be_notified (line 164) | private function get_contributors_to_be_notified(Episode $episode)

FILE: lib/modules/notifications/settings_tab.php
  class SettingsTab (line 12) | class SettingsTab extends Tab
    method get_slug (line 14) | public function get_slug()
    method page (line 19) | public function page()
    method settings_hook (line 37) | public static function settings_hook()
    method debug_hook (line 42) | public static function debug_hook()
    method init (line 47) | public function init()

FILE: lib/modules/oembed/oembed.php
  class oembed (line 7) | class oembed extends \Podlove\Modules\Base
    method load (line 13) | public function load()
    method load_oembed (line 19) | public function load_oembed()
    method get_current_episode (line 64) | public function get_current_episode($post_id)
    method register_oembed_discovery (line 95) | public function register_oembed_discovery()

FILE: lib/modules/onboarding/onboarding.php
  class Onboarding (line 7) | class Onboarding extends \Podlove\Modules\Base
    method load (line 13) | public function load()
    method is_visible (line 21) | public static function is_visible()
    method onboarding_banner (line 26) | public function onboarding_banner()
    method add_scripts_and_styles (line 94) | public function add_scripts_and_styles()
    method add_onboarding_menu (line 100) | public function add_onboarding_menu()
    method is_banner_hide (line 111) | public static function is_banner_hide()
    method set_banner_hide (line 121) | public static function set_banner_hide($option)
    method get_onboarding_type (line 135) | public static function get_onboarding_type()
    method set_onboarding_type (line 143) | public static function set_onboarding_type($option)
    method get_acknowlegde_option (line 163) | public static function get_acknowlegde_option($user_id)
    method set_acknowledge_option (line 168) | public static function set_acknowledge_option($user_id, $option)
    method api_init (line 176) | public function api_init()
    method get_options (line 182) | private static function get_options()
    method update_options (line 187) | private static function update_options($onboarding_options)

FILE: lib/modules/onboarding/rest_api.php
  class WP_REST_PodloveOnboarding_Controller (line 7) | class WP_REST_PodloveOnboarding_Controller extends \WP_REST_Controller
    method __construct (line 12) | public function __construct()
    method register_routes (line 21) | public function register_routes()
    method update_items (line 32) | public function update_items($request)
    method update_permissions_check (line 67) | public function update_permissions_check($request)

FILE: lib/modules/onboarding/settings/onboarding_page.php
  class OnboardingPage (line 8) | class OnboardingPage
    method __construct (line 13) | public function __construct($handle)
    method get_service_url (line 42) | public static function get_service_url()
    method get_page_link (line 51) | public static function get_page_link()
    method page (line 56) | public function page()

FILE: lib/modules/open_graph/open_graph.php
  class Open_Graph (line 8) | class Open_Graph extends \Podlove\Modules\Base
    method load (line 14) | public function load()
    method register_hooks (line 22) | public function register_hooks()
    method the_open_graph_metadata (line 59) | public function the_open_graph_metadata()
    method get_open_graph_metadata (line 76) | public static function get_open_graph_metadata()

FILE: lib/modules/plus/api.php
  class API (line 8) | class API
    method __construct (line 13) | public function __construct($module, $token)
    method getToken (line 19) | public function getToken()
    method get_me (line 24) | public function get_me()
    method get_account_id (line 32) | public function get_account_id()
    method list_feeds (line 47) | public function list_feeds()
    method push_feeds (line 55) | public function push_feeds($feeds)
    method get_proxy_url (line 70) | public function get_proxy_url($origin_url)
    method create_image_preset (line 83) | public function create_image_preset($template_name, $modifications = [])
    method create_file_upload (line 98) | public function create_file_upload($filename)
    method check_file_exists (line 121) | public function check_file_exists($filename)
    method complete_file_upload (line 144) | public function complete_file_upload($filename)
    method migrate_file (line 167) | public function migrate_file($filename, $file_url, $prevent_double_upl...
    method migrate_auphonic_file (line 200) | public function migrate_auphonic_file($auphonic_url, $filename)
    method list_podcasts (line 238) | public function list_podcasts()
    method upsert_podcast_title (line 258) | public function upsert_podcast_title(string $guid, string $title)
    method get_podcast_by_guid (line 272) | public function get_podcast_by_guid(string $guid)
    method get_podcast (line 289) | public function get_podcast(int $podcast_id)
    method update_podcast (line 297) | public function update_podcast(int $podcast_id, array $data)
    method create_podcast (line 313) | public function create_podcast(string $guid, array $data)
    method set_migration_complete (line 329) | public function set_migration_complete()
    method is_migration_complete (line 341) | public function is_migration_complete()
    method do_upload (line 346) | private function do_upload($target_url, $origin_url, $filename)
    method handle_json_response (line 437) | private function handle_json_response($curl)
    method params (line 448) | private function params($params = [])
    method sanitize_filename (line 465) | private function sanitize_filename($filename)

FILE: lib/modules/plus/banner.php
  class Banner (line 11) | class Banner
    method __construct (line 30) | public function __construct($title, $content, $button_text, $button_ur...
    method render (line 43) | public function render()
    method feed_proxy (line 60) | public static function feed_proxy()
    method file_storage (line 79) | public static function file_storage()
    method plus_main (line 95) | public static function plus_main()
    method plus_authenticated (line 123) | public static function plus_authenticated()

FILE: lib/modules/plus/early_file_hosting_banner.php
  class EarlyFileHostingBanner (line 5) | class EarlyFileHostingBanner
    method __construct (line 12) | public function __construct(PromotionCoordinator $coordinator)
    method init (line 17) | public function init()
    method enqueue_assets (line 25) | public function enqueue_assets()
    method render (line 36) | public function render()
    method maybe_handle_dismiss (line 97) | public function maybe_handle_dismiss()
    method ajax_dismiss (line 114) | public function ajax_dismiss()
    method should_render (line 126) | private function should_render(): bool

FILE: lib/modules/plus/feed_proxy.php
  class FeedProxy (line 5) | class FeedProxy
    method __construct (line 10) | public function __construct($module, $api)
    method is_enabled (line 16) | public static function is_enabled()
    method init (line 25) | public function init()
    method refresh_feed_proxy_cache (line 30) | public function refresh_feed_proxy_cache()
    method get_proxy_url (line 36) | public static function get_proxy_url($origin_url)
    method normalize_url (line 55) | private static function normalize_url($url)

FILE: lib/modules/plus/feed_pusher.php
  class FeedPusher (line 5) | class FeedPusher
    method __construct (line 10) | public function __construct($module, $api)
    method init (line 16) | public function init()
    method push_all_feeds (line 44) | public function push_all_feeds()

FILE: lib/modules/plus/file_storage.php
  class FileStorage (line 7) | class FileStorage
    method __construct (line 12) | public function __construct($module, $api)
    method init (line 18) | public function init()
    method remove_media_tab (line 33) | public function remove_media_tab($tabs)
    method file_url_base (line 40) | public static function file_url_base($url_base = null)
    method file_url_template (line 51) | public static function file_url_template($template)
    method get_local_file_url (line 60) | public static function get_local_file_url($file)
    method is_enabled (line 76) | public static function is_enabled()
    method extend_data_js (line 81) | public function extend_data_js($data)
    method settings_card (line 93) | public function settings_card()
    method modify_url_template_field (line 102) | public function modify_url_template_field($config)

FILE: lib/modules/plus/global_feed_settings.php
  class GlobalFeedSettings (line 10) | class GlobalFeedSettings
    method __construct (line 15) | public function __construct($module, $api)
    method init (line 21) | public function init()
    method podlove_feed_table_url (line 28) | public function podlove_feed_table_url($link, $feed)
    method single_feed_proxy_setting (line 41) | public function single_feed_proxy_setting($wrapper, $feed)
    method global_feed_setting (line 59) | public function global_feed_setting()

FILE: lib/modules/plus/growth_banner.php
  class GrowthBanner (line 5) | class GrowthBanner
    method __construct (line 13) | public function __construct(PromotionCoordinator $coordinator)
    method init (line 18) | public function init()
    method enqueue_assets (line 26) | public function enqueue_assets()
    method render (line 37) | public function render()
    method maybe_handle_dismiss (line 98) | public function maybe_handle_dismiss()
    method ajax_dismiss (line 115) | public function ajax_dismiss()
    method should_render (line 127) | private function should_render()

FILE: lib/modules/plus/plus.php
  class Plus (line 7) | class Plus extends \Podlove\Modules\Base
    method load (line 22) | public function load()
    method get_api (line 110) | public function get_api()
    method base_url (line 115) | public static function base_url()
    method update_podcast_title_and_slug (line 130) | private function update_podcast_title_and_slug(string $guid, string $t...

FILE: lib/modules/plus/promotion_coordinator.php
  class PromotionCoordinator (line 8) | class PromotionCoordinator
    method __construct (line 18) | public function __construct($module)
    method should_render (line 23) | public function should_render(string $banner): bool
    method has_active_banner (line 28) | public function has_active_banner(): bool
    method dismiss (line 33) | public function dismiss(string $banner): void
    method winner (line 41) | public function winner(): ?string
    method is_plus_configured (line 58) | public function is_plus_configured(): bool
    method base_conditions_met (line 66) | private function base_conditions_met(): bool
    method growth_conditions_met (line 95) | private function growth_conditions_met(): bool
    method early_file_hosting_conditions_met (line 100) | private function early_file_hosting_conditions_met(): bool
    method is_in_cooldown (line 109) | private function is_in_cooldown(): bool
    method published_episode_count (line 131) | private function published_episode_count(): int
    method is_supported_admin_page (line 142) | private function is_supported_admin_page(): bool
    method current_admin_file (line 153) | private function current_admin_file(): string
    method current_page (line 164) | private function current_page(): string
    method current_post_type (line 173) | private function current_post_type(): string

FILE: lib/modules/plus/rest_api.php
  class RestApi (line 5) | class RestApi extends \WP_REST_Controller
    method __construct (line 9) | public function __construct(API $api)
    method register_routes (line 17) | public function register_routes()
    method create_upload_url (line 109) | public function create_upload_url($request)
    method check_file_exists (line 120) | public function check_file_exists($request)
    method complete_upload (line 131) | public function complete_upload($request)
    method get_permissions_check (line 142) | public function get_permissions_check($request)
    method get_migration_permissions_check (line 151) | public function get_migration_permissions_check($request)
    method migrate_file (line 160) | public function migrate_file($request)
    method set_migration_complete (line 172) | public function set_migration_complete($request)
    method get_migration_status (line 177) | public function get_migration_status($request)
    method generate_filename (line 184) | public function generate_filename($request)
    method require_token (line 204) | private function require_token()

FILE: lib/modules/plus/settings_page.php
  class SettingsPage (line 5) | class SettingsPage
    method __construct (line 10) | public function __construct($module, $api)
    method init (line 16) | public function init()
    method add_admin_menu (line 21) | public function add_admin_menu()
    method render_settings_page (line 33) | public function render_settings_page()

FILE: lib/modules/podlove_web_player/media_tag_renderer.php
  class MediaTagRenderer (line 8) | class MediaTagRenderer
    method __construct (line 10) | public function __construct(Episode $episode)
    method render (line 15) | public function render($context, $attributes = [])
    method add_sources (line 46) | public function add_sources($xml, $files)
    method format_xml (line 61) | private function format_xml($xml)
    method remove_xml_header (line 71) | private function remove_xml_header($xml)

FILE: lib/modules/podlove_web_player/player_printer_interface.php
  type PlayerPrinterInterface (line 27) | interface PlayerPrinterInterface
    method __construct (line 32) | public function __construct(Episode $episode);
    method render (line 41) | public function render($context);

FILE: lib/modules/podlove_web_player/player_v3/player_media_files.php
  class PlayerMediaFiles (line 9) | class PlayerMediaFiles
    method __construct (line 20) | public function __construct(Episode $episode)
    method get (line 25) | public function get($context = null)
    method media_files (line 37) | private function media_files($context)
    method sort_files (line 64) | private function sort_files($media_files)
    method get_files (line 83) | private function get_files()
    method get_playable_video_files (line 96) | private function get_playable_video_files()
    method get_playable_audio_files (line 101) | private function get_playable_audio_files()
    method get_playable_files (line 114) | private function get_playable_files($formats, $media_type)
    method get_tracking_context (line 144) | private function get_tracking_context()

FILE: lib/modules/podlove_web_player/player_v4/html5printer.php
  class Html5Printer (line 11) | class Html5Printer implements \Podlove\Modules\PodloveWebPlayer\PlayerPr...
    method __construct (line 20) | public function __construct(Episode $episode)
    method render (line 25) | public function render($context = null)
    method media_files (line 43) | public static function media_files($episode, $context)
    method config (line 70) | public static function config($episode, $context)
    method config_url (line 167) | public static function config_url($episode)
    method sanitize_color (line 172) | public static function sanitize_color($color, $default = '#000')
    method get_player_id (line 209) | private function get_player_id()

FILE: lib/modules/podlove_web_player/player_v4/module.php
  class Module (line 7) | class Module
    method load (line 9) | public function load()
    method module (line 44) | public static function module()
    method use_cdn (line 49) | public static function use_cdn()
    method register_scripts (line 54) | public function register_scripts()
    method embed_script_url (line 70) | public static function embed_script_url($use_cdn = true)
    method shortcode (line 79) | public static function shortcode($args = [])
    method register_config_url_route (line 142) | public static function register_config_url_route()
    method config_url_route (line 147) | public static function config_url_route()
    method add_player_settings (line 195) | public function add_player_settings($form_data)

FILE: lib/modules/podlove_web_player/player_v5/module.php
  class Module (line 5) | class Module
    method load (line 7) | public function load()
    method check_plugin_active (line 13) | public function check_plugin_active()
    method player_config_form_data (line 22) | public function player_config_form_data($config)
    method print_admin_notice (line 40) | private function print_admin_notice()

FILE: lib/modules/podlove_web_player/podigee/html5printer.php
  class Html5Printer (line 9) | class Html5Printer implements \Podlove\Modules\PodloveWebPlayer\PlayerPr...
    method __construct (line 16) | public function __construct(Episode $episode)
    method render (line 21) | public function render($context = null, $style = 'configfile')
    method config_url (line 36) | public function config_url()
    method config (line 41) | public static function config($episode, $context)
    method con
Condensed preview — 736 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (4,236K chars).
[
  {
    "path": ".distignore",
    "chars": 212,
    "preview": "/.wordpress-org\n/.git\n/.github\n/node_modules\n\n.distignore\n.gitignore\n\n/.build\n.php_cs.dist\ndeploy_key.enc\nmix-manifest.j"
  },
  {
    "path": ".dockerignore",
    "chars": 30,
    "preview": "!/dist\nvendor\nvendor-prefixed\n"
  },
  {
    "path": ".editorconfig",
    "chars": 494,
    "preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 2335,
    "preview": "Thanks for reading our contribution guidelines!\n\n* [Report a Bug](#report-bug)\n* [Ask for Support](#request-support)\n* ["
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 25,
    "preview": "open_collective: podlove\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 98,
    "preview": "### Expected behavior\n\n### Actual behavior\n\n### System information (see `Podlove > Support` menu)\n"
  },
  {
    "path": ".github/workflows/docker-image.yml",
    "chars": 1892,
    "preview": "# https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#publishing-images-to-github-packages\nn"
  },
  {
    "path": ".github/workflows/release-beta.yml",
    "chars": 1082,
    "preview": "on:\n  push:\n    # Sequence of patterns matched against refs/tags\n    tags:\n      - '*-beta*'\n\nname: Beta Release\n\njobs:\n"
  },
  {
    "path": ".github/workflows/release-wordpress.yml",
    "chars": 761,
    "preview": "name: Release to WordPress.org\non:\n  push:\n    tags:\n      - '*'\n      - '!*-beta*'\njobs:\n  tag:\n    name: Build and Rel"
  },
  {
    "path": ".github/workflows/tests.yml",
    "chars": 1335,
    "preview": "name: Tests\n\non:\n  pull_request:\n\njobs:\n  phpunit:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        "
  },
  {
    "path": ".gitignore",
    "chars": 362,
    "preview": ".DS_Store\n.tags*\n.wordpress_release\n*.sublime-*\n*.code-workspace\nwprelease.yml\ncomposer.phar\nnode_modules\njs/node_module"
  },
  {
    "path": ".gitmodules",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".php-cs-fixer.dist.php",
    "chars": 649,
    "preview": "<?php\n\n$finder = PhpCsFixer\\Finder::create()\n    ->exclude('vendor')\n    ->in(__DIR__)\n;\n\n$config = new PhpCsFixer\\Confi"
  },
  {
    "path": ".prettierrc",
    "chars": 173,
    "preview": "{\n    \"printWidth\": 100,\n    \"jsxBracketSameLine\": true,\n    \"semi\": false,\n    \"singleQuote\": true,\n    \"bracketSpacing"
  },
  {
    "path": ".wp-env.json",
    "chars": 149,
    "preview": "{\n  \"plugins\": [\".\"],\n  \"testsEnvironment\": false,\n  \"config\": {\n    \"WP_DEBUG\": true,\n    \"WP_DEBUG_LOG\": true,\n    \"WP"
  },
  {
    "path": ".wp-env.test.json",
    "chars": 378,
    "preview": "{\n  \"port\": 8889,\n  \"testsEnvironment\": false,\n  \"mappings\": {\n    \"wp-content/plugins/podlove-podcasting-plugin-for-wor"
  },
  {
    "path": ".zed/settings.json",
    "chars": 669,
    "preview": "{\n  \"languages\": {\n    \"PHP\": {\n      \"language_servers\": [\n        \"phpactor\",\n        \"!intelephense\",\n        \"!phpto"
  },
  {
    "path": "AGENTS.md",
    "chars": 3390,
    "preview": "# Repository Guidelines\n\n## Project Structure & Module Organization\n- `podlove.php` and `plugin.php` are the plugin entr"
  },
  {
    "path": "Dockerfile",
    "chars": 467,
    "preview": "FROM wordpress:6-php8.1-apache\n\nRUN apt-get update\nRUN apt-get install zip default-mysql-client -y\nRUN curl -O https://r"
  },
  {
    "path": "Makefile",
    "chars": 3502,
    "preview": "PHP_CS_FIXER = vendor-bin/php-cs-fixer/vendor/friendsofphp/php-cs-fixer/php-cs-fixer\n\nrelease:\n\tbin/release.sh\n\nformat:\n"
  },
  {
    "path": "README.md",
    "chars": 4485,
    "preview": "# Podlove Podcast Publisher\n\nThis is the podcast publishing plugin for WordPress.\n\n- [Getting Started & Documentation][6"
  },
  {
    "path": "bin/code-coverage.sh",
    "chars": 132,
    "preview": "phpunit --coverage-html=../../coverage/report\necho \"Code Coverage Report: http://podlove-publisher.dev/wp-content/covera"
  },
  {
    "path": "bin/docker-entry.sh",
    "chars": 3488,
    "preview": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\n# Waiting for the MySQL server to start\nHOST=$(echo $WORDPRESS_DB_HOST | cut -d:"
  },
  {
    "path": "bin/docker-setup.sh",
    "chars": 20,
    "preview": "#!/usr/bin/env bash\n"
  },
  {
    "path": "bin/release.sh",
    "chars": 978,
    "preview": "#!/usr/bin/env bash\n\nPLUGIN_FILE=./podlove.php\n\nCURRENT_VERSION=`head -n 20 $PLUGIN_FILE | grep \"Version:\" | cut -d: -f2"
  },
  {
    "path": "bin/remove-tunnel.sh",
    "chars": 2343,
    "preview": "#!/usr/bin/env bash\nset -euo pipefail\n\n# Reset WordPress URLs back to local defaults after using a tunnel.\n# Run this fr"
  },
  {
    "path": "bin/reset-nux.sh",
    "chars": 8608,
    "preview": "#!/usr/bin/env bash\nset -euo pipefail\n\n# Full WordPress reset + fresh install for new-user experience testing.\n# Run thi"
  },
  {
    "path": "bin/template_ref.erb",
    "chars": 1113,
    "preview": "<% templateRefClasses.each do |item| %>\n<a id=\"podlove-class-<%= item['class']['templatetag'] %>\"></a>\n\n#### <%= item['c"
  },
  {
    "path": "bin/template_ref.rb",
    "chars": 749,
    "preview": "require \"erb\"\nrequire \"json\"\n\ndef templateRefClasses\n\tclasses = %w{podcast episode network list chapter feed asset file "
  },
  {
    "path": "bin/template_ref_json.php",
    "chars": 5200,
    "preview": "<?php\n\n/**\n * Extracts template reference and saves them to JSON files.\n *\n * Complete workflow for generating reference"
  },
  {
    "path": "bin/uadetect.php",
    "chars": 535,
    "preview": "<?php\n\nrequire_once 'vendor/autoload.php';\n\nuse PodlovePublisher_Vendor\\DeviceDetector\\DeviceDetector;\n\n$userAgent = $ar"
  },
  {
    "path": "bin/update-opawg.sh",
    "chars": 125,
    "preview": "#!/usr/bin/env bash\n\nwget -O data/opawg.json https://raw.githubusercontent.com/opawg/user-agents/master/src/user-agents."
  },
  {
    "path": "bin/update_pwp4.sh",
    "chars": 190,
    "preview": "#!/usr/bin/env bash\n\nnpm update @podlove/web-player\nrm -r lib/modules/podlove_web_player/player_v4/dist\ncp -r node_modul"
  },
  {
    "path": "bin/workspace.js",
    "chars": 399,
    "preview": "const path = require('path')\nconst fs = require('fs-extra')\n\nconst toDelete = (fs.readdirSync(path.resolve('.')) || [])."
  },
  {
    "path": "bin/wp-env-test-after-start.js",
    "chars": 686,
    "preview": "const { spawnSync } = require('node:child_process');\nconst path = require('node:path');\n\nconst repoRoot = path.resolve(_"
  },
  {
    "path": "bootstrap/autoload.php",
    "chars": 2265,
    "preview": "<?php\n\nfunction podlove_camelcase_to_snakecase($string)\n{\n    return preg_replace('/([a-z])([A-Z])/', '$1_$2', $string);"
  },
  {
    "path": "bootstrap/bootstrap.php",
    "chars": 84,
    "preview": "<?php\n\nrequire_once __DIR__.'/autoload.php';\nrequire_once __DIR__.'/constants.php';\n"
  },
  {
    "path": "bootstrap/constants.php",
    "chars": 928,
    "preview": "<?php\n\nnamespace Podlove;\n\n/*\n * Conventions\n *\n * \tPlugin Name:\t\tThis Is My Plugin\n * \tPlugin Namespace:\tThisIsMyPlugin"
  },
  {
    "path": "changelog.txt",
    "chars": 146176,
    "preview": "= 4.0.15 =\n\n- security: add nonces to jobs management\n\n= 4.0.14 =\n\n- add: migrate episode license selector user interfac"
  },
  {
    "path": "client/.tool-versions",
    "chars": 15,
    "preview": "nodejs 18.18.2\n"
  },
  {
    "path": "client/config.local.template.js",
    "chars": 236,
    "preview": "// Template for local development configuration\n// Copy this file to config.local.js and customize\nwindow.devConfig = {\n"
  },
  {
    "path": "client/index.html",
    "chars": 2946,
    "preview": "<html>\n  <head>\n    <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n    <title>Podlove Publisher C"
  },
  {
    "path": "client/package.json",
    "chars": 1259,
    "preview": "{\n  \"scripts\": {\n    \"build\": \"vue-tsc --noEmit && NODE_ENV=production vite build --base=/wp-content/plugins/podlove-pub"
  },
  {
    "path": "client/postcss.config.js",
    "chars": 320,
    "preview": "module.exports = {\n  plugins: [\n    require('postcss-import'),\n    require('tailwindcss/nesting'),\n    require('tailwind"
  },
  {
    "path": "client/src/assets/index.d.ts",
    "chars": 69,
    "preview": "declare module '*.png' {\n    const value: any;\n    export = value;\n}\n"
  },
  {
    "path": "client/src/client.ts",
    "chars": 714,
    "preview": "import { createApp } from 'vue'\nimport { provideStore } from 'redux-vuex'\nimport { store } from '@store'\n\nimport modules"
  },
  {
    "path": "client/src/components/button/Button.vue",
    "chars": 2294,
    "preview": "<template>\n  <button\n    type=\"button\"\n    class=\"inline-flex items-center focus:outline-none focus:ring-2 border border"
  },
  {
    "path": "client/src/components/combobox/Combobox.vue",
    "chars": 4920,
    "preview": "<template>\n  <Combobox\n    :model-value=\"selectValues\" \n    :multiple=\"multiple\"\n    @update:model-value=\"selectItem($ev"
  },
  {
    "path": "client/src/components/icons/Avatar.vue",
    "chars": 326,
    "preview": "<template>\n  <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d"
  },
  {
    "path": "client/src/components/modal/Modal.vue",
    "chars": 3519,
    "preview": "<!-- This example requires Tailwind CSS v2.0+ -->\n<template>\n  <TransitionRoot as=\"template\" :show=\"open\">\n    <Dialog a"
  },
  {
    "path": "client/src/components/module/Module.vue",
    "chars": 878,
    "preview": "<template>\n  <section class=\"bg-white font-sans sm:rounded-lg sm:shadow-md\">\n    <div class=\"bg-white px-4 py-5 border-b"
  },
  {
    "path": "client/src/components/popover/Popover.vue",
    "chars": 887,
    "preview": "<template>\n  <Popover class=\"relative\">\n    <PopoverButton>\n      <slot name=\"trigger\" />\n    </PopoverButton>\n\n    <tra"
  },
  {
    "path": "client/src/components/steps/Steps.vue",
    "chars": 3034,
    "preview": "<template>\n    <ol role=\"list\" class=\"divide-y divide-gray-300 rounded-md border border-gray-300 md:flex md:divide-y-0\">"
  },
  {
    "path": "client/src/components/tabs/Tab.vue",
    "chars": 303,
    "preview": "<template>\n  <div class=\"hidden\" :data-tab=\"name\">\n    <slot></slot>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { d"
  },
  {
    "path": "client/src/components/tabs/TabsContainer.vue",
    "chars": 1939,
    "preview": "<template>\n  <div>\n    <div class=\"block\">\n      <div class=\"border-b border-gray-200\">\n        <nav class=\"-mb-px flex "
  },
  {
    "path": "client/src/components/tabs/index.ts",
    "chars": 137,
    "preview": "// @ts-ignore\nimport Tab from './Tab.vue'\n// @ts-ignore\nimport TabsContainer from './TabsContainer.vue'\n\nexport {\n  Tab,"
  },
  {
    "path": "client/src/components/tag/Tag.vue",
    "chars": 861,
    "preview": "<template>\n    <div class=\"m-1 inline-flex\">\n        <span class=\"\n            flex \n            items-center\n          "
  },
  {
    "path": "client/src/components/tooltip/Tooltip.vue",
    "chars": 1042,
    "preview": "<template>\n  <div>\n    <Popper hover openDelay=\"200\" closeDelay=\"100\" :arrow=\"true\">\n      <slot name=\"trigger\" />\n\n    "
  },
  {
    "path": "client/src/lib/api.ts",
    "chars": 3736,
    "preview": "import { curry } from 'lodash'\n\nexport const addQuery = (url: string, query: { [key: string]: any } = {}) => {\n  const p"
  },
  {
    "path": "client/src/lib/array.ts",
    "chars": 188,
    "preview": "export const arrayMove = <T>(arr: T[], fromIndex: number, toIndex: number) => {\n  const newArr = [...arr];\n  newArr.spli"
  },
  {
    "path": "client/src/lib/auphonic.api.ts",
    "chars": 4043,
    "preview": "import { curry } from 'lodash'\nimport axios, { AxiosProgressEvent } from 'axios'\nimport { addQuery, responseParser, ApiO"
  },
  {
    "path": "client/src/lib/chapters.ts",
    "chars": 2841,
    "preview": "import * as npt from './normalplaytime'\nimport { PodloveChapter } from '../types/chapters.types'\n\nexport function parseM"
  },
  {
    "path": "client/src/lib/errorHandling.ts",
    "chars": 1370,
    "preview": "/**\n * Common error handling utilities for file processing operations\n */\n\nexport interface FileWithUrl {\n  filename?: s"
  },
  {
    "path": "client/src/lib/license.ts",
    "chars": 4754,
    "preview": "import {\n  PodloveLicense,\n  PodloveLicenseOptionCommercial,\n  PodloveLicenseOptionModification,\n  PodloveLicenseVersion"
  },
  {
    "path": "client/src/lib/normalplaytime.ts",
    "chars": 1400,
    "preview": "const parse_ms_string = function (msstring: string) {\n  if (!msstring) {\n    return 0\n  }\n\n  switch (msstring.length) {\n"
  },
  {
    "path": "client/src/lib/popper.ts",
    "chars": 778,
    "preview": "import { ref, onMounted, watchEffect } from \"vue\";\nimport { createPopper, Options } from \"@popperjs/core\";\n\nexport funct"
  },
  {
    "path": "client/src/lib/statusHelpers.ts",
    "chars": 1234,
    "preview": "/**\n * Common status determination utilities for file processing operations\n */\n\nexport type ProcessingStatus = 'complet"
  },
  {
    "path": "client/src/lib/timestamp.ts",
    "chars": 1439,
    "preview": "import * as npt from './normalplaytime'\n\nexport default class Timestamp {\n  constructor(public totalMs: number) {}\n\n  ge"
  },
  {
    "path": "client/src/lib/wordpress.ts",
    "chars": 401,
    "preview": "import { get } from 'lodash'\n\nexport const store = get(window, ['wp', 'data'], null)\nexport const media = get(window, ['"
  },
  {
    "path": "client/src/modules/auphonic/Auphonic.vue",
    "chars": 1343,
    "preview": "<template>\n  <module name=\"auphonic\" title=\"Auphonic\">\n    <div v-if=\"!isProductionSelected\">\n      <StartScreen />\n    "
  },
  {
    "path": "client/src/modules/auphonic/components/FileChooser.vue",
    "chars": 8454,
    "preview": "<template>\n  <div>\n    <div class=\"flex flex-col gap-2\">\n      <!-- step one -->\n      <div>\n        <label class=\"block"
  },
  {
    "path": "client/src/modules/auphonic/components/Logo.vue",
    "chars": 4994,
    "preview": "<template>\n  <svg\n    :class=\"className\"\n    viewBox=\"0 0 301.24005 225.44299\"\n    stroke=\"currentColor\"\n    fill=\"curre"
  },
  {
    "path": "client/src/modules/auphonic/components/ManageProductionForm.vue",
    "chars": 21230,
    "preview": "<template>\n  <form class=\"pb-5 space-y-4\">\n    <div class=\"space-y-8\">\n      <div class=\"bg-white px-4 sm:px-6\">\n       "
  },
  {
    "path": "client/src/modules/auphonic/components/SelectPreset.vue",
    "chars": 4309,
    "preview": "<template>\n  <Listbox as=\"div\" @update:modelValue=\"setPreset\" :value=\"currentPreset\">\n    <ListboxLabel class=\"block tex"
  },
  {
    "path": "client/src/modules/auphonic/components/SelectProduction.vue",
    "chars": 4097,
    "preview": "<template>\n  <Listbox as=\"div\" @update:modelValue=\"setProduction\" :value=\"currentProduction\">\n    <ListboxLabel class=\"b"
  },
  {
    "path": "client/src/modules/auphonic/components/StartScreen.vue",
    "chars": 5251,
    "preview": "<template>\n  <div>\n    <div class=\"m-6 text-center max-w-5xl\">\n      <AuphonicLogo className=\"mx-auto h-16 w-16 text-gra"
  },
  {
    "path": "client/src/modules/auphonic/components/WebhookToggle.vue",
    "chars": 1957,
    "preview": "<template>\n  <div>\n    <SwitchGroup as=\"div\" class=\"flex items-center\">\n      <Switch\n        :modelValue=\"enabled\"\n    "
  },
  {
    "path": "client/src/modules/auphonic/components/production_form/DonePage.vue",
    "chars": 9675,
    "preview": "<template>\n  <div>\n    <div class=\"rounded-md bg-green-50 p-4\" v-if=\"production && production.status == 3\">\n      <div c"
  },
  {
    "path": "client/src/modules/auphonic/components/production_form/PlusTransferStatus.vue",
    "chars": 2979,
    "preview": "<template>\n  <div class=\"mt-4 overflow-hidden rounded-lg bg-white shadow\" v-if=\"showPlusTransferStatus\">\n    <div class="
  },
  {
    "path": "client/src/modules/auphonic/components/production_form/TransferFileItem.vue",
    "chars": 1747,
    "preview": "<template>\n  <li class=\"flex items-center text-sm\">\n    <span class=\"flex-shrink-0 mr-2\">\n      <ArrowPathIcon v-if=\"fil"
  },
  {
    "path": "client/src/modules/auphonic/components/production_form/TransferFileList.vue",
    "chars": 739,
    "preview": "<template>\n  <div v-if=\"files && files.length > 0\">\n    <p class=\"font-medium\">{{ title }}</p>\n    <ul class=\"mt-1 space"
  },
  {
    "path": "client/src/modules/auphonic/components/production_form/TransferHeader.vue",
    "chars": 462,
    "preview": "<template>\n  <div class=\"mb-4\">\n    <h3 class=\"text-lg font-medium leading-6 text-gray-900\">PLUS Podcast File Hosting</h"
  },
  {
    "path": "client/src/modules/auphonic/components/production_form/TransferStatusPanel.vue",
    "chars": 7721,
    "preview": "<template>\n  <!-- Waiting for webhook -->\n  <div class=\"rounded-md bg-blue-50 p-4\" v-if=\"status === 'waiting_for_webhook"
  },
  {
    "path": "client/src/modules/auphonic/index.ts",
    "chars": 63,
    "preview": "import Auphonic from './Auphonic.vue'\n\nexport default Auphonic\n"
  },
  {
    "path": "client/src/modules/chapters/Chapters.vue",
    "chars": 1081,
    "preview": "<template>\n  <module name=\"chapters\" :title=\"__('Chapter Marks', 'podlove-podcasting-plugin-for-wordpress')\">\n    <templ"
  },
  {
    "path": "client/src/modules/chapters/components/Export.vue",
    "chars": 2513,
    "preview": "<template>\n  <div>\n    <popover>\n      <template v-slot:trigger>\n        <podlove-button variant=\"secondary\" size=\"small"
  },
  {
    "path": "client/src/modules/chapters/components/Form.vue",
    "chars": 9874,
    "preview": "<template>\n  <div v-if=\"chapters.length > 0\">\n    <div class=\"md:flex p-2 sm:block\">\n      <div class=\"w-full\">\n        "
  },
  {
    "path": "client/src/modules/chapters/components/Import.vue",
    "chars": 2784,
    "preview": "<template>\n  <tooltip>\n    <template v-slot:trigger>\n      <podlove-button variant=\"secondary\" size=\"small\" @click=\"simu"
  },
  {
    "path": "client/src/modules/chapters/index.ts",
    "chars": 65,
    "preview": "import Chapters from './Chapters.vue';\n\nexport default Chapters;\n"
  },
  {
    "path": "client/src/modules/contributors/Contributors.vue",
    "chars": 3277,
    "preview": "<template>\n  <module name=\"chapters\" :title=\"__('Contributors', 'podlove-podcasting-plugin-for-wordpress')\" class=\"overf"
  },
  {
    "path": "client/src/modules/contributors/components/AddContribution.vue",
    "chars": 7395,
    "preview": "<template>\n  <div class=\"block hover:bg-gray-50\">\n    <div class=\"flex items-center px-4 py-4 sm:px-6\">\n      <div class"
  },
  {
    "path": "client/src/modules/contributors/components/Contribution.vue",
    "chars": 7487,
    "preview": "<template>\n  <div class=\"block hover:bg-gray-50\">\n    <div class=\"flex items-center px-4 py-4 sm:px-6\">\n      <div class"
  },
  {
    "path": "client/src/modules/contributors/index.ts",
    "chars": 77,
    "preview": "import Contributors from './Contributors.vue';\n\nexport default Contributors;\n"
  },
  {
    "path": "client/src/modules/description/Description.vue",
    "chars": 1925,
    "preview": "<template>\n  <module name=\"description\" :title=\"__('Episode Description', 'podlove-podcasting-plugin-for-wordpress')\">\n "
  },
  {
    "path": "client/src/modules/description/components/EpisodeContent.vue",
    "chars": 1442,
    "preview": "<template>\n  <div class=\"relative flex items-start\">\n    <div class=\"flex items-center h-5\">\n      <input\n        id=\"ex"
  },
  {
    "path": "client/src/modules/description/components/EpisodeNumber.vue",
    "chars": 1409,
    "preview": "<template>\n  <div>\n    <label for=\"episode-number\" class=\"block text-sm font-medium text-gray-700\">{{ __('Number', 'podl"
  },
  {
    "path": "client/src/modules/description/components/EpisodePoster.vue",
    "chars": 4722,
    "preview": "<template>\n  <div>\n    <modal size=\"medium\" :open=\"modalOpen\" @close=\"closeModal()\">\n      <div class=\"border-gray-200 b"
  },
  {
    "path": "client/src/modules/description/components/EpisodeSubtitle.vue",
    "chars": 1606,
    "preview": "<template>\n  <div>\n    <label for=\"subtitle\" class=\"block text-sm font-medium text-gray-700\">{{\n      __('Subtitle', 'po"
  },
  {
    "path": "client/src/modules/description/components/EpisodeSummary.vue",
    "chars": 1480,
    "preview": "<template>\n  <div>\n    <label for=\"summary\" class=\"block text-sm font-medium text-gray-700\">{{\n      __('Summary', 'podl"
  },
  {
    "path": "client/src/modules/description/components/EpisodeTitle.vue",
    "chars": 1593,
    "preview": "<template>\n  <div>\n    <label for=\"episode-title\" class=\"block text-sm font-medium text-gray-700\">{{ __('Title', 'podlov"
  },
  {
    "path": "client/src/modules/description/components/EpisodeType.vue",
    "chars": 2064,
    "preview": "<template>\n  <div>\n    <label for=\"episode-type\" class=\"block text-sm font-medium text-gray-700\">{{\n      __('Type', 'po"
  },
  {
    "path": "client/src/modules/description/index.ts",
    "chars": 74,
    "preview": "import Description from './Description.vue';\n\nexport default Description;\n"
  },
  {
    "path": "client/src/modules/index.ts",
    "chars": 896,
    "preview": "import PodloveDescription from './description'\nimport PodloveChapters from './chapters'\nimport PodloveTranscripts from '"
  },
  {
    "path": "client/src/modules/license/License.vue",
    "chars": 1564,
    "preview": "<template>\n  <module name=\"license\" :title=getModuleTitle>\n    <template v-slot:actions>\n      <div v-if=\"isScopeEpisode"
  },
  {
    "path": "client/src/modules/license/components/LicenseName.vue",
    "chars": 2176,
    "preview": "<template>\n    <div>\n      <label for=\"episode-license-name\" class=\"block text-sm font-medium text-gray-700\">{{ __('Lice"
  },
  {
    "path": "client/src/modules/license/components/LicenseSelector.vue",
    "chars": 10345,
    "preview": "<template>\n  <div class=\"border-gray-200 border-b pb-2 px-3 py-5\">\n    <h3 class=\"text-lg leading-6 font-medium text-gra"
  },
  {
    "path": "client/src/modules/license/components/LicenseSelectorButton.vue",
    "chars": 1075,
    "preview": "<template>\n  <podlove-button variant=\"secondary\" size=\"small\" @click=\"openSelector\">{{ __('License Selector', 'podlove-p"
  },
  {
    "path": "client/src/modules/license/components/LicenseUrl.vue",
    "chars": 2280,
    "preview": "<template>\n  <div>\n    <label for=\"episode-license-name\" class=\"block text-sm font-medium text-gray-700\">{{ __('License "
  },
  {
    "path": "client/src/modules/license/components/LicenseView.vue",
    "chars": 2006,
    "preview": "<template>\n  <div class=\"mt-3\">\n    <div class=\"mb-3 text-sm font-medium text-gray-700\">\n      License preview:\n    </di"
  },
  {
    "path": "client/src/modules/license/index.ts",
    "chars": 61,
    "preview": "import License from './License.vue';\n\nexport default License;"
  },
  {
    "path": "client/src/modules/mediafiles/MediaFiles.vue",
    "chars": 2757,
    "preview": "<template>\n  <module name=\"mediafiles\" title=\"Media Files\">\n    <div>\n      <div class=\"w-full flex justify-center m-12 "
  },
  {
    "path": "client/src/modules/mediafiles/components/AssetsEmptyState.vue",
    "chars": 1160,
    "preview": "<template>\n  <div class=\"text-center\">\n    <svg\n      class=\"mx-auto h-12 w-12 text-gray-400\"\n      fill=\"none\"\n      vi"
  },
  {
    "path": "client/src/modules/mediafiles/components/AssetsTable.vue",
    "chars": 6378,
    "preview": "<template>\n  <label for=\"assets\" class=\"block text-sm font-medium leading-6 text-gray-900 sm:pt-1.5\">{{\n    __('Assets',"
  },
  {
    "path": "client/src/modules/mediafiles/components/MediaSlug.vue",
    "chars": 3980,
    "preview": "<template>\n  <label for=\"filename_slug\" class=\"block text-sm font-medium leading-6 text-gray-900 sm:pt-1.5\">{{\n    __('F"
  },
  {
    "path": "client/src/modules/mediafiles/components/MediaUpload.vue",
    "chars": 1148,
    "preview": "<template>\n  <label\n    for=\"mediafile_upload\"\n    class=\"block text-sm font-medium leading-6 text-gray-900 sm:pt-1.5\"\n "
  },
  {
    "path": "client/src/modules/mediafiles/components/PlusMediaUpload.vue",
    "chars": 10237,
    "preview": "<template>\n  <label\n    for=\"mediafile_upload\"\n    class=\"block text-sm font-medium leading-6 text-gray-900 sm:pt-1.5\"\n "
  },
  {
    "path": "client/src/modules/mediafiles/index.ts",
    "chars": 69,
    "preview": "import MediaFiles from './MediaFiles.vue'\n\nexport default MediaFiles\n"
  },
  {
    "path": "client/src/modules/plus_features/Feature.vue",
    "chars": 3564,
    "preview": "<template>\n  <div\n    class=\"overflow-hidden rounded-lg bg-white border border-gray-200 transition-shadow duration-200 h"
  },
  {
    "path": "client/src/modules/plus_features/PlusFeatures.vue",
    "chars": 3997,
    "preview": "<template>\n  <div class=\"mb-6 rounded-lg bg-white p-6 shadow-sm\">\n    <div class=\"mb-6\">\n      <h2 class=\"mb-2 text-xl f"
  },
  {
    "path": "client/src/modules/plus_features/index.ts",
    "chars": 75,
    "preview": "import PlusFeatures from './PlusFeatures.vue'\n\nexport default PlusFeatures\n"
  },
  {
    "path": "client/src/modules/plus_file_migration/PlusFileMigration.vue",
    "chars": 8310,
    "preview": "<template>\n  <div class=\"m-3 rounded-lg bg-white\">\n    <section class=\"bg-white w-full\" v-if=\"uiState === 'init'\">\n     "
  },
  {
    "path": "client/src/modules/plus_file_migration/index.ts",
    "chars": 90,
    "preview": "import PlusFileMigration from './PlusFileMigration.vue'\n\nexport default PlusFileMigration\n"
  },
  {
    "path": "client/src/modules/plus_token/PlusToken.vue",
    "chars": 3404,
    "preview": "<template>\n  <div class=\"mb-6 rounded-lg bg-white p-6 shadow-sm\">\n    <div class=\"mb-6\">\n      <h2 class=\"mb-2 text-xl f"
  },
  {
    "path": "client/src/modules/plus_token/TokenInput.vue",
    "chars": 3213,
    "preview": "<template>\n  <div class=\"space-y-4\">\n    <div>\n      <label for=\"api_token\" class=\"block text-sm font-medium text-gray-7"
  },
  {
    "path": "client/src/modules/plus_token/index.ts",
    "chars": 66,
    "preview": "import PlusToken from './PlusToken.vue'\n\nexport default PlusToken\n"
  },
  {
    "path": "client/src/modules/related/RelatedEpisodes.vue",
    "chars": 3457,
    "preview": "<template>\n  <module name=\"relatedEpisodes\" :title=\"__('Related episodes', 'podlove-podcasting-plugin-for-wordpress')\">\n"
  },
  {
    "path": "client/src/modules/related/index.ts",
    "chars": 85,
    "preview": "import RelatedEpisodes from './RelatedEpisodes.vue';\n\nexport default RelatedEpisodes;"
  },
  {
    "path": "client/src/modules/shows/ShowSelect.vue",
    "chars": 2107,
    "preview": "<template>\n  <div>\n    <div class=\"flex items-center\">\n      <input\n        id=\"podlove-show-default\"\n        name=\"podl"
  },
  {
    "path": "client/src/modules/shows/index.ts",
    "chars": 69,
    "preview": "import ShowSelect from './ShowSelect.vue'\n\nexport default ShowSelect\n"
  },
  {
    "path": "client/src/modules/soundbite/Soundbite.vue",
    "chars": 932,
    "preview": "<template>\n    <module name=\"soundbite\" :title=\"__('Soundbite', 'podlove-podcasting-plugin-for-wordpress')\">\n        <te"
  },
  {
    "path": "client/src/modules/soundbite/components/Clear.vue",
    "chars": 939,
    "preview": "<template>\n    <podlove-button variant=\"secondary\" size=\"small\" @click=\"clearSoundbite()\">{{ __('Clear', 'podlove-podcas"
  },
  {
    "path": "client/src/modules/soundbite/components/Form.vue",
    "chars": 4793,
    "preview": "<template>\n  <div class=\"grid md:grid-cols-4 md:grid-rows-1 sm:grid-rows-4 p-3\">\n    <div class=\"mb-5 ml-5\">\n      <labe"
  },
  {
    "path": "client/src/modules/soundbite/index.ts",
    "chars": 68,
    "preview": "import Soundbite from './Soundbite.vue';\n\nexport default Soundbite;\n"
  },
  {
    "path": "client/src/modules/transcripts/Transcripts.vue",
    "chars": 1274,
    "preview": "<template>\n  <module name=\"transcript\" :title=\"__('Transcripts', 'podlove-podcasting-plugin-for-wordpress')\">\n    <templ"
  },
  {
    "path": "client/src/modules/transcripts/components/Delete.vue",
    "chars": 2616,
    "preview": "<template>\n  <div v-if=\"state.transcripts.length > 0\">\n    <podlove-button variant=\"secondary\" size=\"small\" @click=\"open"
  },
  {
    "path": "client/src/modules/transcripts/components/Export.vue",
    "chars": 2531,
    "preview": "<template>\n  <div v-if=\"state.transcripts.length > 0\">\n    <popover>\n      <template v-slot:trigger>\n        <podlove-bu"
  },
  {
    "path": "client/src/modules/transcripts/components/Import.vue",
    "chars": 2143,
    "preview": "<template>\n  <form ref=\"importForm\" class=\"cursor-pointer\">\n    <div class=\"grid grid-cols-2\">\n      <div>\n        <podl"
  },
  {
    "path": "client/src/modules/transcripts/components/List.vue",
    "chars": 4836,
    "preview": "<template>\n  <div class=\"h-96 p-2 overflow-x-auto\" v-if=\"transcripts.length > 0\">\n    <div\n      class=\"flex mb-2\"\n     "
  },
  {
    "path": "client/src/modules/transcripts/components/Voices.vue",
    "chars": 2951,
    "preview": "<template>\n  <div v-if=\"state.voices.length > 0\">\n    <podlove-button variant=\"secondary\" size=\"small\" @click=\"openVoice"
  },
  {
    "path": "client/src/modules/transcripts/index.ts",
    "chars": 74,
    "preview": "import Transcripts from './Transcripts.vue';\n\nexport default Transcripts;\n"
  },
  {
    "path": "client/src/plugins/translations.ts",
    "chars": 476,
    "preview": "import { get } from 'lodash'\nimport { App } from 'vue'\n\ndeclare module '@vue/runtime-core' {\n  interface ComponentCustom"
  },
  {
    "path": "client/src/sagas/admin.sagas.ts",
    "chars": 1056,
    "preview": "import { fork, select, put, takeEvery } from 'redux-saga/effects'\nimport { PodloveApiClient } from '@lib/api'\nimport { c"
  },
  {
    "path": "client/src/sagas/api.ts",
    "chars": 1123,
    "preview": "import { call, select } from 'redux-saga/effects'\nimport { podlove } from '../lib/api'\nimport { selectors, store } from "
  },
  {
    "path": "client/src/sagas/auphonic.api.ts",
    "chars": 532,
    "preview": "import { selectors, store } from '@store'\nimport { notify } from '@store/notification.store'\nimport { select } from 'red"
  },
  {
    "path": "client/src/sagas/auphonic.sagas.ts",
    "chars": 32686,
    "preview": "import * as auphonic from '@store/auphonic.store'\nimport * as episode from '@store/episode.store'\nimport * as progress f"
  },
  {
    "path": "client/src/sagas/chapters.sagas.ts",
    "chars": 6465,
    "preview": "import { TakeableChannel } from '@redux-saga/core'\nimport { select, takeEvery, call, put, fork } from 'redux-saga/effect"
  },
  {
    "path": "client/src/sagas/contributors.sagas.ts",
    "chars": 3960,
    "preview": "import { fork, put, select, takeEvery, throttle } from 'redux-saga/effects'\nimport { get, toInteger } from 'lodash'\n\nimp"
  },
  {
    "path": "client/src/sagas/episode.sagas.ts",
    "chars": 3827,
    "preview": "import { PodloveApiClient } from '@lib/api'\nimport { selectors } from '@store'\nimport { get, isEmpty } from 'lodash'\nimp"
  },
  {
    "path": "client/src/sagas/helper.ts",
    "chars": 2526,
    "preview": "import { eventChannel, Channel, channel as reduxChannel } from 'redux-saga'\nimport { fork, take, call, select, spawn, ca"
  },
  {
    "path": "client/src/sagas/lifecycle.sagas.ts",
    "chars": 1101,
    "preview": "import { eventChannel, END, EventChannel } from 'redux-saga'\nimport { call, takeEvery, put } from 'redux-saga/effects'\n\n"
  },
  {
    "path": "client/src/sagas/mediafiles.duration.sagas.ts",
    "chars": 1317,
    "preview": "import { PodloveApiClient } from '@lib/api'\nimport { selectors } from '@store'\nimport { put, select } from 'redux-saga/e"
  },
  {
    "path": "client/src/sagas/mediafiles.enable.sagas.ts",
    "chars": 1232,
    "preview": "import { PodloveApiClient } from '@lib/api'\nimport { selectors } from '@store'\nimport { put, select } from 'redux-saga/e"
  },
  {
    "path": "client/src/sagas/mediafiles.fileselection.sagas.ts",
    "chars": 4999,
    "preview": "import { PodloveApiClient } from '@lib/api'\nimport { call, put, select, delay, fork } from 'redux-saga/effects'\nimport *"
  },
  {
    "path": "client/src/sagas/mediafiles.sagas.ts",
    "chars": 2539,
    "preview": "import { PodloveApiClient } from '@lib/api'\nimport { selectors } from '@store'\nimport {\n  fork,\n  put,\n  select,\n  takeE"
  },
  {
    "path": "client/src/sagas/mediafiles.slug.sagas.ts",
    "chars": 2629,
    "preview": "import { PodloveApiClient } from '@lib/api'\nimport { selectors } from '@store'\nimport { put, select, fork } from 'redux-"
  },
  {
    "path": "client/src/sagas/mediafiles.upload.sagas.ts",
    "chars": 4214,
    "preview": "import { PodloveApiClient } from '@lib/api'\nimport { selectors } from '@store'\nimport { call, put, select } from 'redux-"
  },
  {
    "path": "client/src/sagas/mediafiles.verification.sagas.ts",
    "chars": 1869,
    "preview": "import { PodloveApiClient } from '@lib/api'\nimport { selectors } from '@store'\nimport { all, fork, put, select } from 'r"
  },
  {
    "path": "client/src/sagas/notification.saga.ts",
    "chars": 1343,
    "preview": "import { takeEvery } from 'redux-saga/effects'\n\nimport { get } from 'lodash'\nimport { NOTIFY } from '@store/notification"
  },
  {
    "path": "client/src/sagas/plus.sagas.ts",
    "chars": 2417,
    "preview": "import * as plus from '@store/plus.store'\nimport { takeFirst } from './helper'\nimport { fork, put, select, call, takeEve"
  },
  {
    "path": "client/src/sagas/plusFileMigration.sagas.ts",
    "chars": 4310,
    "preview": "import { takeFirst } from '../sagas/helper'\nimport { fork, put, select, call } from 'redux-saga/effects'\nimport { Podlov"
  },
  {
    "path": "client/src/sagas/podcast.sagas.ts",
    "chars": 1584,
    "preview": "import { PodloveApiClient } from '@lib/api'\nimport { fork, put, takeEvery } from 'redux-saga/effects'\nimport { takeFirst"
  },
  {
    "path": "client/src/sagas/relatedEpisodes.sagas.ts",
    "chars": 1931,
    "preview": "import { createApi } from './api'\nimport { PodloveApiClient } from '@lib/api'\nimport { fork, takeEvery } from '@redux-sa"
  },
  {
    "path": "client/src/sagas/shows.sagas.ts",
    "chars": 2113,
    "preview": "import { PodloveApiClient } from '@lib/api'\nimport { fork, put, select, takeEvery } from 'redux-saga/effects'\nimport { t"
  },
  {
    "path": "client/src/sagas/transcripts.sagas.ts",
    "chars": 2714,
    "preview": "import { fork } from '@redux-saga/core/effects'\nimport { takeEvery, select, put } from 'redux-saga/effects'\nimport { get"
  },
  {
    "path": "client/src/sagas/wordpress.sagas.ts",
    "chars": 4352,
    "preview": "import { call, put, select, takeEvery } from 'redux-saga/effects'\n\nimport * as lifecycleStore from '@store/lifecycle.sto"
  },
  {
    "path": "client/src/store/admin.store.ts",
    "chars": 1318,
    "preview": "import { get } from 'lodash'\nimport { handleActions, createAction } from 'redux-actions'\n\nexport const INIT = 'podlove/p"
  },
  {
    "path": "client/src/store/auphonic.store.ts",
    "chars": 17179,
    "preview": "import { createAction, handleActions } from 'redux-actions'\n\nexport type Service = {\n  uuid: string\n  display_name: stri"
  },
  {
    "path": "client/src/store/chapters.store.ts",
    "chars": 4256,
    "preview": "import { get } from 'lodash'\nimport { handleActions, createAction } from 'redux-actions'\nimport { PodloveChapter } from "
  },
  {
    "path": "client/src/store/contributors.store.ts",
    "chars": 1861,
    "preview": "import { handleActions, createAction } from 'redux-actions'\nimport { PodloveContributor, PodloveGroup, PodloveRole } fro"
  },
  {
    "path": "client/src/store/episode.store.ts",
    "chars": 11159,
    "preview": "import { get, pick } from 'lodash'\nimport { handleActions } from 'redux-actions'\nimport { createAction } from 'redux-act"
  },
  {
    "path": "client/src/store/index.ts",
    "chars": 3235,
    "preview": "declare global {\n  interface Window {\n    __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: Function\n  }\n}\n\nimport { createStore, ap"
  },
  {
    "path": "client/src/store/lifecycle.store.ts",
    "chars": 977,
    "preview": "import { handleActions, createAction } from 'redux-actions'\n\nexport type State = {\n  saved: boolean;\n  changes: boolean;"
  },
  {
    "path": "client/src/store/mediafiles.store.ts",
    "chars": 5989,
    "preview": "import { createAction, handleActions } from 'redux-actions'\n\nexport type MediaFile = {\n  asset_id: number\n  asset: strin"
  },
  {
    "path": "client/src/store/notification.store.ts",
    "chars": 368,
    "preview": "import { handleActions, createAction } from 'redux-actions'\n\nexport const NOTIFY = 'podlove/publisher/NOTIFY'\nexport con"
  },
  {
    "path": "client/src/store/plus.store.ts",
    "chars": 2663,
    "preview": "import { handleActions, createAction } from 'redux-actions'\nimport * as lifecycle from './lifecycle.store'\n\nexport type "
  },
  {
    "path": "client/src/store/plusFileMigration.store.ts",
    "chars": 4615,
    "preview": "import { handleActions, createAction } from 'redux-actions'\nimport * as lifecycle from './lifecycle.store'\n\ntype UploadS"
  },
  {
    "path": "client/src/store/podcast.store.ts",
    "chars": 3014,
    "preview": "import { get } from 'lodash'\nimport { handleActions } from 'redux-actions'\nimport { createAction } from 'redux-actions'\n"
  },
  {
    "path": "client/src/store/post.store.ts",
    "chars": 1326,
    "preview": "import { get } from 'lodash'\nimport { handleActions } from 'redux-actions'\nimport { INIT, init } from './lifecycle.store"
  },
  {
    "path": "client/src/store/progress.store.ts",
    "chars": 2446,
    "preview": "import { createAction, handleActions } from 'redux-actions'\n\nexport type ProgressStatus = 'init' | 'in_progress' | 'fini"
  },
  {
    "path": "client/src/store/reducers.ts",
    "chars": 1558,
    "preview": "import { combineReducers } from 'redux'\nimport * as lifecycleStore from './lifecycle.store'\nimport * as chaptersStore fr"
  },
  {
    "path": "client/src/store/relatedEpisodes.store.ts",
    "chars": 1593,
    "preview": "import { handleActions, createAction } from \"redux-actions\";\nimport { PodloveEpisodeList } from \"../types/relatedEpisode"
  },
  {
    "path": "client/src/store/runtime.store.ts",
    "chars": 1128,
    "preview": "import { get } from 'lodash';\nimport { handleActions, } from 'redux-actions'\nimport { INIT, init } from './lifecycle.sto"
  },
  {
    "path": "client/src/store/selectors.ts",
    "chars": 11880,
    "preview": "import { createSelector } from 'reselect'\nimport { State } from './index'\nimport * as lifecycleStore from './lifecycle.s"
  },
  {
    "path": "client/src/store/settings.store.ts",
    "chars": 6564,
    "preview": "import { get } from 'lodash'\nimport { handleActions } from 'redux-actions'\nimport { init, INIT } from './lifecycle.store"
  },
  {
    "path": "client/src/store/shows.store.ts",
    "chars": 824,
    "preview": "import { PodloveShow } from '../types/shows.types'\nimport { get } from 'lodash'\nimport { handleActions, createAction } f"
  },
  {
    "path": "client/src/store/transcripts.store.ts",
    "chars": 2404,
    "preview": "import { handleActions, createAction } from 'redux-actions'\n\nimport { PodloveTranscript, PodloveTranscriptVoice } from '"
  },
  {
    "path": "client/src/store/vue.ts",
    "chars": 768,
    "preview": "import type { Dispatch, Store, UnknownAction } from 'redux'\nimport { injectStore, mapState } from 'redux-vuex'\n\nimport t"
  },
  {
    "path": "client/src/store/wordpress.store.ts",
    "chars": 408,
    "preview": "import { Action } from 'redux'\nimport { createAction } from 'redux-actions'\n\nexport const UPDATE = 'podlove/publisher/wo"
  },
  {
    "path": "client/src/style.css",
    "chars": 138,
    "preview": "\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n/* fix overwrites */\ninput[type=checkbox]:checked::before {"
  },
  {
    "path": "client/src/types/chapters.types.ts",
    "chars": 105,
    "preview": "export interface PodloveChapter {\n  start: number;\n  title: string;\n  href?: string;\n  image?: string;\n}\n"
  },
  {
    "path": "client/src/types/contributors.types.ts",
    "chars": 433,
    "preview": "export interface PodloveContributor {\n  id: string\n  avatar: string\n  avatar_url: string\n  count: string\n  department: s"
  },
  {
    "path": "client/src/types/episode.types.ts",
    "chars": 322,
    "preview": "export interface PodloveEpisode {\n  slug: string\n  slug_frozen: boolean\n  number: string\n  title: string\n  subtitle: str"
  },
  {
    "path": "client/src/types/license.types.ts",
    "chars": 5660,
    "preview": "export enum PodloveLicenseVersion {\n    cc0 = \"Public Domain License\",\n    pdmark = \"Public Domain Mark License\",\n    cc"
  },
  {
    "path": "client/src/types/relatedEpisodes.types.ts",
    "chars": 91,
    "preview": "export interface PodloveEpisodeList {\n    episode_id: number,\n    episode_title: string,\n}\n"
  },
  {
    "path": "client/src/types/shows.types.ts",
    "chars": 194,
    "preview": "export interface PodloveShow {\n  id: number\n  title: string\n  slug: string\n  subtitle: string\n  summary: string\n  image:"
  },
  {
    "path": "client/src/types/transcripts.types.ts",
    "chars": 229,
    "preview": "export interface PodloveTranscript {\n  voice: string;\n  start: string,\n  start_ms: number;\n  end: string;\n  end_ms: numb"
  },
  {
    "path": "client/src/vue-shims.d.ts",
    "chars": 147,
    "preview": "declare module '*.vue' {\n  import type { DefineComponent } from 'vue'\n  const component: DefineComponent<{}, {}, any>\n  "
  },
  {
    "path": "client/tailwind.config.js",
    "chars": 336,
    "preview": "// tailwind.config.js\nconst defaultTheme = require('tailwindcss/defaultTheme')\n\nmodule.exports = {\n  content: ['./index."
  },
  {
    "path": "client/tsconfig.json",
    "chars": 792,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"target\": \"esnext\",\n    \"useDefineForClassFields\": true,\n    \"module\": "
  },
  {
    "path": "client/typings/podlove.d.ts",
    "chars": 752,
    "preview": "interface Chapter {\n  start: number;\n  title: string;\n  url?: string;\n  image?: string;\n}\n\ndeclare module '@podlove/util"
  }
]

// ... and 536 more files (download for full content)

About this extraction

This page contains the full source code of the podlove/podlove-publisher GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 736 files (3.8 MB), approximately 1.0M tokens, and a symbol index with 3383 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!