initial state
This commit is contained in:
commit
fbd595aa36
78
.gitignore
vendored
Normal file
78
.gitignore
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
# Development Dependencies
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
# Build Artifacts
|
||||
# The compiled assets/build/ directory should be included in distributions
|
||||
# but can be regenerated with `npm run build`
|
||||
assets/build/
|
||||
|
||||
# IDE and Editor Configuration
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# OS-Specific Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Cache Files
|
||||
.npm-cache/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# PHP and Development Caches
|
||||
.cache/
|
||||
.PHP_VERSION
|
||||
|
||||
# WordPress Debug Log
|
||||
debug.log
|
||||
|
||||
# Translation Backup Files
|
||||
# Keep .po, .pot, and .mo files in repo
|
||||
# Only ignore editor backup files
|
||||
*.po~
|
||||
*.pot~
|
||||
|
||||
# Distribution Artifacts
|
||||
dist/
|
||||
builds/
|
||||
*.zip
|
||||
|
||||
# Environment Files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Test Coverage
|
||||
coverage/
|
||||
.coverage
|
||||
|
||||
# Temporary Files
|
||||
*.tmp
|
||||
*.temp
|
||||
*.bak
|
||||
|
||||
# macOS Specific
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
._*
|
||||
|
||||
# Linux Temporary Files
|
||||
.directory
|
||||
|
||||
# Editor Lock Files
|
||||
*~
|
||||
*.lock
|
||||
|
||||
# Ignore Everything in Docs Backup (if template/reference only)
|
||||
# Uncomment if ~docs/ is just a reference copy and not part of distribution
|
||||
~docs/
|
||||
506
README-DEV.md
Normal file
506
README-DEV.md
Normal file
@ -0,0 +1,506 @@
|
||||
# Swiss Football Matchdata — Developer Setup Guide
|
||||
|
||||
This guide explains how to set up the development environment after checking out the project from Git and how to generate a distribution-ready package.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, ensure you have the following installed on your system:
|
||||
|
||||
- **Node.js** (LTS 18.x or newer) - [download](https://nodejs.org/)
|
||||
- **npm** (comes with Node.js)
|
||||
- **Git** (for cloning and version control)
|
||||
- **PHP 7.4+** (for running WordPress locally, if developing)
|
||||
- **WordPress** (local development installation recommended)
|
||||
|
||||
Verify installations:
|
||||
|
||||
```bash
|
||||
node --version
|
||||
npm --version
|
||||
php --version
|
||||
```
|
||||
|
||||
## Initial Setup After Git Checkout
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone <repository-url> swiss-football-matchdata
|
||||
cd swiss-football-matchdata
|
||||
```
|
||||
|
||||
### 2. Install Dependencies
|
||||
|
||||
Install all npm dependencies required for building the Gutenberg editor blocks:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
This reads `package.json` and installs:
|
||||
|
||||
- `@wordpress/scripts` — WordPress build tooling (webpack, babel, eslint)
|
||||
|
||||
### 3. Build the Editor Bundle
|
||||
|
||||
Create the compiled editor JavaScript bundle that the plugin requires:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
This command:
|
||||
|
||||
|
||||
- Runs `wp-scripts build --output-path=assets/build`
|
||||
- Uses the custom `webpack.config.js` to ensure output is named `editor-blocks.js`
|
||||
- Generates additional asset files in `assets/build/`
|
||||
- **Result**: `assets/build/editor-blocks.js` is now ready for use
|
||||
|
||||
### 4. Verify the Build
|
||||
|
||||
Check that the compiled bundle exists:
|
||||
|
||||
```bash
|
||||
ls -lh assets/build/editor-blocks.js
|
||||
```
|
||||
|
||||
You should see a JavaScript file (typically 50-150 KB depending on dependencies).
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Live Development with Hot Reload
|
||||
|
||||
For active development with automatic recompilation and browser refresh:
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
This starts the WordPress development server with:
|
||||
|
||||
- **File watching**: Changes to `src/editor-blocks.js` trigger rebuilds
|
||||
- **Hot module reload**: Changes appear in the editor without full page refresh
|
||||
- **Source maps**: Easier debugging in browser DevTools
|
||||
|
||||
Press `Ctrl+C` to stop the server.
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
swiss-football-matchdata/
|
||||
├── src/
|
||||
│ ├── editor-blocks.js # Main editor block components
|
||||
│ ├── format-shortcode.js # Shortcode formatting toolbar
|
||||
│ ├── index.js # Package entry point
|
||||
│ └── [other source files]
|
||||
├── blocks/ # Block definitions (metadata)
|
||||
│ ├── context/
|
||||
│ ├── match-events/
|
||||
│ ├── match-roster/
|
||||
│ ├── schedule/
|
||||
│ ├── standings/
|
||||
│ ├── team-data/
|
||||
│ └── shortcode-inserter/
|
||||
├── assets/
|
||||
│ ├── build/
|
||||
│ │ ├── editor-blocks.js # Compiled output (generated)
|
||||
│ │ ├── editor-blocks.asset.php # Asset dependencies (generated)
|
||||
│ │ └── [other generated assets]
|
||||
│ ├── admin.js / admin.css # Admin-only frontend code
|
||||
│ ├── blocks.js / blocks.css # Public frontend code
|
||||
│ └── [other assets]
|
||||
├── includes/ # PHP backend
|
||||
│ ├── class-swi-foot-admin.php
|
||||
│ ├── class-swi-foot-api.php
|
||||
│ ├── class-swi-foot-blocks.php
|
||||
│ ├── class-swi-foot-rest.php
|
||||
│ └── class-swi-foot-shortcodes.php
|
||||
├── languages/ # Translation files (i18n)
|
||||
├── tests/ # Test files
|
||||
├── webpack.config.js # Custom webpack configuration
|
||||
├── package.json # npm dependencies and scripts
|
||||
├── README.md # User documentation
|
||||
├── DEV-README.md # Legacy build notes
|
||||
└── README-DEV.md # This file
|
||||
```
|
||||
|
||||
## Editing WordPress Blocks
|
||||
|
||||
### Block Files
|
||||
|
||||
Each block is defined in its own directory under `blocks/`:
|
||||
- `block.json` — Block metadata (name, icon, attributes, supports)
|
||||
- The React component code lives in `src/editor-blocks.js`
|
||||
|
||||
### Example Workflow
|
||||
|
||||
1. **Edit block in editor** (`src/editor-blocks.js`):
|
||||
- Add UI elements (SelectControl, TextControl, etc.)
|
||||
- Implement save logic that generates shortcodes
|
||||
- Update attribute definitions
|
||||
|
||||
2. **Test with hot reload**:
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
- Hard-refresh your WordPress editor page
|
||||
- Make changes in `src/editor-blocks.js`
|
||||
- The browser auto-updates (HMR)
|
||||
|
||||
3. **Verify output**:
|
||||
- Insert/edit the block in WordPress
|
||||
- Check browser DevTools → Network → look for `editor-blocks.js`
|
||||
- Look at page source to verify shortcode is generated correctly
|
||||
|
||||
4. **Build for production**:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Code Quality
|
||||
|
||||
### Linting and Formatting
|
||||
|
||||
The `@wordpress/scripts` package includes eslint configuration. WordPress code standards are automatically enforced on `.js` files in the project.
|
||||
|
||||
To fix common issues automatically:
|
||||
|
||||
```bash
|
||||
npx eslint src/ --fix
|
||||
```
|
||||
|
||||
### PHP Code
|
||||
|
||||
PHP files follow standard WordPress coding standards. Check syntax:
|
||||
|
||||
```bash
|
||||
php -l includes/class-swi-foot-blocks.php
|
||||
```
|
||||
|
||||
(or any other PHP file)
|
||||
|
||||
### Managing Translations
|
||||
|
||||
Translation files include:
|
||||
|
||||
- **`.po` files** — Human-editable source translations (one per language)
|
||||
- **`.pot` file** — Translation template (extraction from source code)
|
||||
- **`.mo` files** — Compiled binary translations (generated from `.po` files)
|
||||
|
||||
**Workflow when adding new translatable strings:**
|
||||
|
||||
1. Add translation strings to PHP or JavaScript with proper functions:
|
||||
|
||||
```php
|
||||
__('Text to translate', 'swi_foot_matchdata')
|
||||
_e('Text to display', 'swi_foot_matchdata')
|
||||
```
|
||||
|
||||
2. Extract strings to the `.pot` template file:
|
||||
```bash
|
||||
wp i18n make-pot . languages/swi_foot_matchdata.pot
|
||||
```
|
||||
(Requires WP-CLI installed)
|
||||
|
||||
3. Update existing `.po` translation files from the new template (done by translators in PoEdit or similar)
|
||||
|
||||
4. Generate binary `.mo` files:
|
||||
```bash
|
||||
wp i18n make-mo languages/
|
||||
```
|
||||
|
||||
5. Commit both `.po` and `.mo` files to git:
|
||||
```bash
|
||||
git add languages/*.po languages/*.mo
|
||||
git commit -m "Update translations for [feature/language]"
|
||||
```
|
||||
|
||||
**Note**: Editor backup files (`.po~`) are **not** committed (ignored by `.gitignore`).
|
||||
|
||||
The `.mo` files are essential for distribution — they're included in both git commits and distribution packages so installations work immediately without requiring a build step.
|
||||
|
||||
## Testing in Development
|
||||
|
||||
### Local WordPress Installation
|
||||
|
||||
For testing the complete plugin:
|
||||
|
||||
1. **Place the plugin in WordPress**:
|
||||
|
||||
```bash
|
||||
# Assuming WordPress is at /path/to/wordpress
|
||||
cp -r . /path/to/wordpress/wp-content/plugins/swiss-football-matchdata
|
||||
```
|
||||
|
||||
2. **Activate the plugin**:
|
||||
|
||||
- Go to WordPress Admin → Plugins
|
||||
- Find "Swiss Football Matchdata"
|
||||
- Click "Activate"
|
||||
|
||||
3. **Test blocks**:
|
||||
|
||||
- Create a new post/page
|
||||
- Insert blocks using the editor
|
||||
- Verify blocks are available and functional
|
||||
- Hard-refresh browser to clear cache after rebuilds
|
||||
|
||||
### Browser DevTools Checklist
|
||||
|
||||
- **Console**: Watch for JavaScript errors
|
||||
- **Network**: Verify `editor-blocks.js` loads correctly
|
||||
- **Sources**: Use source maps to debug original `.js` files
|
||||
- **Application → Storage**: Clear LocalStorage/IndexedDB if blocks misbehave
|
||||
|
||||
## Building for Distribution
|
||||
|
||||
### 1. Ensure Clean Build
|
||||
|
||||
```bash
|
||||
# Remove old build artifacts (optional)
|
||||
rm -rf assets/build
|
||||
|
||||
# Install fresh dependencies
|
||||
npm ci # (instead of npm install - more reproducible)
|
||||
|
||||
# Build
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 2. Verify Build Output
|
||||
|
||||
```bash
|
||||
# All required files should exist
|
||||
test -f assets/build/editor-blocks.js && echo "✓ Editor bundle present"
|
||||
test -f assets/build/editor-blocks.asset.php && echo "✓ Asset manifest present"
|
||||
```
|
||||
|
||||
### 3. Create Distribution Package
|
||||
|
||||
#### Method A: Manual Packaging (Recommended)
|
||||
|
||||
```bash
|
||||
# Create a clean directory for the distribution
|
||||
mkdir -p dist
|
||||
PLUGIN_NAME="swiss-football-matchdata"
|
||||
|
||||
# Copy all plugin files (excluding development files)
|
||||
rsync -av --exclude='.git' \
|
||||
--exclude='node_modules' \
|
||||
--exclude='.npm' \
|
||||
--exclude='package-lock.json' \
|
||||
--exclude='.gitignore' \
|
||||
--exclude='README-DEV.md' \
|
||||
--exclude='.DS_Store' \
|
||||
--exclude='*.swp' \
|
||||
--exclude='.idea/' \
|
||||
. dist/$PLUGIN_NAME/
|
||||
|
||||
# Create ZIP archive
|
||||
cd dist
|
||||
zip -r "$PLUGIN_NAME.zip" $PLUGIN_NAME/
|
||||
cd ..
|
||||
|
||||
# Result: dist/swiss-football-matchdata.zip
|
||||
echo "Distribution package created: dist/$PLUGIN_NAME.zip"
|
||||
```
|
||||
|
||||
#### Method B: Using Git Archive (Cleaner)
|
||||
|
||||
If your `.gitignore` is properly configured to exclude development files:
|
||||
|
||||
```bash
|
||||
git archive --output=dist/swiss-football-matchdata.zip HEAD \
|
||||
--prefix=swiss-football-matchdata/
|
||||
```
|
||||
|
||||
This creates the distribution package from committed files only (respecting `.gitignore`).
|
||||
|
||||
### 4. Verify Distribution Package
|
||||
|
||||
```bash
|
||||
# What's in the ZIP?
|
||||
unzip -l dist/swiss-football-matchdata.zip | head -30
|
||||
|
||||
# Should include:
|
||||
# ✓ swiss-football-matchdata.php (main plugin file)
|
||||
# ✓ assets/build/editor-blocks.js (compiled bundle)
|
||||
# ✓ includes/*.php (backend code)
|
||||
# ✓ README.md (user documentation)
|
||||
|
||||
# Should NOT include:
|
||||
# ✗ node_modules/ (development dependencies)
|
||||
# ✗ package.json / package-lock.json
|
||||
# ✗ README-DEV.md (developer guide)
|
||||
# ✗ .git (git history)
|
||||
# ✗ src/ (source code — users don't need it)
|
||||
```
|
||||
|
||||
### 5. Size Check
|
||||
|
||||
The distribution ZIP should be reasonable in size:
|
||||
- **With `assets/build/editor-blocks.js`**: ~150–300 KB
|
||||
- **Typical unzipped size**: ~800 KB–2 MB
|
||||
|
||||
If significantly larger, check for unwanted files:
|
||||
|
||||
```bash
|
||||
unzip -l dist/swiss-football-matchdata.zip | sort -k4 -rn | head -20
|
||||
```
|
||||
|
||||
## Deployment / Installation
|
||||
|
||||
### For Site Administrators (Users)
|
||||
|
||||
Users install the distribution ZIP file normally:
|
||||
|
||||
1. Go to WordPress Admin → Plugins → Add New → Upload Plugin
|
||||
2. Select the `.zip` file
|
||||
3. Activate
|
||||
|
||||
**Important**: The plugin expects `assets/build/editor-blocks.js` to be present. Always run `npm run build` before packaging.
|
||||
|
||||
### For Developers on Target Site
|
||||
|
||||
If deploying to a site where you're also developing:
|
||||
|
||||
```bash
|
||||
# Copy the plugin to WordPress
|
||||
cp -r . /path/to/wordpress/wp-content/plugins/swiss-football-matchdata
|
||||
|
||||
# Navigate to plugin directory
|
||||
cd /path/to/wordpress/wp-content/plugins/swiss-football-matchdata
|
||||
|
||||
# Ensure build is present (if needed)
|
||||
npm ci && npm run build
|
||||
```
|
||||
|
||||
## Continuous Integration (Optional)
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
Add to `.github/workflows/build.yml`:
|
||||
|
||||
```yaml
|
||||
name: Build and Verify
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build editor bundle
|
||||
run: npm run build
|
||||
|
||||
- name: Verify build output
|
||||
run: |
|
||||
test -f assets/build/editor-blocks.js || exit 1
|
||||
test -f assets/build/editor-blocks.asset.php || exit 1
|
||||
|
||||
- name: PHP Syntax Check
|
||||
run: |
|
||||
find includes -name "*.php" -exec php -l {} \;
|
||||
```
|
||||
|
||||
This ensures:
|
||||
- Every push/PR has a valid build
|
||||
- Missing build artifacts fail visibly
|
||||
- PHP code is syntactically correct
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Fails with Module Not Found
|
||||
|
||||
**Problem**: `npm run build` fails with "module not found"
|
||||
|
||||
**Solution**:
|
||||
|
||||
```bash
|
||||
rm package-lock.json
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Changes Don't Appear in Editor
|
||||
|
||||
**Problem**: Edited `src/editor-blocks.js` but changes don't show in WordPress
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Hard-refresh browser: `Cmd+Shift+R` (Mac) or `Ctrl+Shift+R` (Windows/Linux)
|
||||
2. Clear WordPress cache (if caching plugin is active)
|
||||
3. Verify `assets/build/editor-blocks.js` was updated: `ls -l assets/build/editor-blocks.js`
|
||||
4. Check browser console for JavaScript errors
|
||||
|
||||
### `npm start` Doesn't Work
|
||||
|
||||
**Problem**: Development server won't start
|
||||
|
||||
**Solution**:
|
||||
|
||||
```bash
|
||||
# Kill any existing Node process on port 8888
|
||||
lsof -i :8888 | grep -v PID | awk '{print $2}' | xargs kill -9
|
||||
|
||||
# Restart
|
||||
npm start
|
||||
```
|
||||
|
||||
### PHP Errors
|
||||
|
||||
**Problem**: Plugin doesn't activate or causes errors
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Check WordPress error log: `wp-content/debug.log`
|
||||
2. Verify PHP version: `php --version` (should be 7.4+)
|
||||
3. Check file permissions: `chmod -R 755 includes/`
|
||||
4. Look for syntax errors: `php -l includes/class-swi-foot-blocks.php`
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Install deps | `npm install` |
|
||||
| Build for production | `npm run build` |
|
||||
| Development with hot reload | `npm start` |
|
||||
| Create distribution ZIP | `git archive --output=dist/swiss-football-matchdata.zip HEAD --prefix=swiss-football-matchdata/` |
|
||||
| Check PHP syntax | `php -l includes/class-swi-foot-blocks.php` |
|
||||
| Fix linting issues | `npx eslint src/ --fix` |
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [WordPress Block Editor Handbook](https://developer.wordpress.org/block-editor/)
|
||||
- [WordPress Plugin Handbook](https://developer.wordpress.org/plugins/)
|
||||
- [@wordpress/scripts Documentation](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/)
|
||||
- [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/)
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
|
||||
1. Check existing Git issues
|
||||
2. Review comments in the relevant PHP/JavaScript files
|
||||
3. Consult the main [README.md](README.md) for user-facing documentation
|
||||
4. See [DEV-README.md](DEV-README.md) for legacy build notes
|
||||
|
||||
---
|
||||
|
||||
Last updated: March 2026
|
||||
135
README.md
Normal file
135
README.md
Normal file
@ -0,0 +1,135 @@
|
||||
# Swiss Football Matchdata
|
||||
|
||||
Connect to the Swiss Football Association API and display match data, standings, schedules and match elements using flexible shortcodes and Gutenberg blocks.
|
||||
|
||||
## Description
|
||||
|
||||
Swiss Football Matchdata provides a small, focused integration with the Swiss Football Association API so you can embed:
|
||||
|
||||
- Team standings and rankings
|
||||
- Upcoming match schedules
|
||||
- Detailed match information
|
||||
- Individual match elements via shortcodes
|
||||
- Gutenberg blocks for visual composition
|
||||
|
||||
The plugin includes caching, automatic token management, and both shortcodes and Gutenberg blocks for flexibility.
|
||||
|
||||
## Editor Blocks — Usage Guide
|
||||
|
||||
This section explains how the plugin's Gutenberg editor blocks are intended to be used in the editor. If a block is unavailable in your editor, ensure the editor script is enqueued and that no console ReferenceErrors appear.
|
||||
|
||||
### Insertions Methods
|
||||
|
||||
#### 1. Paragraph Toolbar Shortcode Button (Recommended)
|
||||
|
||||
- **Purpose**: Quickly insert any Swiss Football shortcode directly into paragraphs or other text blocks.
|
||||
- **How to use**:
|
||||
1. Click in a paragraph block where you want to insert content.
|
||||
2. Look for the **shortcode icon** in the paragraph's formatting toolbar (top of the editor).
|
||||
3. Click the button to open the Shortcode insertion modal.
|
||||
4. Select the shortcode type (e.g., "Full Match Display", "Score", etc.).
|
||||
5. Choose a team and/or match from the dropdowns.
|
||||
6. Click "Insert Shortcode" — the shortcode text is inserted at your cursor.
|
||||
- **Notes**:
|
||||
- This is the fastest way to add shortcodes inline.
|
||||
- Works in any paragraph or text-based block.
|
||||
- The shortcode generates dynamically at render time on the frontend.
|
||||
|
||||
#### 2. Team Data Block
|
||||
|
||||
- **Purpose**: Create and insert shortcodes using a guided block interface.
|
||||
- **How to use**:
|
||||
1. Insert the `Swiss Football Team Data` block in your post/page.
|
||||
2. Open the Inspector Controls (right sidebar).
|
||||
3. Select a team from the dropdown.
|
||||
4. Choose what to display (Standings or Match).
|
||||
5. If you chose Match, select the specific match.
|
||||
6. Save the post — the block generates and saves the appropriate shortcode.
|
||||
- **Best for**: Users who prefer a visual block interface over toolbars.
|
||||
|
||||
#### 3. Context Provider Block (for scoped data)
|
||||
|
||||
- **Purpose**: Provide shared contextual data (season, club/team selection) for child blocks placed inside it. It saves JSON to the frontend as a `data-swi-foot-context` attribute on the provider's wrapper element.
|
||||
- **How to use**:
|
||||
1. Insert the `SWI Football Context` block at the place in the document that should scope its child blocks (usually near the top of the content where you want the team/season context to apply).
|
||||
2. Open the block's Inspector Controls (right sidebar) to set context values such as `Season` or `Team` where supported.
|
||||
3. Drop child blocks inside the Context block — these child blocks will inherit the contextual settings.
|
||||
4. On save, the provider writes a `data-swi-foot-context` attribute containing a compact JSON string with the configured keys and values. This attribute is used by frontend renderers.
|
||||
|
||||
#### 4. Specialized Blocks (Standings, Schedule, Roster, Events)
|
||||
|
||||
- **Swiss Football Standings**
|
||||
- Purpose: Display a league standings table for a team.
|
||||
- How to use: Insert the block, select a team from Inspector Controls.
|
||||
|
||||
- **Swiss Football Schedule**
|
||||
- Purpose: Display upcoming matches for a team.
|
||||
- How to use: Insert the block, select a team and set the match limit.
|
||||
|
||||
- **Swiss Football Match Roster**
|
||||
- Purpose: Show a roster for a specific match. Works best inside a Context Provider.
|
||||
- How to use: Insert the block, select a team and match. Optionally configure which side (home/away) and whether to include bench players.
|
||||
|
||||
- **Swiss Football Match Events**
|
||||
- Purpose: Display live events (goals, cards, substitutions) for a match. Works best inside a Context Provider.
|
||||
- How to use: Insert the block, select a team and match. Configure auto-refresh interval for live updates.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- **Swiss Football Shortcode Inserter block** — This block is deprecated in favor of the paragraph toolbar button above. It has been retained for backwards compatibility with existing posts but is no longer recommended for new content.
|
||||
|
||||
## Shortcodes
|
||||
|
||||
Shortcodes provide additional flexibility where blocks are not desirable. Examples:
|
||||
|
||||
```html
|
||||
[swi_foot_match match_id="12345"]
|
||||
[swi_foot_match_home_team match_id="12345"]
|
||||
[swi_foot_match_date match_id="12345" format="d.m.Y"]
|
||||
```
|
||||
|
||||
Refer to the original `readme.txt` for a complete list of shortcodes and their parameters.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Upload the plugin files to `/wp-content/plugins/swiss-football-matchdata/`.
|
||||
2. Activate the plugin via Plugins → Installed Plugins.
|
||||
3. Go to Settings → Swiss Football and configure your API credentials (base URL, application key, pass, Verein ID, Season ID).
|
||||
4. Use the Shortcode helper or Gutenberg blocks in your posts/pages.
|
||||
|
||||
## Configuration
|
||||
|
||||
- **API Base URL**: Set the API endpoint for Swiss Football.
|
||||
- **API Username / Password**: Application key and password provided by Swiss Football.
|
||||
- **Verein ID / Season ID**: used to scope team/season data.
|
||||
- **Cache Duration**: how long API responses are cached (default is 30 seconds; configurable).
|
||||
|
||||
## Developer Notes
|
||||
|
||||
- Block registrations and editor scripts live in:
|
||||
- [includes/class-swi-foot-blocks.php](includes/class-swi-foot-blocks.php)
|
||||
- [assets/editor-blocks.js](assets/editor-blocks.js)
|
||||
- [assets/build/index.js](assets/build/index.js)
|
||||
|
||||
- The editor script includes defensive `safeRegisterBlockType` and `safeRegisterFormatType` wrappers to avoid runtime errors when WordPress' editor APIs are not available.
|
||||
- Blocks are written to use `apiVersion: 3` and expect modern Gutenberg APIs.
|
||||
|
||||
## Headless / Automated Checks
|
||||
|
||||
For quick verification that the built bundle registers the plugin's blocks and formats, a small Node-based test helper exists at `test/register-check.js`.
|
||||
|
||||
Run it locally with:
|
||||
|
||||
```bash
|
||||
node test/register-check.js
|
||||
```
|
||||
|
||||
This script stubs a minimal `window.wp` environment to confirm that `swi-foot/context` and the inline format are registered by the built bundle.
|
||||
|
||||
## Support
|
||||
|
||||
For support or bug reports, open an issue in the project's tracker or contact the plugin author.
|
||||
|
||||
## License
|
||||
|
||||
GPL v2 or later — see `readme.txt` for the original header and license URI.
|
||||
328
assets/admin.css
Normal file
328
assets/admin.css
Normal file
@ -0,0 +1,328 @@
|
||||
/* Swiss Football Admin Styles */
|
||||
|
||||
/* Main admin sections */
|
||||
.swi-foot-admin-section {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.swi-foot-admin-section h2 {
|
||||
margin-bottom: 15px;
|
||||
color: #23282d;
|
||||
}
|
||||
|
||||
/* Teams grid layout */
|
||||
.swi-foot-teams-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.team-card {
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
background: #f5f7fa;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.team-card:hover {
|
||||
background: #f0f0f0;
|
||||
border-color: #007cba;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.team-card strong {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #23282d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.team-card small {
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Shortcodes help styling */
|
||||
.swi-foot-shortcodes-help {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.shortcode-group {
|
||||
margin-bottom: 15px;
|
||||
padding: 12px;
|
||||
background: #eef3f7;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #007cba;
|
||||
}
|
||||
|
||||
.shortcode-group strong {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #111;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.shortcode-group code {
|
||||
display: block;
|
||||
margin: 4px 0;
|
||||
padding: 4px 6px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.shortcode-group code:hover {
|
||||
background: #f0f8ff;
|
||||
border-color: #007cba;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.shortcode-group code:active {
|
||||
background: #e6f3ff;
|
||||
}
|
||||
|
||||
.shortcode-group ul {
|
||||
margin-top: 8px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.shortcode-group li {
|
||||
margin: 3px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.shortcode-group li code {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 1px 3px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.shortcode-group li code:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Status indicators */
|
||||
#refresh-status,
|
||||
#cache-status,
|
||||
#connection-status {
|
||||
margin-left: 12px;
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#refresh-status.success,
|
||||
#cache-status.success,
|
||||
#connection-status.success {
|
||||
color: #46b450;
|
||||
}
|
||||
|
||||
#refresh-status.error,
|
||||
#cache-status.error,
|
||||
#connection-status.error {
|
||||
color: #dc3232;
|
||||
}
|
||||
|
||||
/* Quick shortcode reference */
|
||||
.shortcode-quick-ref {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.shortcode-examples h4 {
|
||||
margin-bottom: 15px;
|
||||
color: #23282d;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.shortcode-examples p {
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.shortcode-examples strong {
|
||||
color: #23282d;
|
||||
}
|
||||
|
||||
.shortcode-examples code {
|
||||
background: #f1f1f1;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Button enhancements */
|
||||
.button {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Settings form styling */
|
||||
.form-table th {
|
||||
padding-top: 15px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.form-table td {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.form-table input[type="text"],
|
||||
.form-table input[type="url"],
|
||||
.form-table input[type="password"],
|
||||
.form-table input[type="number"] {
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-table input[type="text"]:focus,
|
||||
.form-table input[type="url"]:focus,
|
||||
.form-table input[type="password"]:focus,
|
||||
.form-table input[type="number"]:focus {
|
||||
border-color: #007cba;
|
||||
box-shadow: 0 0 0 1px #007cba;
|
||||
}
|
||||
|
||||
.form-table .description {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* Notice styling */
|
||||
.notice {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.notice.notice-success {
|
||||
border-left-color: #46b450;
|
||||
}
|
||||
|
||||
.notice.notice-error {
|
||||
border-left-color: #dc3232;
|
||||
}
|
||||
|
||||
/* Meta box styling in post editor */
|
||||
#swi-foot-shortcodes .inside {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#swi-foot-shortcodes .shortcode-group {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #ddd;
|
||||
border-left: 3px solid #007cba;
|
||||
}
|
||||
|
||||
/* Loading states */
|
||||
.loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: -10px 0 0 -10px;
|
||||
border: 2px solid #f3f3f3;
|
||||
border-top: 2px solid #007cba;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.swi-foot-teams-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.shortcode-group code {
|
||||
font-size: 10px;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
.form-table th,
|
||||
.form-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.form-table th {
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark mode support for WordPress admin */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.team-card {
|
||||
background: #2c2c2c;
|
||||
border-color: #555;
|
||||
color: #f5f7fa;
|
||||
}
|
||||
|
||||
.team-card:hover {
|
||||
background: #383838;
|
||||
}
|
||||
.team-card strong {
|
||||
color: #F4F4F4;
|
||||
}
|
||||
|
||||
.shortcode-group {
|
||||
background: #2c2c2c;
|
||||
border-left-color: #00a0d2;
|
||||
color: #eef6fb;
|
||||
}
|
||||
|
||||
.shortcode-group code {
|
||||
background: #1e1e1e;
|
||||
border-color: #555;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.shortcode-group code:hover {
|
||||
background: #383838;
|
||||
border-color: #00a0d2;
|
||||
}
|
||||
|
||||
.shortcode-quick-ref {
|
||||
background: #2c2c2c;
|
||||
border-color: #555;
|
||||
color: #f3f7fb;
|
||||
}
|
||||
}
|
||||
94
assets/admin.js
Normal file
94
assets/admin.js
Normal file
@ -0,0 +1,94 @@
|
||||
// Swiss Football Admin JavaScript
|
||||
jQuery(document).ready(function($) {
|
||||
'use strict';
|
||||
|
||||
// Refresh teams functionality
|
||||
$('#refresh-teams').on('click', function() {
|
||||
var $button = $(this);
|
||||
var $status = $('#refresh-status');
|
||||
|
||||
$button.prop('disabled', true).text('Refreshing...');
|
||||
$status.text('').removeClass('success error');
|
||||
|
||||
fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/refresh-teams', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) {
|
||||
return resp.json();
|
||||
}).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$status.text('Teams refreshed successfully!').addClass('success');
|
||||
setTimeout(function() { location.reload(); }, 1500);
|
||||
} else {
|
||||
$status.text('Error: ' + (response.error || 'Unknown')).addClass('error');
|
||||
}
|
||||
}).catch(function() {
|
||||
$status.text('Network error occurred.').addClass('error');
|
||||
}).finally(function() {
|
||||
$button.prop('disabled', false).text('Refresh Teams List');
|
||||
});
|
||||
});
|
||||
|
||||
// Clear cache functionality
|
||||
$('#clear-cache').on('click', function() {
|
||||
var $button = $(this);
|
||||
var $status = $('#cache-status');
|
||||
|
||||
if (!confirm('Are you sure you want to clear the match data cache?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$button.prop('disabled', true);
|
||||
$status.text('Clearing cache...').removeClass('success error');
|
||||
|
||||
fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/clear-cache', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) { return resp.json(); }).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$status.text('Cache cleared successfully!').addClass('success');
|
||||
setTimeout(function() { location.reload(); }, 1000);
|
||||
} else {
|
||||
$status.text('Error clearing cache.').addClass('error');
|
||||
}
|
||||
}).catch(function() {
|
||||
$status.text('Error clearing cache.').addClass('error');
|
||||
}).finally(function() {
|
||||
$button.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
// Test API connection
|
||||
$('#test-connection').on('click', function() {
|
||||
var $button = $(this);
|
||||
var $status = $('#connection-status');
|
||||
|
||||
$button.prop('disabled', true).text('Testing...');
|
||||
$status.text('').removeClass('success error');
|
||||
|
||||
fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/test-connection', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) { return resp.json(); }).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$status.text('Connection successful!').addClass('success');
|
||||
} else {
|
||||
$status.text('Connection failed: ' + (response.error || 'Unknown')).addClass('error');
|
||||
}
|
||||
}).catch(function() {
|
||||
$status.text('Network error occurred.').addClass('error');
|
||||
}).finally(function() {
|
||||
$button.prop('disabled', false).text('Test API Connection');
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-save settings notice
|
||||
$('form').on('submit', function() {
|
||||
$('<div class="notice notice-info is-dismissible"><p>Settings saved! The plugin will automatically refresh API tokens as needed.</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
});
|
||||
|
||||
});
|
||||
381
assets/blocks.css
Normal file
381
assets/blocks.css
Normal file
@ -0,0 +1,381 @@
|
||||
/* Swiss Football Blocks Styles - Minimal */
|
||||
|
||||
/* Editor block wrapper - identifies blocks in editor */
|
||||
.swi-foot-editor-block {
|
||||
background-color: #f5f5f5;
|
||||
border: 0.5px solid #d0d0d0;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Block container styles */
|
||||
.swi-foot-standings,
|
||||
.swi-foot-schedule,
|
||||
.swi-foot-match-container {
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
border: 0.5px solid #d0d0d0;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* Table styles */
|
||||
.swi-foot-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.swi-foot-table th,
|
||||
.swi-foot-table td {
|
||||
padding: 10px 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.swi-foot-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.swi-foot-table tr.highlight {
|
||||
background-color: #fff3cd;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Event item styles */
|
||||
.swi-foot-event {
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #e9ecef;
|
||||
border-left: 4px solid #007cba;
|
||||
border-radius: 6px;
|
||||
background-color: #fdfdfe;
|
||||
}
|
||||
|
||||
.event-date {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.event-teams {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
/* Match container - center aligned */
|
||||
.swi-foot-match-container {
|
||||
text-align: center;
|
||||
max-width: 450px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.match-teams {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.match-teams .vs {
|
||||
margin: 0 15px;
|
||||
color: #6c757d;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.match-date {
|
||||
font-size: 16px;
|
||||
color: #495057;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.match-score {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
color: #007cba;
|
||||
margin: 15px 0;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.match-status {
|
||||
font-size: 12px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 15px;
|
||||
background-color: #e7f3ff;
|
||||
color: #007cba;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Inline shortcodes */
|
||||
.swi-foot-inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.swi-foot-inline.home-team,
|
||||
.swi-foot-inline.away-team {
|
||||
font-weight: 600;
|
||||
color: #007cba;
|
||||
}
|
||||
|
||||
.swi-foot-inline.score {
|
||||
font-weight: 700;
|
||||
color: #007cba;
|
||||
}
|
||||
|
||||
.swi-foot-inline.status {
|
||||
background: #007cba;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Error messages */
|
||||
.swi-foot-error {
|
||||
color: #721c24;
|
||||
padding: 12px 16px;
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-radius: 6px;
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.swi-foot-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.swi-foot-table th,
|
||||
.swi-foot-table td {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
|
||||
.swi-foot-table th:nth-child(n+6),
|
||||
.swi-foot-table td:nth-child(n+6) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.swi-foot-table th:nth-child(n+4),
|
||||
.swi-foot-table td:nth-child(n+4) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enhanced Events Display Styles */
|
||||
.swi-foot-events {
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
background-color: #fafafa;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.swi-foot-events h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.events-timeline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.event-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background-color: white;
|
||||
border-left: 4px solid #007cba;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.event-item:hover {
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.event-minute {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 70px;
|
||||
padding: 8px 10px;
|
||||
background-color: #f0f6ff;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
color: #007cba;
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.minute-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.event-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.event-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.event-type-info {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.event-type {
|
||||
font-size: 15px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.event-subtype {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.team-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 50px;
|
||||
height: 50px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.team-logo img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.team-name-fallback {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
background-color: #e8e8e8;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.event-details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.player-info {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.substitute-info {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
background-color: #fff3cd;
|
||||
border-radius: 3px;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
/* Team Logo Shortcodes */
|
||||
.swi-foot-team-logo {
|
||||
max-width: 120px;
|
||||
max-height: 120px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.swi-foot-team-logo.home-team {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.swi-foot-team-logo.away-team {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* Responsive Design for Events */
|
||||
@media (max-width: 768px) {
|
||||
.event-item {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.event-header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.team-logo {
|
||||
order: -1;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.minute-time {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.event-minute {
|
||||
min-width: 60px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.event-details {
|
||||
font-size: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.swi-foot-team-logo {
|
||||
max-width: 80px;
|
||||
max-height: 80px;
|
||||
}
|
||||
}
|
||||
297
assets/blocks.js
Normal file
297
assets/blocks.js
Normal file
@ -0,0 +1,297 @@
|
||||
// Swiss Football Blocks Frontend JavaScript
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
// Team selector functionality for blocks
|
||||
window.swiFootSelectTeam = function(teamId, blockType) {
|
||||
if (!teamId) return;
|
||||
|
||||
// This could be enhanced to use AJAX for dynamic loading
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('swi_foot_team_' + blockType, teamId);
|
||||
window.location.href = url.toString();
|
||||
};
|
||||
|
||||
// Auto-refresh functionality for events shortcodes
|
||||
$(document).ready(function() {
|
||||
initEventRefresh();
|
||||
setupShortcodeHelpers();
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize event refresh polling for all match event shortcodes on page
|
||||
*
|
||||
* Finds all `.swi-foot-events[data-match-id][data-refresh]` elements and sets up
|
||||
* automatic polling based on the configured refresh interval. Respects match status
|
||||
* (does not update UI until match starts, stops polling when match ends).
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function initEventRefresh() {
|
||||
const $eventContainers = $('.swi-foot-events[data-match-id][data-refresh]');
|
||||
|
||||
if ($eventContainers.length === 0) return;
|
||||
|
||||
$eventContainers.each(function() {
|
||||
const $container = $(this);
|
||||
const matchId = $container.data('match-id');
|
||||
const interval = parseInt($container.data('refresh')) || 30;
|
||||
|
||||
if (!matchId) return;
|
||||
|
||||
// Initial check: determine if match has started and if it should refresh
|
||||
checkMatchStatusAndRefresh($container, matchId, interval);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize polling interval for match events
|
||||
*
|
||||
* Sets up a setInterval that periodically fetches the latest events for a match.
|
||||
* Automatically clears the interval when the match ends.
|
||||
*
|
||||
* @param {jQuery} $container - jQuery element for the event container
|
||||
* @param {string} matchId - ID of the match to fetch events for
|
||||
* @param {number} interval - Refresh interval in seconds (e.g., 30)
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkMatchStatusAndRefresh($container, matchId, interval) {
|
||||
fetchMatchEvents($container, matchId);
|
||||
|
||||
// Set up polling - will stop automatically once match ends
|
||||
const pollInterval = setInterval(function() {
|
||||
const hasEnded = $container.data('match-ended');
|
||||
|
||||
// Stop polling if match has ended
|
||||
if (hasEnded) {
|
||||
clearInterval(pollInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
fetchMatchEvents($container, matchId);
|
||||
}, interval * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch latest match events from REST API
|
||||
*
|
||||
* Makes an authenticated request to `/wp-json/swi-foot/v1/events/{matchId}` to
|
||||
* retrieve the current list of match events. Updates container data with match
|
||||
* status (started/ended). Only updates the DOM if the match has started.
|
||||
*
|
||||
* @param {jQuery} $container - jQuery element for the event container
|
||||
* @param {string} matchId - Match ID to fetch events for
|
||||
* @returns {void}
|
||||
*/
|
||||
function fetchMatchEvents($container, matchId) {
|
||||
const restUrl = window.swiFootRest ? window.swiFootRest.rest_url : '/wp-json';
|
||||
const nonce = window.swiFootRest ? window.swiFootRest.rest_nonce : '';
|
||||
|
||||
// Get event order from data attribute
|
||||
const eventOrder = $container.data('event-order') || 'dynamic';
|
||||
|
||||
// Build URL with event_order parameter
|
||||
let url = restUrl + 'swi-foot/v1/events/' + encodeURIComponent(matchId);
|
||||
if (eventOrder && eventOrder !== 'dynamic') {
|
||||
url += '?event_order=' + encodeURIComponent(eventOrder);
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'X-WP-Nonce': nonce
|
||||
}
|
||||
})
|
||||
.then(function(resp) {
|
||||
if (!resp.ok) throw new Error('Failed to fetch events');
|
||||
return resp.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (!data) return;
|
||||
|
||||
// Track match status for polling control
|
||||
$container.data('match-started', data.hasMatchStarted);
|
||||
$container.data('match-ended', data.hasMatchEnded);
|
||||
|
||||
// Only update if match has started
|
||||
if (!data.hasMatchStarted) {
|
||||
return; // Don't update UI, match hasn't started yet
|
||||
}
|
||||
|
||||
// Update events list
|
||||
updateEventsList($container, data.events);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Error fetching match events:', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the events timeline display with new events
|
||||
*
|
||||
* Renders an HTML list of match events from the API response, sorted newest first.
|
||||
* Handles empty event lists with a localized message. Updates the `.events-timeline`
|
||||
* element within the container.
|
||||
*
|
||||
* @param {jQuery} $container - jQuery element for the event container
|
||||
* @param {Array<Object>} events - Array of event objects with matchMinute, eventTypeName, playerName, teamName
|
||||
* @returns {void}
|
||||
*/
|
||||
function updateEventsList($container, events) {
|
||||
const $timeline = $container.find('.events-timeline');
|
||||
if ($timeline.length === 0) return;
|
||||
|
||||
// Build HTML for events
|
||||
let html = '';
|
||||
if (events && events.length > 0) {
|
||||
events.forEach(function(event) {
|
||||
html += '<div class="event-item">';
|
||||
html += '<div class="event-time">' + (event.matchMinute || '') + '\'</div>';
|
||||
html += '<div class="event-content">';
|
||||
html += '<div class="event-type">' + (event.eventTypeName || '') + '</div>';
|
||||
html += '<div class="event-details">';
|
||||
if (event.playerName) {
|
||||
html += '<span class="player">' + event.playerName + '</span>';
|
||||
}
|
||||
if (event.teamName) {
|
||||
html += '<span class="team">(' + event.teamName + ')</span>';
|
||||
}
|
||||
html += '</div></div></div>';
|
||||
});
|
||||
} else {
|
||||
html = '<p>' + (window.swiFootNoEvents || 'No events recorded yet.') + '</p>';
|
||||
}
|
||||
|
||||
$timeline.html(html);
|
||||
}
|
||||
|
||||
function setupShortcodeHelpers() {
|
||||
// Add copy functionality to shortcode examples in admin
|
||||
if ($('.shortcode-group code').length > 0) {
|
||||
$('.shortcode-group code').each(function() {
|
||||
const $code = $(this);
|
||||
$code.css('cursor', 'pointer');
|
||||
$code.attr('title', 'Click to copy shortcode');
|
||||
|
||||
$code.on('click', function() {
|
||||
const shortcode = $(this).text();
|
||||
copyToClipboard(shortcode);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
showNotification('Shortcode copied to clipboard!', 'success');
|
||||
}).catch(function() {
|
||||
fallbackCopyTextToClipboard(text);
|
||||
});
|
||||
} else {
|
||||
fallbackCopyTextToClipboard(text);
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopyTextToClipboard(text) {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
textArea.style.top = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
showNotification('Shortcode copied to clipboard!', 'success');
|
||||
} catch (err) {
|
||||
showNotification('Failed to copy shortcode', 'error');
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
function showNotification(message, type) {
|
||||
const $notification = $('<div class="swi-foot-notification swi-foot-notification-' + type + '">' + message + '</div>');
|
||||
|
||||
$notification.css({
|
||||
position: 'fixed',
|
||||
top: '20px',
|
||||
right: '20px',
|
||||
padding: '10px 15px',
|
||||
borderRadius: '4px',
|
||||
color: '#fff',
|
||||
fontSize: '14px',
|
||||
zIndex: '10000',
|
||||
opacity: '0',
|
||||
transform: 'translateY(-20px)',
|
||||
transition: 'all 0.3s ease'
|
||||
});
|
||||
|
||||
if (type === 'success') {
|
||||
$notification.css('backgroundColor', '#28a745');
|
||||
} else {
|
||||
$notification.css('backgroundColor', '#dc3545');
|
||||
}
|
||||
|
||||
$('body').append($notification);
|
||||
|
||||
// Animate in
|
||||
setTimeout(function() {
|
||||
$notification.css({
|
||||
opacity: '1',
|
||||
transform: 'translateY(0)'
|
||||
});
|
||||
}, 100);
|
||||
|
||||
// Remove after 3 seconds
|
||||
setTimeout(function() {
|
||||
$notification.css({
|
||||
opacity: '0',
|
||||
transform: 'translateY(-20px)'
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
$notification.remove();
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Initialize tooltips for match elements
|
||||
$(document).ready(function() {
|
||||
$('.swi-foot-match-container [title]').each(function() {
|
||||
const $element = $(this);
|
||||
$element.on('mouseenter', function() {
|
||||
// Simple tooltip implementation
|
||||
const title = $element.attr('title');
|
||||
if (title) {
|
||||
const $tooltip = $('<div class="swi-foot-tooltip">' + title + '</div>');
|
||||
$tooltip.css({
|
||||
position: 'absolute',
|
||||
background: '#333',
|
||||
color: '#fff',
|
||||
padding: '5px 8px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '12px',
|
||||
zIndex: '1000',
|
||||
whiteSpace: 'nowrap'
|
||||
});
|
||||
|
||||
$('body').append($tooltip);
|
||||
|
||||
const rect = this.getBoundingClientRect();
|
||||
$tooltip.css({
|
||||
left: rect.left + (rect.width / 2) - ($tooltip.outerWidth() / 2),
|
||||
top: rect.top - $tooltip.outerHeight() - 5
|
||||
});
|
||||
}
|
||||
}).on('mouseleave', function() {
|
||||
$('.swi-foot-tooltip').remove();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
156
assets/post-context.js
Normal file
156
assets/post-context.js
Normal file
@ -0,0 +1,156 @@
|
||||
(function(wp){
|
||||
const { registerPlugin } = wp.plugins;
|
||||
const PluginDocumentSettingPanel = (wp && wp.editor && wp.editor.PluginDocumentSettingPanel) ? wp.editor.PluginDocumentSettingPanel : (wp && wp.editPost && wp.editPost.PluginDocumentSettingPanel) ? wp.editPost.PluginDocumentSettingPanel : null;
|
||||
const { withSelect, withDispatch } = wp.data;
|
||||
const { useState, useEffect } = wp.element;
|
||||
const { SelectControl, PanelRow, Spinner, Button } = wp.components;
|
||||
|
||||
function PostContextPanel(props) {
|
||||
const meta = (props.meta || {});
|
||||
const ctx = meta.swi_foot_context || {};
|
||||
|
||||
if (!PluginDocumentSettingPanel) return null;
|
||||
|
||||
const [teams, setTeams] = useState([]);
|
||||
const [matches, setMatches] = useState([]);
|
||||
const [loadingTeams, setLoadingTeams] = useState(false);
|
||||
const [loadingMatches, setLoadingMatches] = useState(false);
|
||||
|
||||
const restBaseRaw = (window.swi_foot_post_context && window.swi_foot_post_context.rest_url) || '/wp-json/swi-foot/v1';
|
||||
const restBase = restBaseRaw.replace(/\/$/, '');
|
||||
|
||||
useEffect(() => {
|
||||
setLoadingTeams(true);
|
||||
fetch(restBase + '/teams', { credentials: 'same-origin', headers: { 'X-WP-Nonce': window.swi_foot_post_context.rest_nonce } })
|
||||
.then(r => r.json()).then(data => {
|
||||
if (Array.isArray(data)) setTeams(data);
|
||||
}).catch(() => {}).finally(() => setLoadingTeams(false));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const teamId = ctx.team_id || '';
|
||||
if (!teamId) {
|
||||
setMatches([]);
|
||||
return;
|
||||
}
|
||||
setLoadingMatches(true);
|
||||
fetch(restBase + '/matches?team_id=' + encodeURIComponent(teamId), { credentials: 'same-origin', headers: { 'X-WP-Nonce': window.swi_foot_post_context.rest_nonce } })
|
||||
.then(r => r.json()).then(data => {
|
||||
if (Array.isArray(data)) {
|
||||
setMatches(data);
|
||||
|
||||
// If no match selected yet, pick the next future match by date
|
||||
try {
|
||||
if (!ctx.match_id) {
|
||||
const now = Date.now();
|
||||
let next = null;
|
||||
data.forEach(m => {
|
||||
if (!m.matchDate) return;
|
||||
const t = Date.parse(m.matchDate);
|
||||
if (isNaN(t)) return;
|
||||
if (t > now) {
|
||||
if (!next || t < next.time) next = { time: t, id: m.matchId };
|
||||
}
|
||||
});
|
||||
if (next && next.id) {
|
||||
updateContext('match_id', next.id);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}).catch(() => {}).finally(() => setLoadingMatches(false));
|
||||
}, [ctx.team_id]);
|
||||
|
||||
function updateContext(key, val) {
|
||||
const newMeta = Object.assign({}, meta, { swi_foot_context: Object.assign({}, ctx, { [key]: val }) });
|
||||
props.editPost({ meta: newMeta });
|
||||
}
|
||||
|
||||
const teamOptions = [{ value: '', label: '— use post default —' }].concat(
|
||||
teams.map(t => ({ value: t.teamId, label: t.teamName + (t.teamLeagueName ? ' (' + t.teamLeagueName + ')' : '') }))
|
||||
);
|
||||
|
||||
const matchOptions = [{ value: '', label: '— use team/season default —' }].concat(
|
||||
matches.map(m => {
|
||||
var datePart = m.matchDate ? (m.matchDate.substr(0,10) + ' — ') : '';
|
||||
var opposing = m.matchId;
|
||||
var role = '';
|
||||
try {
|
||||
var selectedTeam = (ctx.team_id || '').toString();
|
||||
var teamAId = (m.teamAId || m.homeTeamId || '').toString();
|
||||
var teamBId = (m.teamBId || m.awayTeamId || '').toString();
|
||||
var teamAName = m.teamNameA || m.homeTeamName || '';
|
||||
var teamBName = m.teamNameB || m.awayTeamName || '';
|
||||
if (selectedTeam && selectedTeam === teamAId) {
|
||||
opposing = teamBName || teamAName || m.matchId;
|
||||
role = 'home';
|
||||
} else if (selectedTeam && selectedTeam === teamBId) {
|
||||
opposing = teamAName || teamBName || m.matchId;
|
||||
role = 'away';
|
||||
} else {
|
||||
opposing = teamBName || teamAName || m.teamName || m.matchId;
|
||||
}
|
||||
} catch (e) {
|
||||
opposing = m.teamNameB || m.teamNameA || m.awayTeamName || m.homeTeamName || m.teamName || m.matchId;
|
||||
}
|
||||
var label = datePart + opposing + (role ? (' (' + role + ')') : '');
|
||||
return { value: m.matchId, label: label };
|
||||
})
|
||||
);
|
||||
|
||||
return wp.element.createElement(
|
||||
PluginDocumentSettingPanel,
|
||||
{ title: 'Swiss Football Context', className: 'swi-foot-post-context-panel' },
|
||||
wp.element.createElement(PanelRow, null,
|
||||
wp.element.createElement('div', { style: { width: '100%' } },
|
||||
wp.element.createElement('label', null, 'Season'),
|
||||
wp.element.createElement('input', {
|
||||
type: 'number',
|
||||
value: ctx.season || window.swi_foot_post_context.default_season,
|
||||
onChange: function(e){ updateContext('season', e.target.value); },
|
||||
style: { width: '100%' }
|
||||
})
|
||||
)
|
||||
),
|
||||
wp.element.createElement(PanelRow, null,
|
||||
wp.element.createElement('div', { style: { width: '100%' } },
|
||||
wp.element.createElement('label', null, 'Team'),
|
||||
loadingTeams ? wp.element.createElement(Spinner, null) : wp.element.createElement(SelectControl, {
|
||||
value: ctx.team_id || '',
|
||||
options: teamOptions,
|
||||
onChange: function(v){ updateContext('team_id', v); },
|
||||
__next40pxDefaultSize: true,
|
||||
__nextHasNoMarginBottom: true
|
||||
})
|
||||
)
|
||||
),
|
||||
wp.element.createElement(PanelRow, null,
|
||||
wp.element.createElement('div', { style: { width: '100%' } },
|
||||
wp.element.createElement('label', null, 'Match'),
|
||||
loadingMatches ? wp.element.createElement(Spinner, null) : wp.element.createElement(SelectControl, {
|
||||
value: ctx.match_id || '',
|
||||
options: matchOptions,
|
||||
onChange: function(v){ updateContext('match_id', v); },
|
||||
__next40pxDefaultSize: true,
|
||||
__nextHasNoMarginBottom: true
|
||||
})
|
||||
)
|
||||
),
|
||||
wp.element.createElement(PanelRow, null,
|
||||
wp.element.createElement('div', { style: { width: '100%', color: '#666', fontSize: '12px', marginTop: '6px' } },
|
||||
ctx.match_id ? ('Match ID: ' + ctx.match_id) : ''
|
||||
)
|
||||
),
|
||||
wp.element.createElement(PanelRow, null,
|
||||
wp.element.createElement(Button, { isSecondary: true, onClick: function(){ props.editPost({ meta: Object.assign({}, meta, { swi_foot_context: {} }) }); } }, 'Clear Context')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const Connected = withSelect( (select) => ({ meta: select('core/editor').getEditedPostAttribute('meta') || {} }) )( withDispatch( (dispatch) => ({ editPost: dispatch('core/editor').editPost }) )( PostContextPanel ) );
|
||||
|
||||
registerPlugin('swi-foot-post-context', { render: Connected });
|
||||
|
||||
})(window.wp);
|
||||
17
blocks/context/block.json
Normal file
17
blocks/context/block.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/context",
|
||||
"title": "Swiss Football Context (container)",
|
||||
"category": "widgets",
|
||||
"icon": "admin-site",
|
||||
"description": "Provides a team/season/match context to child blocks. Children will inherit these settings unless they override them.",
|
||||
"providesContext": {
|
||||
"swi-foot/context": "swi_foot_context"
|
||||
},
|
||||
"attributes": {
|
||||
"swi_foot_context": {
|
||||
"type": "object",
|
||||
"default": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
blocks/match-events/block.json
Normal file
16
blocks/match-events/block.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/match-events",
|
||||
"title": "Swiss Football Match Events",
|
||||
"category": "widgets",
|
||||
"icon": "list-view",
|
||||
"description": "Live match events with optional auto-refresh.",
|
||||
"attributes": {
|
||||
"selectedTeam": { "type": "string", "default": "" },
|
||||
"selectedMatch": { "type": "string", "default": "" },
|
||||
"refreshInterval": { "type": "number", "default": 30 },
|
||||
"eventOrder": { "type": "string", "default": "dynamic" }
|
||||
}
|
||||
,
|
||||
"usesContext": ["swi-foot/context"]
|
||||
}
|
||||
16
blocks/match-roster/block.json
Normal file
16
blocks/match-roster/block.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/match-roster",
|
||||
"title": "Swiss Football Match Roster",
|
||||
"category": "widgets",
|
||||
"icon": "groups",
|
||||
"description": "Display match roster for a selected match and side.",
|
||||
"attributes": {
|
||||
"selectedTeam": { "type": "string", "default": "" },
|
||||
"selectedMatch": { "type": "string", "default": "" },
|
||||
"side": { "type": "string", "default": "home" },
|
||||
"withBench": { "type": "boolean", "default": false }
|
||||
}
|
||||
,
|
||||
"usesContext": ["swi-foot/context"]
|
||||
}
|
||||
20
blocks/schedule/block.json
Normal file
20
blocks/schedule/block.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/schedule",
|
||||
"title": "Swiss Football Schedule",
|
||||
"category": "widgets",
|
||||
"icon": "schedule",
|
||||
"description": "Display upcoming matches for a team.",
|
||||
"attributes": {
|
||||
"teamId": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"default": 5
|
||||
}
|
||||
}
|
||||
,
|
||||
"usesContext": ["swi-foot/context"]
|
||||
}
|
||||
36
blocks/shortcode-inserter/block.json
Normal file
36
blocks/shortcode-inserter/block.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/shortcode-inserter",
|
||||
"title": "SWI Football Shortcode Inserter",
|
||||
"category": "widgets",
|
||||
"icon": "editor-code",
|
||||
"description": "Insert shortcode-based match or team displays.",
|
||||
"attributes": {
|
||||
"shortcodeType": {
|
||||
"type": "string",
|
||||
"default": "match"
|
||||
},
|
||||
"matchId": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"teamId": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"showNext": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"separator": {
|
||||
"type": "string",
|
||||
"default": ":"
|
||||
}
|
||||
},
|
||||
"usesContext": ["swi-foot/context"]
|
||||
,"supports": { "html": false }
|
||||
}
|
||||
16
blocks/standings/block.json
Normal file
16
blocks/standings/block.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/standings",
|
||||
"title": "Swiss Football Standings",
|
||||
"category": "widgets",
|
||||
"icon": "analytics",
|
||||
"description": "Display current standings for a team.",
|
||||
"attributes": {
|
||||
"teamId": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
,
|
||||
"usesContext": ["swi-foot/context"]
|
||||
}
|
||||
18
blocks/team-data/block.json
Normal file
18
blocks/team-data/block.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/team-data",
|
||||
"title": "Swiss Football Team Data",
|
||||
"category": "widgets",
|
||||
"icon": "universal-access-alt",
|
||||
"description": "Guided team selector to insert shortcodes for various match/team displays.",
|
||||
"attributes": {
|
||||
"selectedTeam": { "type": "string", "default": "" },
|
||||
"dataType": { "type": "string", "default": "" },
|
||||
"selectedMatch": { "type": "string", "default": "" },
|
||||
"shortcodeType": { "type": "string", "default": "match" },
|
||||
"format": { "type": "string", "default": "" },
|
||||
"separator": { "type": "string", "default": ":" }
|
||||
}
|
||||
,
|
||||
"usesContext": ["swi-foot/context"]
|
||||
}
|
||||
466
includes/class-swi-foot-admin.php
Normal file
466
includes/class-swi-foot-admin.php
Normal file
@ -0,0 +1,466 @@
|
||||
<?php
|
||||
|
||||
class Swi_Foot_Admin
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
add_action('admin_menu', array($this, 'add_admin_menu'));
|
||||
add_action('admin_init', array($this, 'settings_init'));
|
||||
add_action('admin_enqueue_scripts', array($this, 'admin_scripts'));
|
||||
add_action('add_meta_boxes', array($this, 'add_match_meta_boxes'));
|
||||
// Finished-match deletion moved to REST endpoints (see includes/class-swi-foot-rest.php)
|
||||
}
|
||||
|
||||
public function add_admin_menu()
|
||||
{
|
||||
add_options_page(
|
||||
__('Swiss Football Matchdata Settings', 'swi_foot_matchdata'),
|
||||
__('Swiss Football', 'swi_foot_matchdata'),
|
||||
'manage_options',
|
||||
'swiss-football-matchdata',
|
||||
array($this, 'options_page')
|
||||
);
|
||||
}
|
||||
|
||||
public function settings_init()
|
||||
{
|
||||
register_setting('swi_foot_settings', 'swi_foot_api_base_url');
|
||||
register_setting('swi_foot_settings', 'swi_foot_api_username');
|
||||
register_setting('swi_foot_settings', 'swi_foot_api_password');
|
||||
register_setting('swi_foot_settings', 'swi_foot_verein_id');
|
||||
register_setting('swi_foot_settings', 'swi_foot_season_id');
|
||||
register_setting('swi_foot_settings', 'swi_foot_match_cache_duration');
|
||||
|
||||
add_settings_section(
|
||||
'swi_foot_api_section',
|
||||
__('API Configuration', 'swi_foot_matchdata'),
|
||||
array($this, 'api_section_callback'),
|
||||
'swi_foot_settings'
|
||||
);
|
||||
|
||||
add_settings_field('swi_foot_api_base_url', __('API Base URL', 'swi_foot_matchdata'), array($this, 'base_url_render'), 'swi_foot_settings', 'swi_foot_api_section');
|
||||
add_settings_field('swi_foot_api_username', __('API Username (Application Key)', 'swi_foot_matchdata'), array($this, 'username_render'), 'swi_foot_settings', 'swi_foot_api_section');
|
||||
add_settings_field('swi_foot_api_password', __('API Password (Application Pass)', 'swi_foot_matchdata'), array($this, 'password_render'), 'swi_foot_settings', 'swi_foot_api_section');
|
||||
add_settings_field('swi_foot_verein_id', __('Verein ID (Club ID)', 'swi_foot_matchdata'), array($this, 'verein_id_render'), 'swi_foot_settings', 'swi_foot_api_section');
|
||||
add_settings_field('swi_foot_season_id', __('Season ID', 'swi_foot_matchdata'), array($this, 'season_id_render'), 'swi_foot_settings', 'swi_foot_api_section');
|
||||
|
||||
add_settings_section(
|
||||
'swi_foot_cache_section',
|
||||
__('Cache Settings', 'swi_foot_matchdata'),
|
||||
array($this, 'cache_section_callback'),
|
||||
'swi_foot_settings'
|
||||
);
|
||||
|
||||
add_settings_field('swi_foot_match_cache_duration', __('Match Data Cache Duration (seconds)', 'swi_foot_matchdata'), array($this, 'cache_duration_render'), 'swi_foot_settings', 'swi_foot_cache_section');
|
||||
}
|
||||
|
||||
public function api_section_callback()
|
||||
{
|
||||
echo '<p>' . __('Configure your Swiss Football API credentials here.', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function cache_section_callback()
|
||||
{
|
||||
echo '<p>' . __('Configure caching settings for match data.', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function base_url_render()
|
||||
{
|
||||
$base_url = get_option('swi_foot_api_base_url', 'https://stg-club-api-services.football.ch');
|
||||
echo '<input type="url" name="swi_foot_api_base_url" value="' . esc_attr($base_url) . '" class="regular-text" />';
|
||||
echo '<p class="description">' . __('The base URL for the Swiss Football API', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function username_render()
|
||||
{
|
||||
$username = get_option('swi_foot_api_username');
|
||||
echo '<input type="text" name="swi_foot_api_username" value="' . esc_attr($username) . '" class="regular-text" />';
|
||||
echo '<p class="description">' . __('Your API application key', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function password_render()
|
||||
{
|
||||
$password = get_option('swi_foot_api_password');
|
||||
echo '<input type="password" name="swi_foot_api_password" value="' . esc_attr($password) . '" class="regular-text" />';
|
||||
echo '<p class="description">' . __('Your API application password', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function verein_id_render()
|
||||
{
|
||||
$verein_id = get_option('swi_foot_verein_id');
|
||||
echo '<input type="number" name="swi_foot_verein_id" value="' . esc_attr($verein_id) . '" class="regular-text" />';
|
||||
echo '<p class="description">' . __('Enter your club\'s Verein ID (Club ID)', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function season_id_render()
|
||||
{
|
||||
$season_id = get_option('swi_foot_season_id', date('Y'));
|
||||
echo '<input type="number" name="swi_foot_season_id" value="' . esc_attr($season_id) . '" class="regular-text" min="2020" max="2030" />';
|
||||
echo '<p class="description">' . __('Current season ID (usually the year)', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function cache_duration_render()
|
||||
{
|
||||
$duration = get_option('swi_foot_match_cache_duration', 30);
|
||||
echo '<input type="number" name="swi_foot_match_cache_duration" value="' . esc_attr($duration) . '" min="10" max="300" />';
|
||||
echo '<p class="description">' . __('How long to cache match data in seconds (10-300)', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function admin_scripts($hook)
|
||||
{
|
||||
if ($hook === 'settings_page_swiss-football-matchdata') {
|
||||
wp_enqueue_script('swi-foot-admin', SWI_FOOT_PLUGIN_URL . 'assets/admin.js', array('jquery'), SWI_FOOT_PLUGIN_VERSION, true);
|
||||
wp_localize_script('swi-foot-admin', 'swi_foot_ajax', array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('swi_foot_nonce'),
|
||||
'rest_url' => esc_url_raw(rest_url('swi-foot/v1')),
|
||||
'rest_nonce' => wp_create_nonce('wp_rest')
|
||||
));
|
||||
wp_enqueue_style('swi-foot-admin', SWI_FOOT_PLUGIN_URL . 'assets/admin.css', array(), SWI_FOOT_PLUGIN_VERSION);
|
||||
}
|
||||
|
||||
// Add shortcode help to post/page editors
|
||||
global $pagenow;
|
||||
if (in_array($pagenow, array('post.php', 'post-new.php', 'edit.php'))) {
|
||||
// Enqueue the registered editor bundle so WordPress picks the built asset when available.
|
||||
// The script handle `swi-foot-editor-blocks` is registered in `register_blocks()`.
|
||||
wp_enqueue_script('swi-foot-editor-blocks');
|
||||
|
||||
// Add admin footer debug output to help diagnose missing script tags.
|
||||
add_action('admin_footer', array($this, 'print_editor_script_debug'));
|
||||
// Post context editor panel: allow editor to set per-post season/team/match
|
||||
wp_enqueue_script('swi-foot-post-context', SWI_FOOT_PLUGIN_URL . 'assets/post-context.js', array('wp-plugins','wp-edit-post','wp-element','wp-data','wp-components'), SWI_FOOT_PLUGIN_VERSION, true);
|
||||
wp_localize_script('swi-foot-post-context', 'swi_foot_post_context', array(
|
||||
'rest_url' => esc_url_raw(rest_url('swi-foot/v1')),
|
||||
'rest_nonce' => wp_create_nonce('wp_rest'),
|
||||
'default_season' => get_option('swi_foot_season_id', date('Y'))
|
||||
));
|
||||
wp_enqueue_style('swi-foot-admin', SWI_FOOT_PLUGIN_URL . 'assets/admin.css', array(), SWI_FOOT_PLUGIN_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
public function print_editor_script_debug()
|
||||
{
|
||||
// Only show on post editor pages
|
||||
$pagenow = isset($GLOBALS['pagenow']) ? $GLOBALS['pagenow'] : '';
|
||||
if (!in_array($pagenow, array('post.php', 'post-new.php', 'edit.php'))) return;
|
||||
|
||||
// Check registration/enqueue status and attempt to find the resolved src
|
||||
$registered = false;
|
||||
$enqueued = false;
|
||||
$src = '';
|
||||
global $wp_scripts;
|
||||
if (isset($wp_scripts) && is_object($wp_scripts)) {
|
||||
$registered = wp_script_is('swi-foot-editor-blocks', 'registered');
|
||||
$enqueued = wp_script_is('swi-foot-editor-blocks', 'enqueued');
|
||||
$handle = $wp_scripts->query('swi-foot-editor-blocks', 'registered');
|
||||
if ($handle && isset($wp_scripts->registered['swi-foot-editor-blocks']->src)) {
|
||||
$src = $wp_scripts->registered['swi-foot-editor-blocks']->src;
|
||||
}
|
||||
}
|
||||
|
||||
$msg = array(
|
||||
'registered' => $registered ? 'yes' : 'no',
|
||||
'enqueued' => $enqueued ? 'yes' : 'no',
|
||||
'src' => $src
|
||||
);
|
||||
echo "<script>console.debug('swi-foot: editor script debug', ". json_encode($msg) .");</script>";
|
||||
}
|
||||
|
||||
public function add_match_meta_boxes()
|
||||
{
|
||||
add_meta_box(
|
||||
'swi-foot-shortcodes',
|
||||
__('Swiss Football Shortcodes', 'swi_foot_matchdata'),
|
||||
array($this, 'shortcodes_meta_box'),
|
||||
array('post', 'page'),
|
||||
'side',
|
||||
'default'
|
||||
);
|
||||
}
|
||||
|
||||
public function shortcodes_meta_box($post)
|
||||
{
|
||||
?>
|
||||
<div class="swi-foot-shortcodes-help">
|
||||
<h4><?php _e('Available Shortcodes:', 'swi_foot_matchdata'); ?></h4>
|
||||
|
||||
<div class="shortcode-group">
|
||||
<strong><?php _e('Full Match Display:', 'swi_foot_matchdata'); ?></strong>
|
||||
<code data-shortcode="[swi_foot_match match_id="12345"]">[swi_foot_match match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match team_id="67" show_next="true"]">[swi_foot_match team_id="67" show_next="true"]</code>
|
||||
</div>
|
||||
|
||||
<div class="shortcode-group">
|
||||
<strong><?php _e('Individual Match Elements:', 'swi_foot_matchdata'); ?></strong>
|
||||
<code data-shortcode="[swi_foot_match_home_team match_id="12345"]">[swi_foot_match_home_team match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match_away_team match_id="12345"]">[swi_foot_match_away_team match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match_date match_id="12345" format="d.m.Y"]">[swi_foot_match_date match_id="12345" format="d.m.Y"]</code>
|
||||
<code data-shortcode="[swi_foot_match_time match_id="12345" format="H:i"]">[swi_foot_match_time match_id="12345" format="H:i"]</code>
|
||||
<code data-shortcode="[swi_foot_match_venue match_id="12345"]">[swi_foot_match_venue match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match_score match_id="12345" separator=":"]">[swi_foot_match_score match_id="12345" separator=":"]</code>
|
||||
<code data-shortcode="[swi_foot_match_status match_id="12345"]">[swi_foot_match_status match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match_league match_id="12345"]">[swi_foot_match_league match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match_round match_id="12345"]">[swi_foot_match_round match_id="12345"]</code>
|
||||
</div>
|
||||
|
||||
<div class="shortcode-group">
|
||||
<strong><?php _e('Parameters:', 'swi_foot_matchdata'); ?></strong>
|
||||
<ul>
|
||||
<li><code>match_id</code> - Specific match ID</li>
|
||||
<li><code>team_id</code> - Team ID (for next match)</li>
|
||||
<li><code>show_next</code> - Show next match (true/false)</li>
|
||||
<li><code>format</code> - Date/time format</li>
|
||||
<li><code>separator</code> - Score separator</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p><em><?php _e('Click any shortcode above to copy it to clipboard', 'swi_foot_matchdata'); ?></em></p>
|
||||
</div>
|
||||
<script>
|
||||
jQuery(document).ready(function($) {
|
||||
$('.swi-foot-shortcodes-help code[data-shortcode]').css('cursor', 'pointer').on('click', function() {
|
||||
var shortcode = $(this).data('shortcode');
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(shortcode).then(function() {
|
||||
alert('<?php _e('Shortcode copied to clipboard!', 'swi_foot_matchdata'); ?>');
|
||||
});
|
||||
} else {
|
||||
// Use native clipboard API
|
||||
var textArea = document.createElement('textarea');
|
||||
textArea.value = shortcode;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
alert('<?php _e('Shortcode copied to clipboard!', 'swi_foot_matchdata'); ?>');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function options_page()
|
||||
{
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php _e('Swiss Football Matchdata Settings', 'swi_foot_matchdata'); ?></h1>
|
||||
|
||||
<form action="options.php" method="post">
|
||||
<?php
|
||||
settings_fields('swi_foot_settings');
|
||||
do_settings_sections('swi_foot_settings');
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
|
||||
<div class="swi-foot-admin-section">
|
||||
<h2><?php _e('Connection Test', 'swi_foot_matchdata'); ?></h2>
|
||||
<p>
|
||||
<button type="button" id="test-connection" class="button">
|
||||
<?php _e('Test API Connection', 'swi_foot_matchdata'); ?>
|
||||
</button>
|
||||
<span id="connection-status"></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="swi-foot-admin-section">
|
||||
<h2><?php _e('Team Management', 'swi_foot_matchdata'); ?></h2>
|
||||
<p>
|
||||
<button type="button" id="refresh-teams" class="button">
|
||||
<?php _e('Refresh Teams List', 'swi_foot_matchdata'); ?>
|
||||
</button>
|
||||
<span id="refresh-status"></span>
|
||||
</p>
|
||||
|
||||
<div id="teams-list">
|
||||
<?php $this->display_teams(); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="swi-foot-admin-section">
|
||||
<h2><?php _e('Cache Management', 'swi_foot_matchdata'); ?></h2>
|
||||
<p>
|
||||
<button type="button" id="clear-cache" class="button">
|
||||
<?php _e('Clear Match Data Cache', 'swi_foot_matchdata'); ?>
|
||||
</button>
|
||||
<span id="cache-status"></span>
|
||||
</p>
|
||||
<?php $this->display_cache_info(); ?>
|
||||
</div>
|
||||
|
||||
<div class="swi-foot-admin-section">
|
||||
<h2><?php _e('Finished Matches Data', 'swi_foot_matchdata'); ?></h2>
|
||||
<?php $this->display_finished_matches(); ?>
|
||||
</div>
|
||||
|
||||
<div class="swi-foot-admin-section">
|
||||
<h2><?php _e('Quick Shortcode Reference', 'swi_foot_matchdata'); ?></h2>
|
||||
<div class="shortcode-quick-ref">
|
||||
<div class="shortcode-examples">
|
||||
<h4><?php _e('Common Examples:', 'swi_foot_matchdata'); ?></h4>
|
||||
<p><strong><?php _e('Display full match info:', 'swi_foot_matchdata'); ?></strong><br>
|
||||
<code>[swi_foot_match match_id="12345"]</code>
|
||||
</p>
|
||||
|
||||
<p><strong><?php _e('Show next match for a team:', 'swi_foot_matchdata'); ?></strong><br>
|
||||
<code>[swi_foot_match team_id="67" show_next="true"]</code>
|
||||
</p>
|
||||
|
||||
<p><strong><?php _e('Individual elements in text:', 'swi_foot_matchdata'); ?></strong><br>
|
||||
<?php _e('The match between', 'swi_foot_matchdata'); ?> <code>[swi_foot_match_home_team match_id="12345"]</code> <?php _e('and', 'swi_foot_matchdata'); ?> <code>[swi_foot_match_away_team match_id="12345"]</code> <?php _e('is on', 'swi_foot_matchdata'); ?> <code>[swi_foot_match_date match_id="12345"]</code></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function display_teams()
|
||||
{
|
||||
$api = new Swi_Foot_API();
|
||||
$teams = $api->get_teams();
|
||||
|
||||
if (is_wp_error($teams)) {
|
||||
echo '<p class="notice notice-error">' .
|
||||
sprintf(__('Error loading teams: %s', 'swi_foot_matchdata'), $teams->get_error_message()) .
|
||||
'</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($teams)) {
|
||||
echo '<p class="notice notice-warning">' .
|
||||
__('No teams found. Please check your API configuration.', 'swi_foot_matchdata') .
|
||||
'</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<h3>' . __('Available Teams:', 'swi_foot_matchdata') . '</h3>';
|
||||
echo '<div class="swi-foot-teams-grid">';
|
||||
foreach ($teams as $team) {
|
||||
echo '<div class="team-card">';
|
||||
echo '<strong>' . esc_html($team['teamName'] ?? 'Unknown Team') . '</strong><br>';
|
||||
echo '<small>ID: ' . esc_html($team['teamId'] ?? 'N/A') . '</small><br>';
|
||||
if (!empty($team['teamLeagueName'])) {
|
||||
echo '<small>' . esc_html($team['teamLeagueName']) . '</small>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
private function display_cache_info()
|
||||
{
|
||||
$keys = get_transient('swi_foot_match_keys');
|
||||
$cache_count = is_array($keys) ? count($keys) : 0;
|
||||
$cache_duration = get_option('swi_foot_match_cache_duration', 30);
|
||||
|
||||
echo '<p>' . sprintf(__('Currently caching %d match records with %d second cache duration.', 'swi_foot_matchdata'), $cache_count, $cache_duration) . '</p>';
|
||||
|
||||
if ($cache_count > 0) {
|
||||
$timestamps = array();
|
||||
foreach ($keys as $k) {
|
||||
$payload = get_transient('swi_foot_match_' . $k);
|
||||
if (is_array($payload) && isset($payload['cached_at'])) {
|
||||
$timestamps[] = (int) $payload['cached_at'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($timestamps)) {
|
||||
$oldest_timestamp = min($timestamps);
|
||||
$newest_timestamp = max($timestamps);
|
||||
|
||||
echo '<p><strong>' . __('Cache Range:', 'swi_foot_matchdata') . '</strong><br>';
|
||||
echo __('Oldest:', 'swi_foot_matchdata') . ' ' . date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $oldest_timestamp) . '<br>';
|
||||
echo __('Newest:', 'swi_foot_matchdata') . ' ' . date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $newest_timestamp) . '</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function display_finished_matches()
|
||||
{
|
||||
$finished = get_option('swi_foot_finished_matches', array());
|
||||
if (empty($finished)) {
|
||||
echo '<p>' . __('No finished match data stored.', 'swi_foot_matchdata') . '</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<table class="widefat fixed striped">';
|
||||
echo '<thead>
|
||||
<tr>
|
||||
<th>' . __('Match ID', 'swi_foot_matchdata') . '</th>
|
||||
<th>' . __('Saved At', 'swi_foot_matchdata') . '</th>
|
||||
<th>' . __('Players', 'swi_foot_matchdata') . '</th>
|
||||
<th>' . __('Bench', 'swi_foot_matchdata') . '</th>
|
||||
<th>' . __('Events', 'swi_foot_matchdata') . '</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead><tbody>';
|
||||
|
||||
foreach ($finished as $mid => $item) {
|
||||
$players = count($item['roster']['players'] ?? array());
|
||||
$bench = count($item['roster']['bench'] ?? array());
|
||||
$events = count($item['events'] ?? array());
|
||||
$saved = !empty($item['saved_at']) ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $item['saved_at']) : '-';
|
||||
|
||||
echo '<tr>
|
||||
<td>' . esc_html($mid) . '</td>
|
||||
<td>' . esc_html($saved) . '</td>
|
||||
<td>' . esc_html($players) . '</td>
|
||||
<td>' . esc_html($bench) . '</td>
|
||||
<td>' . esc_html($events) . '</td>
|
||||
<td>
|
||||
<button class="button swi-foot-clear-finished" data-id="' . esc_attr($mid) . '">' . __('Delete', 'swi_foot_matchdata') . '</button>
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
echo '</tbody></table>';
|
||||
echo '<p><button id="swi-foot-clear-all-finished" class="button button-secondary">' . __('Clear All Finished Matches', 'swi_foot_matchdata') . '</button></p>';
|
||||
|
||||
// Include inline JS for actions
|
||||
?>
|
||||
<script>
|
||||
jQuery(function($) {
|
||||
$('.swi-foot-clear-finished').on('click', function() {
|
||||
if (!confirm('<?php _e('Delete this finished match data?', 'swi_foot_matchdata'); ?>')) return;
|
||||
var btn = $(this);
|
||||
var matchId = btn.data('id');
|
||||
fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/finished/' + encodeURIComponent(matchId), {
|
||||
method: 'DELETE',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) { return resp.json(); }).then(function(resp) {
|
||||
if (resp && resp.success) {
|
||||
btn.closest('tr').fadeOut();
|
||||
} else {
|
||||
alert(resp.error || 'Error');
|
||||
}
|
||||
}).catch(function() { alert('Network error'); });
|
||||
});
|
||||
|
||||
$('#swi-foot-clear-all-finished').on('click', function() {
|
||||
if (!confirm('<?php _e('Clear all finished match data?', 'swi_foot_matchdata'); ?>')) return;
|
||||
fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/finished', {
|
||||
method: 'DELETE',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) { return resp.json(); }).then(function(resp) {
|
||||
if (resp && resp.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(resp.error || 'Error');
|
||||
}
|
||||
}).catch(function() { alert('Network error'); });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
?>
|
||||
559
includes/class-swi-foot-api.php
Normal file
559
includes/class-swi-foot-api.php
Normal file
@ -0,0 +1,559 @@
|
||||
<?php
|
||||
|
||||
class Swi_Foot_API
|
||||
{
|
||||
|
||||
private $base_url;
|
||||
private $username;
|
||||
private $password;
|
||||
private $verein_id;
|
||||
private $season_id;
|
||||
private $cache_duration;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->base_url = get_option('swi_foot_api_base_url', 'https://stg-club-api-services.football.ch');
|
||||
$this->username = get_option('swi_foot_api_username');
|
||||
$this->password = get_option('swi_foot_api_password');
|
||||
$this->verein_id = get_option('swi_foot_verein_id');
|
||||
$this->season_id = get_option('swi_foot_season_id', date('Y'));
|
||||
$this->cache_duration = get_option('swi_foot_match_cache_duration', 30);
|
||||
|
||||
// AJAX actions were migrated to REST endpoints (see includes/class-swi-foot-rest.php)
|
||||
|
||||
// Hook to update match cache on page load
|
||||
add_action('wp', array($this, 'maybe_update_match_cache'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a full URL for the given endpoint, avoiding duplicate /api segments
|
||||
*/
|
||||
private function build_url($endpoint)
|
||||
{
|
||||
$base = rtrim($this->base_url, '/');
|
||||
|
||||
// If base already contains '/api' and endpoint starts with '/api', strip the endpoint prefix
|
||||
if (strpos($base, '/api') !== false && strpos($endpoint, '/api') === 0) {
|
||||
$endpoint = preg_replace('#^/api#', '', $endpoint);
|
||||
}
|
||||
|
||||
return $base . '/' . ltrim($endpoint, '/');
|
||||
}
|
||||
|
||||
public function maybe_update_match_cache()
|
||||
{
|
||||
// Only run on single posts/pages
|
||||
if (!is_single() && !is_page()) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $post;
|
||||
if (!$post) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if content has match shortcodes
|
||||
if (
|
||||
!has_shortcode($post->post_content, 'swi_foot_match') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_home_team') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_away_team') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_date') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_time') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_venue') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_score') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_status') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_league') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_round')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract match IDs from shortcodes
|
||||
$match_ids = array();
|
||||
$patterns = array(
|
||||
'/\[swi_foot_match[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_home_team[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_away_team[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_date[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_time[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_venue[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_score[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_status[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_league[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_round[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/'
|
||||
);
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
preg_match_all($pattern, $post->post_content, $matches);
|
||||
if (!empty($matches[1])) {
|
||||
$match_ids = array_merge($match_ids, $matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Update cache for found match IDs
|
||||
$match_ids = array_unique($match_ids);
|
||||
foreach ($match_ids as $match_id) {
|
||||
$this->get_match_details($match_id, false); // Don't force refresh unless needed
|
||||
}
|
||||
}
|
||||
|
||||
private function get_access_token()
|
||||
{
|
||||
$token = get_transient('swi_foot_access_token');
|
||||
|
||||
// Transient handles expiration; return if present
|
||||
if ($token) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
// Fetch a new token
|
||||
return $this->refresh_access_token();
|
||||
}
|
||||
|
||||
private function refresh_access_token()
|
||||
{
|
||||
if (empty($this->username) || empty($this->password)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = wp_remote_post($this->build_url('/api/token'), array(
|
||||
'body' => json_encode(array(
|
||||
'applicationKey' => $this->username,
|
||||
'applicationPass' => $this->password
|
||||
)),
|
||||
'headers' => array(
|
||||
'Content-Type' => 'application/json'
|
||||
),
|
||||
'timeout' => 30
|
||||
));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
error_log('Swiss Football API: Token request failed - ' . $response->get_error_message());
|
||||
return false;
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code($response);
|
||||
if ($response_code !== 200) {
|
||||
$body_debug = wp_remote_retrieve_body($response);
|
||||
error_log('Swiss Football API: Token request returned ' . $response_code . ' - body: ' . substr($body_debug, 0, 1000));
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
|
||||
// Try JSON decode first, fallback to trimmed string
|
||||
$maybe_json = json_decode($body, true);
|
||||
if (is_string($maybe_json) && $maybe_json !== '') {
|
||||
$token = $maybe_json;
|
||||
} elseif (is_string($body) && $body !== '') {
|
||||
$token = trim($body, '"');
|
||||
} else {
|
||||
$token = false;
|
||||
}
|
||||
|
||||
if ($token) {
|
||||
// Store token in transient (30 minutes). WP constant MINUTE_IN_SECONDS available.
|
||||
set_transient('swi_foot_access_token', $token, 30 * MINUTE_IN_SECONDS);
|
||||
return $token;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function api_request($endpoint, $params = array(), $retry_on_401 = true)
|
||||
{
|
||||
$token = $this->get_access_token();
|
||||
|
||||
if (!$token) {
|
||||
return new WP_Error('auth_failed', 'Failed to authenticate with Swiss Football API');
|
||||
}
|
||||
|
||||
$url = $this->build_url($endpoint);
|
||||
if (!empty($params)) {
|
||||
$url .= '?' . http_build_query($params);
|
||||
}
|
||||
|
||||
$response = wp_remote_get($url, array(
|
||||
'headers' => array(
|
||||
'X-User-Token' => $token,
|
||||
'Content-Type' => 'application/json'
|
||||
),
|
||||
'timeout' => 30
|
||||
));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code($response);
|
||||
|
||||
// Handle 401 Unauthorized - token may have expired
|
||||
if ($response_code === 401 && $retry_on_401) {
|
||||
error_log('Swiss Football API: Received 401 Unauthorized, attempting to refresh token');
|
||||
// Clear the expired token
|
||||
delete_transient('swi_foot_access_token');
|
||||
// Refresh the token
|
||||
$new_token = $this->refresh_access_token();
|
||||
if ($new_token) {
|
||||
// Retry the request with the new token (prevent infinite recursion with retry_on_401 = false)
|
||||
return $this->api_request($endpoint, $params, false);
|
||||
}
|
||||
return new WP_Error('auth_failed', 'Authentication failed: Token refresh unsuccessful');
|
||||
}
|
||||
|
||||
if ($response_code !== 200) {
|
||||
$body_debug = wp_remote_retrieve_body($response);
|
||||
error_log('Swiss Football API: Request to ' . $url . ' returned ' . $response_code . ' - body: ' . substr($body_debug, 0, 1000));
|
||||
return new WP_Error('api_error', 'API request failed with code ' . $response_code);
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
return json_decode($body, true);
|
||||
}
|
||||
|
||||
public function get_teams()
|
||||
{
|
||||
// Prefer transient-based caching for teams (24 hours)
|
||||
$teams = get_transient('swi_foot_teams');
|
||||
if ($teams !== false) {
|
||||
return $teams;
|
||||
}
|
||||
|
||||
if (empty($this->verein_id)) {
|
||||
return new WP_Error('no_verein_id', 'Verein ID not configured');
|
||||
}
|
||||
|
||||
$teams = $this->api_request('/api/team/list', array(
|
||||
'ClubId' => $this->verein_id,
|
||||
'SeasonId' => $this->season_id
|
||||
));
|
||||
|
||||
if (!is_wp_error($teams) && is_array($teams)) {
|
||||
set_transient('swi_foot_teams', $teams, 24 * HOUR_IN_SECONDS);
|
||||
}
|
||||
|
||||
return $teams;
|
||||
}
|
||||
|
||||
public function get_standings($team_id)
|
||||
{
|
||||
if (empty($this->verein_id)) {
|
||||
return new WP_Error('no_verein_id', 'Verein ID not configured');
|
||||
}
|
||||
|
||||
return $this->api_request('/api/club/ranking', array(
|
||||
'ClubId' => $this->verein_id,
|
||||
'SeasonId' => $this->season_id,
|
||||
'TeamId' => $team_id
|
||||
));
|
||||
}
|
||||
|
||||
public function get_schedule($team_id)
|
||||
{
|
||||
if (empty($this->verein_id)) {
|
||||
return new WP_Error('no_verein_id', 'Verein ID not configured');
|
||||
}
|
||||
|
||||
return $this->api_request('/api/club/schedule', array(
|
||||
'ClubId' => $this->verein_id,
|
||||
'SeasonId' => $this->season_id,
|
||||
'TeamId' => $team_id
|
||||
));
|
||||
}
|
||||
|
||||
public function get_match_details($match_id, $force_refresh = false)
|
||||
{
|
||||
// If we've permanently saved finished match data, return it (do not call API)
|
||||
$finished = $this->get_finished_match_data($match_id);
|
||||
if (!$force_refresh && $finished && isset($finished['match'])) {
|
||||
return $finished['match'];
|
||||
}
|
||||
|
||||
$transient_key = 'swi_foot_match_' . $match_id;
|
||||
|
||||
if (!$force_refresh) {
|
||||
$cached = get_transient($transient_key);
|
||||
if ($cached !== false) {
|
||||
return isset($cached['data']) ? $cached['data'] : $cached;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch fresh data from API
|
||||
$match_data = $this->api_request('/api/match/' . $match_id);
|
||||
|
||||
if (!is_wp_error($match_data)) {
|
||||
// Cache the data as transient for configured duration
|
||||
$cache_payload = array(
|
||||
'data' => $match_data,
|
||||
'cached_at' => time()
|
||||
);
|
||||
set_transient($transient_key, $cache_payload, (int) $this->cache_duration);
|
||||
|
||||
// Maintain index of cached match ids for management / clearing
|
||||
$keys = get_transient('swi_foot_match_keys');
|
||||
if (!is_array($keys)) {
|
||||
$keys = array();
|
||||
}
|
||||
if (!in_array($match_id, $keys, true)) {
|
||||
$keys[] = $match_id;
|
||||
// No expiration for keys index so we can clear caches reliably
|
||||
set_transient('swi_foot_match_keys', $keys, 0);
|
||||
}
|
||||
|
||||
// If match finished, persist it permanently (store match details inside finished data)
|
||||
if (!empty($match_data['hasMatchEnded'])) {
|
||||
// Ensure finished entry exists and include match details
|
||||
$saved = $this->get_finished_match_data($match_id) ?: array();
|
||||
$saved['match'] = $match_data;
|
||||
// Keep any existing roster/events if present
|
||||
if (!isset($saved['roster'])) $saved['roster'] = array();
|
||||
if (!isset($saved['events'])) $saved['events'] = array();
|
||||
$all = get_option('swi_foot_finished_matches', array());
|
||||
$all[$match_id] = array_merge($all[$match_id] ?? array(), $saved, array('saved_at' => time()));
|
||||
update_option('swi_foot_finished_matches', $all);
|
||||
// Also delete transient cache to force reads from permanent store
|
||||
delete_transient($transient_key);
|
||||
}
|
||||
}
|
||||
|
||||
return $match_data;
|
||||
}
|
||||
|
||||
public function get_cached_match_data($match_id)
|
||||
{
|
||||
// Prefer finished permanent store
|
||||
$finished = $this->get_finished_match_data($match_id);
|
||||
if ($finished && isset($finished['match'])) {
|
||||
return $finished['match'];
|
||||
}
|
||||
|
||||
$transient_key = 'swi_foot_match_' . $match_id;
|
||||
$cached = get_transient($transient_key);
|
||||
if ($cached === false) return null;
|
||||
return isset($cached['data']) ? $cached['data'] : $cached;
|
||||
}
|
||||
|
||||
public function get_match_players($match_id)
|
||||
{
|
||||
return $this->api_request('/api/match/' . $match_id . '/players');
|
||||
}
|
||||
|
||||
public function get_match_bench($match_id)
|
||||
{
|
||||
return $this->api_request('/api/match/' . $match_id . '/bench');
|
||||
}
|
||||
|
||||
public function get_match_events($match_id)
|
||||
{
|
||||
return $this->api_request('/api/match/' . $match_id . '/events');
|
||||
}
|
||||
|
||||
public function get_team_picture($team_id)
|
||||
{
|
||||
// Special handling for team picture endpoint which returns 200 with data or 204 No Content
|
||||
$token = $this->get_access_token();
|
||||
|
||||
if (!$token) {
|
||||
error_log('Swiss Football API: get_team_picture - Failed to get access token');
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = $this->build_url('/api/team/picture/' . $team_id);
|
||||
error_log('Swiss Football API: get_team_picture - Requesting URL: ' . $url);
|
||||
|
||||
$response = wp_remote_get($url, array(
|
||||
'headers' => array(
|
||||
'X-User-Token' => $token,
|
||||
'Content-Type' => 'application/json'
|
||||
),
|
||||
'timeout' => 30
|
||||
));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
error_log('Swiss Football API: get_team_picture - wp_remote_get error: ' . $response->get_error_message());
|
||||
return null;
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code($response);
|
||||
error_log('Swiss Football API: get_team_picture - Response code: ' . $response_code);
|
||||
|
||||
// Handle 401 Unauthorized - token may have expired
|
||||
if ($response_code === 401) {
|
||||
error_log('Swiss Football API: get_team_picture - Received 401, refreshing token and retrying');
|
||||
delete_transient('swi_foot_access_token');
|
||||
$new_token = $this->refresh_access_token();
|
||||
if ($new_token) {
|
||||
// Recursively retry with new token
|
||||
return $this->get_team_picture($team_id);
|
||||
}
|
||||
error_log('Swiss Football API: get_team_picture - Token refresh failed');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle 204 No Content - team has no picture
|
||||
if ($response_code === 204) {
|
||||
error_log('Swiss Football API: get_team_picture - Team ' . $team_id . ' has no picture (204)');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle other error responses
|
||||
if ($response_code !== 200) {
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
error_log('Swiss Football API: get_team_picture - Request failed with code ' . $response_code . ' - body: ' . substr($body, 0, 500));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Success - return the base64 image data
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
error_log('Swiss Football API: get_team_picture - Successfully retrieved image, size: ' . strlen($body) . ' bytes');
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
public function get_commons_ids()
|
||||
{
|
||||
return $this->api_request('/api/commons/ids');
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug helper — return token and resolved endpoint info for admin debugging.
|
||||
* Note: only for admin use; not called on public endpoints.
|
||||
*/
|
||||
public function debug_get_token_info()
|
||||
{
|
||||
$token = $this->get_access_token();
|
||||
$team_list_url = $this->build_url('/api/team/list') . '?' . http_build_query(array(
|
||||
'ClubId' => $this->verein_id,
|
||||
'SeasonId' => $this->season_id
|
||||
));
|
||||
|
||||
return array(
|
||||
'token_present' => ($token !== false && !empty($token)),
|
||||
'token_preview' => is_string($token) ? substr($token, 0, 32) : null,
|
||||
'base_url' => $this->base_url,
|
||||
'team_list_url' => $team_list_url
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public helper to test whether we can obtain a valid access token
|
||||
* Returns true on success, false otherwise.
|
||||
*/
|
||||
public function test_connection()
|
||||
{
|
||||
// Actually test the connection by making an API request
|
||||
$result = $this->api_request('/api/teams', array('vereinId' => $this->verein_id));
|
||||
|
||||
// Check if the request was successful (not an error)
|
||||
if (is_wp_error($result)) {
|
||||
error_log('Swiss Football API: Connection test failed - ' . $result->get_error_message());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify we got a response (array means success)
|
||||
return is_array($result);
|
||||
}
|
||||
|
||||
public function get_current_match($team_id)
|
||||
{
|
||||
$schedule = $this->get_schedule($team_id);
|
||||
|
||||
if (is_wp_error($schedule) || empty($schedule)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$current_time = time();
|
||||
$current_match = null;
|
||||
|
||||
// Look for upcoming match within 5 days or recent match within 2 days
|
||||
foreach ($schedule as $match) {
|
||||
$match_time = strtotime($match['matchDate']);
|
||||
|
||||
// Upcoming within 5 days
|
||||
if ($match_time > $current_time && ($match_time - $current_time) <= (5 * 24 * 60 * 60)) {
|
||||
$current_match = $match;
|
||||
break;
|
||||
}
|
||||
|
||||
// Recent within 2 days
|
||||
if ($match_time <= $current_time && ($current_time - $match_time) <= (2 * 24 * 60 * 60)) {
|
||||
$current_match = $match;
|
||||
}
|
||||
}
|
||||
|
||||
return $current_match;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save finished match data permanently (roster + events in one structure)
|
||||
*/
|
||||
public function save_finished_match_data($match_id, $roster_data, $events_data)
|
||||
{
|
||||
if (empty($match_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$saved_matches = get_option('swi_foot_finished_matches', array());
|
||||
$existing = isset($saved_matches[$match_id]) ? $saved_matches[$match_id] : array();
|
||||
|
||||
$existing['roster'] = $roster_data;
|
||||
$existing['events'] = $events_data;
|
||||
$existing['saved_at'] = time();
|
||||
|
||||
// If match details are cached as transient, try to move them into permanent record
|
||||
$transient_key = 'swi_foot_match_' . $match_id;
|
||||
$match_details = get_transient($transient_key);
|
||||
if ($match_details !== false) {
|
||||
$existing['match'] = $match_details;
|
||||
delete_transient($transient_key);
|
||||
}
|
||||
|
||||
$saved_matches[$match_id] = $existing;
|
||||
update_option('swi_foot_finished_matches', $saved_matches);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve saved finished match data if available
|
||||
*/
|
||||
public function get_finished_match_data($match_id)
|
||||
{
|
||||
$saved_matches = get_option('swi_foot_finished_matches', array());
|
||||
return $saved_matches[$match_id] ?? null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve effective context for a post: season, team_id, match_id.
|
||||
* Priority: post meta 'swi_foot_context' -> plugin option defaults
|
||||
*/
|
||||
function swi_foot_resolve_context($post_id = null)
|
||||
{
|
||||
if (empty($post_id)) {
|
||||
$post_id = get_the_ID();
|
||||
}
|
||||
|
||||
$stored = get_post_meta($post_id, 'swi_foot_context', true);
|
||||
$season = null;
|
||||
$team_id = null;
|
||||
$match_id = null;
|
||||
|
||||
if (is_array($stored)) {
|
||||
$season = !empty($stored['season']) ? $stored['season'] : null;
|
||||
$team_id = !empty($stored['team_id']) ? $stored['team_id'] : null;
|
||||
$match_id = !empty($stored['match_id']) ? $stored['match_id'] : null;
|
||||
}
|
||||
|
||||
if (empty($season)) {
|
||||
$season = get_option('swi_foot_season_id', date('Y'));
|
||||
}
|
||||
|
||||
return array(
|
||||
'season' => $season,
|
||||
'team_id' => $team_id,
|
||||
'match_id' => $match_id
|
||||
);
|
||||
}
|
||||
|
||||
479
includes/class-swi-foot-blocks.php
Normal file
479
includes/class-swi-foot-blocks.php
Normal file
@ -0,0 +1,479 @@
|
||||
<?php
|
||||
|
||||
class Swi_Foot_Blocks
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Register blocks immediately when this object is constructed.
|
||||
// The plugin constructs this class during the plugin init sequence,
|
||||
// so calling register_blocks() now ensures scripts/styles are registered
|
||||
// before `enqueue_block_editor_assets` runs later in the request.
|
||||
$this->register_blocks();
|
||||
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
|
||||
// Editor assets enqueued below; data endpoints are provided via REST (see includes/class-swi-foot-rest.php)
|
||||
add_action('enqueue_block_editor_assets', array($this, 'enqueue_editor_assets'));
|
||||
}
|
||||
|
||||
public function register_blocks()
|
||||
{
|
||||
// Register blocks from metadata (block.json) and provide server-side render callbacks
|
||||
$base = dirname(__DIR__) . '/blocks';
|
||||
|
||||
// Register editor script and styles from webpack build
|
||||
$editor_js_url = SWI_FOOT_PLUGIN_URL . 'assets/build/editor-blocks.js';
|
||||
|
||||
wp_register_script(
|
||||
'swi-foot-editor-blocks',
|
||||
$editor_js_url,
|
||||
array('wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n', 'wp-data', 'wp-rich-text'),
|
||||
SWI_FOOT_PLUGIN_VERSION
|
||||
);
|
||||
|
||||
$editor_css = SWI_FOOT_PLUGIN_URL . 'assets/blocks.css';
|
||||
wp_register_style('swi-foot-editor-styles', $editor_css, array(), SWI_FOOT_PLUGIN_VERSION);
|
||||
|
||||
if ( file_exists( $base . '/standings/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/standings', array(
|
||||
'render_callback' => array( $this, 'render_standings_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
if ( file_exists( $base . '/context/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/context', array(
|
||||
'render_callback' => function($attributes, $content, $block) {
|
||||
// Provide a transparent wrapper but push provider context to a global stack
|
||||
// so inner shortcodes and blocks can read it during rendering.
|
||||
$provided = is_array($attributes['swi_foot_context'] ?? null) ? $attributes['swi_foot_context'] : array();
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $provided);
|
||||
|
||||
global $swi_foot_context_stack;
|
||||
if (!isset($swi_foot_context_stack) || !is_array($swi_foot_context_stack)) {
|
||||
$swi_foot_context_stack = array();
|
||||
}
|
||||
$swi_foot_context_stack[] = $ctx;
|
||||
|
||||
// Render inner blocks (so shortcodes executed within this scope can read the global)
|
||||
$rendered = do_blocks($content);
|
||||
|
||||
// Pop stack
|
||||
array_pop($swi_foot_context_stack);
|
||||
|
||||
return $rendered;
|
||||
},
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
if ( file_exists( $base . '/schedule/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/schedule', array(
|
||||
'render_callback' => array( $this, 'render_schedule_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated shortcode-inserter block
|
||||
* Kept for backwards compatibility only
|
||||
*/
|
||||
if ( file_exists( $base . '/shortcode-inserter/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/shortcode-inserter', array(
|
||||
'render_callback' => array( $this, 'render_shortcode_inserter_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles',
|
||||
'deprecated' => true,
|
||||
) );
|
||||
}
|
||||
|
||||
// Register additional blocks (editor-only save shortcodes)
|
||||
if ( file_exists( $base . '/team-data/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/team-data', array(
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
if ( file_exists( $base . '/match-roster/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/match-roster', array(
|
||||
'render_callback' => array( $this, 'render_match_roster_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
if ( file_exists( $base . '/match-events/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/match-events', array(
|
||||
'render_callback' => array( $this, 'render_match_events_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
public function enqueue_scripts()
|
||||
{
|
||||
wp_enqueue_script('swi-foot-blocks', SWI_FOOT_PLUGIN_URL . 'assets/blocks.js', array('jquery'), SWI_FOOT_PLUGIN_VERSION, true);
|
||||
wp_enqueue_style('swi-foot-blocks', SWI_FOOT_PLUGIN_URL . 'assets/blocks.css', array(), SWI_FOOT_PLUGIN_VERSION);
|
||||
|
||||
// Localize REST API data for frontend event refresh polling
|
||||
wp_localize_script('swi-foot-blocks', 'swiFootRest', array(
|
||||
'rest_url' => rest_url('swi-foot/v1/'),
|
||||
'rest_nonce' => wp_create_nonce('wp_rest')
|
||||
));
|
||||
}
|
||||
|
||||
/*
|
||||
public function enqueue_editor_assets()
|
||||
{
|
||||
wp_enqueue_script(
|
||||
'swi-foot-editor-blocks',
|
||||
SWI_FOOT_PLUGIN_URL . 'assets/editor-blocks.js',
|
||||
array('wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n'),
|
||||
SWI_FOOT_PLUGIN_VERSION
|
||||
);
|
||||
|
||||
wp_localize_script('swi-foot-editor-blocks', 'swiFootData', array(
|
||||
'teams' => $this->get_teams_for_editor(),
|
||||
'shortcodes' => array(
|
||||
'match' => __('Full Match Display', 'swi_foot_matchdata'),
|
||||
'match_home_team' => __('Home Team', 'swi_foot_matchdata'),
|
||||
'match_away_team' => __('Away Team', 'swi_foot_matchdata'),
|
||||
'match_date' => __('Match Date', 'swi_foot_matchdata'),
|
||||
'match_time' => __('Match Time', 'swi_foot_matchdata'),
|
||||
'match_venue' => __('Venue', 'swi_foot_matchdata'),
|
||||
'match_score' => __('Score', 'swi_foot_matchdata'),
|
||||
'match_status' => __('Status', 'swi_foot_matchdata'),
|
||||
'match_league' => __('League', 'swi_foot_matchdata'),
|
||||
'match_round' => __('Round', 'swi_foot_matchdata')
|
||||
)
|
||||
));
|
||||
}
|
||||
*/
|
||||
|
||||
public function enqueue_editor_assets()
|
||||
{
|
||||
// Enqueue the registered script handle (register_blocks() registers it on init)
|
||||
if ( ! wp_script_is( 'swi-foot-editor-blocks', 'enqueued' ) ) {
|
||||
wp_enqueue_script( 'swi-foot-editor-blocks' );
|
||||
}
|
||||
|
||||
// Enqueue editor styles globally to ensure block wrapper styling applies
|
||||
wp_enqueue_style( 'swi-foot-editor-styles' );
|
||||
|
||||
wp_localize_script('swi-foot-editor-blocks', 'swiFootEditorData', array(
|
||||
'rest_url' => esc_url_raw(rest_url('swi-foot/v1')),
|
||||
'rest_nonce' => wp_create_nonce('wp_rest'),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function get_teams_for_editor()
|
||||
{
|
||||
$api = new Swi_Foot_API();
|
||||
$teams = $api->get_teams();
|
||||
|
||||
if (is_wp_error($teams) || empty($teams)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$team_options = array();
|
||||
foreach ($teams as $team) {
|
||||
$team_name = $team['teamName'] ?? '';
|
||||
$league = $team['teamLeagueName'] ?? '';
|
||||
|
||||
// If league name is available, append in parentheses
|
||||
if (!empty($league)) {
|
||||
$team_name .= ' (' . $league . ')';
|
||||
}
|
||||
|
||||
$team_options[] = array(
|
||||
'value' => $team['teamId'] ?? '',
|
||||
'label' => $team_name
|
||||
);
|
||||
}
|
||||
|
||||
return $team_options;
|
||||
}
|
||||
|
||||
public function render_standings_block($attributes, $content = '', $block = null)
|
||||
{
|
||||
// Resolve context: block context (from provider) overrides post meta defaults
|
||||
$provided = is_object($block) && !empty($block->context['swi-foot/context']) ? $block->context['swi-foot/context'] : null;
|
||||
$ctx = array();
|
||||
if (is_array($provided)) $ctx = $provided;
|
||||
// Merge with post-level defaults (post meta -> plugin options)
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $ctx);
|
||||
|
||||
// Team must come from context only
|
||||
$team_id = $ctx['team_id'] ?? null;
|
||||
|
||||
if (empty($team_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Standings: No team provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
$api = new Swi_Foot_API();
|
||||
$standings = $api->get_standings($team_id);
|
||||
|
||||
if (is_wp_error($standings)) {
|
||||
return '<div class="swi-foot-error">' .
|
||||
sprintf(__('Error loading standings: %s', 'swi_foot_matchdata'), $standings->get_error_message()) .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
if (empty($standings)) {
|
||||
return '<div class="swi-foot-notice">' . __('No standings data available.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="swi-foot-standings">
|
||||
<h3><?php _e('Current Standings', 'swi_foot_matchdata'); ?></h3>
|
||||
<table class="swi-foot-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php _e('Pos', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('Team', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('P', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('W', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('D', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('L', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('GF', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('GA', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('Pts', 'swi_foot_matchdata'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($standings as $team): ?>
|
||||
<tr<?php echo ($team['teamId'] == $team_id) ? ' class="highlight"' : ''; ?>>
|
||||
<td><?php echo esc_html($team['position']); ?></td>
|
||||
<td><?php echo esc_html($team['teamName']); ?></td>
|
||||
<td><?php echo esc_html($team['matches']); ?></td>
|
||||
<td><?php echo esc_html($team['wins']); ?></td>
|
||||
<td><?php echo esc_html($team['draws']); ?></td>
|
||||
<td><?php echo esc_html($team['losses']); ?></td>
|
||||
<td><?php echo esc_html($team['goalsFor']); ?></td>
|
||||
<td><?php echo esc_html($team['goalsAgainst']); ?></td>
|
||||
<td><strong><?php echo esc_html($team['points']); ?></strong></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
public function render_schedule_block($attributes, $content = '', $block = null)
|
||||
{
|
||||
$provided = is_object($block) && !empty($block->context['swi-foot/context']) ? $block->context['swi-foot/context'] : null;
|
||||
$ctx = array();
|
||||
if (is_array($provided)) $ctx = $provided;
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $ctx);
|
||||
|
||||
// Team must come from context only
|
||||
$team_id = $ctx['team_id'] ?? null;
|
||||
$limit = isset($attributes['limit']) ? $attributes['limit'] : 5;
|
||||
|
||||
if (empty($team_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Schedule: No team provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
$api = new Swi_Foot_API();
|
||||
$events = $api->get_schedule($team_id);
|
||||
|
||||
if (is_wp_error($events)) {
|
||||
return '<div class="swi-foot-error">' .
|
||||
sprintf(__('Error loading schedule: %s', 'swi_foot_matchdata'), $events->get_error_message()) .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// Filter upcoming events and limit results
|
||||
$upcoming_events = array();
|
||||
if (is_array($events)) {
|
||||
foreach ($events as $event) {
|
||||
if (strtotime($event['matchDate']) >= time()) {
|
||||
$upcoming_events[] = $event;
|
||||
if (count($upcoming_events) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="swi-foot-schedule">
|
||||
<h3><?php _e('Upcoming Matches', 'swi_foot_matchdata'); ?></h3>
|
||||
<?php if (empty($upcoming_events)): ?>
|
||||
<p><?php _e('No upcoming matches scheduled.', 'swi_foot_matchdata'); ?></p>
|
||||
<?php else: ?>
|
||||
<div class="swi-foot-events">
|
||||
<?php foreach ($upcoming_events as $event): ?>
|
||||
<div class="swi-foot-event">
|
||||
<div class="event-date">
|
||||
<?php echo date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($event['matchDate'])); ?>
|
||||
</div>
|
||||
<div class="event-teams">
|
||||
<?php echo esc_html($event['teamNameA'] ?? ''); ?> vs <?php echo esc_html($event['teamNameB'] ?? ''); ?>
|
||||
</div>
|
||||
<?php if (!empty($event['stadiumPlaygroundName'])): ?>
|
||||
<div class="event-location"><?php echo esc_html($event['stadiumPlaygroundName']); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($event['leagueName'])): ?>
|
||||
<div class="event-league"><?php echo esc_html($event['leagueName']); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
public function render_shortcode_inserter_block($attributes, $content = '', $block = null)
|
||||
{
|
||||
$provided = is_object($block) && !empty($block->context['swi-foot/context']) ? $block->context['swi-foot/context'] : null;
|
||||
$ctx = array();
|
||||
if (is_array($provided)) $ctx = $provided;
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $ctx);
|
||||
|
||||
$shortcode_type = $attributes['shortcodeType'] ?? 'match';
|
||||
$match_id = $attributes['matchId'] ?? ($ctx['match_id'] ?? '');
|
||||
$team_id = $attributes['teamId'] ?? ($ctx['team_id'] ?? '');
|
||||
$show_next = !empty($attributes['showNext']);
|
||||
$format = $attributes['format'] ?? '';
|
||||
$separator = $attributes['separator'] ?? ':';
|
||||
|
||||
// Build shortcode based on attributes
|
||||
$shortcode_parts = array();
|
||||
|
||||
if (!empty($match_id)) {
|
||||
$shortcode_parts[] = 'match_id="' . esc_attr($match_id) . '"';
|
||||
}
|
||||
|
||||
if (!empty($team_id)) {
|
||||
$shortcode_parts[] = 'team_id="' . esc_attr($team_id) . '"';
|
||||
}
|
||||
|
||||
if ($show_next) {
|
||||
$shortcode_parts[] = 'show_next="true"';
|
||||
}
|
||||
|
||||
if (!empty($format) && in_array($shortcode_type, array('match_date', 'match_time'))) {
|
||||
$shortcode_parts[] = 'format="' . esc_attr($format) . '"';
|
||||
}
|
||||
|
||||
if (!empty($separator) && $separator !== ':' && $shortcode_type === 'match_score') {
|
||||
$shortcode_parts[] = 'separator="' . esc_attr($separator) . '"';
|
||||
}
|
||||
|
||||
$shortcode = '[swi_foot_' . $shortcode_type . (!empty($shortcode_parts) ? ' ' . implode(' ', $shortcode_parts) : '') . ']';
|
||||
|
||||
// Execute the shortcode and return inline wrapper so editors can insert it inside text
|
||||
$rendered = do_shortcode($shortcode);
|
||||
return '<span class="swi-foot-inline shortcode-inserter">' . $rendered . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render match roster block
|
||||
* Gets match from container context, uses side attribute to determine which team to show
|
||||
*/
|
||||
public function render_match_roster_block($attributes, $content = '', $block = null)
|
||||
{
|
||||
// Get context from parent block container
|
||||
$provided = is_object($block) && !empty($block->context['swi-foot/context']) ? $block->context['swi-foot/context'] : null;
|
||||
$ctx = array();
|
||||
if (is_array($provided)) $ctx = $provided;
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $ctx);
|
||||
|
||||
// Match must come from context
|
||||
$match_id = $ctx['match_id'] ?? '';
|
||||
$side = $attributes['side'] ?? 'home';
|
||||
$with_bench = $attributes['withBench'] ? 'true' : 'false';
|
||||
|
||||
if (empty($match_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Match Roster: No match provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
// Build shortcode using match from context and side attribute
|
||||
$shortcode = '[swi_foot_roster match_id="' . esc_attr($match_id) . '" side="' . esc_attr($side) . '"';
|
||||
if ($with_bench === 'true') {
|
||||
$shortcode .= ' with_bench="true"';
|
||||
}
|
||||
$shortcode .= ']';
|
||||
|
||||
// Process and return the shortcode
|
||||
return do_shortcode($shortcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render match events block
|
||||
* Gets match from container context, displays live events for that match
|
||||
*/
|
||||
public function render_match_events_block($attributes, $content = '', $block = null)
|
||||
{
|
||||
// Get context from parent block container
|
||||
$provided = is_object($block) && !empty($block->context['swi-foot/context']) ? $block->context['swi-foot/context'] : null;
|
||||
$ctx = array();
|
||||
if (is_array($provided)) $ctx = $provided;
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $ctx);
|
||||
|
||||
// Match must come from context
|
||||
$match_id = $ctx['match_id'] ?? '';
|
||||
$refresh_interval = $attributes['refreshInterval'] ?? 30;
|
||||
$event_order = $attributes['eventOrder'] ?? 'dynamic';
|
||||
|
||||
if (empty($match_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Match Events: No match provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
// Build shortcode using match from context
|
||||
$shortcode = '[swi_foot_events match_id="' . esc_attr($match_id) . '" refresh_interval="' . esc_attr($refresh_interval) . '" event_order="' . esc_attr($event_order) . '"]';
|
||||
|
||||
// Process and return the shortcode
|
||||
return do_shortcode($shortcode);
|
||||
}
|
||||
|
||||
private function render_team_selector($block_type)
|
||||
{
|
||||
$api = new Swi_Foot_API();
|
||||
$teams = $api->get_teams();
|
||||
|
||||
if (is_wp_error($teams) || empty($teams)) {
|
||||
return '<div class="swi-foot-error">' .
|
||||
__('Please configure your API settings and ensure teams are available.', 'swi_foot_matchdata') .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="swi-foot-team-selector">
|
||||
<p><?php printf(__('Please select a team to display %s:', 'swi_foot_matchdata'), $block_type); ?></p>
|
||||
<select onchange="swiFootSelectTeam(this.value, '<?php echo esc_attr($block_type); ?>')">
|
||||
<option value=""><?php _e('Select a team...', 'swi_foot_matchdata'); ?></option>
|
||||
<?php foreach ($teams as $team): ?>
|
||||
<option value="<?php echo esc_attr($team['teamId'] ?? ''); ?>">
|
||||
<?php echo esc_html($team['teamName'] ?? 'Unknown Team'); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
?>
|
||||
399
includes/class-swi-foot-rest.php
Normal file
399
includes/class-swi-foot-rest.php
Normal file
@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
class Swi_Foot_REST {
|
||||
|
||||
public function __construct() {
|
||||
add_action('rest_api_init', array($this, 'register_routes'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize team data for consistent API responses
|
||||
* @param array $team Raw team data from API
|
||||
* @return array Normalized team object
|
||||
*/
|
||||
private function normalize_team($team) {
|
||||
return array(
|
||||
'teamId' => isset($team['teamId']) ? (string)$team['teamId'] : (isset($team['id']) ? (string)$team['id'] : ''),
|
||||
'teamName' => isset($team['teamName']) ? $team['teamName'] : (isset($team['name']) ? $team['name'] : ''),
|
||||
'teamLeagueName' => isset($team['teamLeagueName']) ? $team['teamLeagueName'] : (isset($team['league']) ? $team['league'] : null),
|
||||
'icon' => isset($team['icon']) ? $team['icon'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize match data for consistent API responses
|
||||
* @param array $match Raw match data from API
|
||||
* @return array Normalized match object
|
||||
*/
|
||||
private function normalize_match($match) {
|
||||
// Extract team names from teams array if not present as direct fields
|
||||
$teamNameA = '';
|
||||
$teamNameB = '';
|
||||
|
||||
if (!empty($match['teams']) && is_array($match['teams'])) {
|
||||
foreach ($match['teams'] as $team) {
|
||||
if (!empty($team['isHomeTeam']) && !empty($team['teamName'])) {
|
||||
$teamNameA = $team['teamName'];
|
||||
} elseif (empty($team['isHomeTeam']) && !empty($team['teamName'])) {
|
||||
$teamNameB = $team['teamName'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use direct fields if available, fallback to extracted values, then fallback field variations
|
||||
return array(
|
||||
'matchId' => isset($match['matchId']) ? (string)$match['matchId'] : (isset($match['id']) ? (string)$match['id'] : ''),
|
||||
'matchDate' => isset($match['matchDate']) ? $match['matchDate'] : (isset($match['date']) ? $match['date'] : null),
|
||||
'teamAId' => isset($match['teamAId']) ? (string)$match['teamAId'] : (isset($match['homeTeamId']) ? (string)$match['homeTeamId'] : ''),
|
||||
'teamBId' => isset($match['teamBId']) ? (string)$match['teamBId'] : (isset($match['awayTeamId']) ? (string)$match['awayTeamId'] : ''),
|
||||
'teamNameA' => isset($match['teamNameA']) ? $match['teamNameA'] : ($teamNameA ?: (isset($match['homeTeamName']) ? $match['homeTeamName'] : '')),
|
||||
'teamNameB' => isset($match['teamNameB']) ? $match['teamNameB'] : ($teamNameB ?: (isset($match['awayTeamName']) ? $match['awayTeamName'] : '')),
|
||||
'scoreTeamA' => isset($match['scoreTeamA']) ? $match['scoreTeamA'] : (isset($match['homeScore']) ? $match['homeScore'] : null),
|
||||
'scoreTeamB' => isset($match['scoreTeamB']) ? $match['scoreTeamB'] : (isset($match['awayScore']) ? $match['awayScore'] : null),
|
||||
'matchStateName' => isset($match['matchStateName']) ? $match['matchStateName'] : (isset($match['status']) ? $match['status'] : null),
|
||||
'stadiumFieldName' => isset($match['stadiumFieldName']) ? $match['stadiumFieldName'] : (isset($match['stadiumPlaygroundName']) ? $match['stadiumPlaygroundName'] : null),
|
||||
'leagueName' => isset($match['leagueName']) ? $match['leagueName'] : null,
|
||||
'divisionName' => isset($match['divisionName']) ? $match['divisionName'] : null,
|
||||
'roundNbr' => isset($match['roundNbr']) ? $match['roundNbr'] : null,
|
||||
'matchTypeName' => isset($match['matchTypeName']) ? $match['matchTypeName'] : null,
|
||||
'hasMatchStarted' => isset($match['hasMatchStarted']) ? $match['hasMatchStarted'] : false,
|
||||
'isMatchPause' => isset($match['isMatchPause']) ? $match['isMatchPause'] : false,
|
||||
'hasMatchEnded' => isset($match['hasMatchEnded']) ? $match['hasMatchEnded'] : false,
|
||||
'intermediateResults' => isset($match['intermediateResults']) ? $match['intermediateResults'] : null,
|
||||
'teams' => isset($match['teams']) ? $match['teams'] : array(),
|
||||
);
|
||||
}
|
||||
|
||||
public function register_routes() {
|
||||
register_rest_route('swi-foot/v1', '/teams', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array($this, 'get_teams'),
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
register_rest_route('swi-foot/v1', '/matches', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array($this, 'get_matches_for_team'),
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => array(
|
||||
'team_id' => array(
|
||||
'validate_callback' => function($param, $request, $key) {
|
||||
return is_numeric($param);
|
||||
}
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
// Also allow team_id as a path parameter: /wp-json/swi-foot/v1/matches/123
|
||||
register_rest_route('swi-foot/v1', '/matches/(?P<team_id>\d+)', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array($this, 'get_matches_for_team'),
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => array(
|
||||
'team_id' => array(
|
||||
'validate_callback' => function($param, $request, $key) {
|
||||
return is_numeric($param);
|
||||
}
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
// Single match details: /wp-json/swi-foot/v1/match/<match_id>
|
||||
register_rest_route('swi-foot/v1', '/match/(?P<match_id>[^/]+)', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array($this, 'get_match_details'),
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => array(
|
||||
'match_id' => array(
|
||||
'validate_callback' => function($param, $request, $key) {
|
||||
return is_string($param) && strlen($param) > 0;
|
||||
}
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
// Match events with status: /wp-json/swi-foot/v1/events/<match_id>
|
||||
// Requires authentication to prevent abuse from non-logged-in users
|
||||
register_rest_route('swi-foot/v1', '/events/(?P<match_id>[^/]+)', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array($this, 'get_match_events'),
|
||||
'permission_callback' => function() {
|
||||
return is_user_logged_in() || apply_filters('swi_foot_rest_events_public', false);
|
||||
},
|
||||
'args' => array(
|
||||
'match_id' => array(
|
||||
'validate_callback' => function($param, $request, $key) {
|
||||
return is_string($param) && strlen($param) > 0;
|
||||
}
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
register_rest_route('swi-foot/v1', '/commons-ids', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array($this, 'get_commons_ids'),
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
// Admin actions
|
||||
register_rest_route('swi-foot/v1', '/admin/refresh-teams', array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array($this, 'admin_refresh_teams'),
|
||||
'permission_callback' => function() { return current_user_can('manage_options'); }
|
||||
));
|
||||
|
||||
register_rest_route('swi-foot/v1', '/admin/clear-cache', array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array($this, 'admin_clear_cache'),
|
||||
'permission_callback' => function() { return current_user_can('manage_options'); }
|
||||
));
|
||||
|
||||
register_rest_route('swi-foot/v1', '/admin/test-connection', array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array($this, 'admin_test_connection'),
|
||||
'permission_callback' => function() { return current_user_can('manage_options'); }
|
||||
));
|
||||
|
||||
register_rest_route('swi-foot/v1', '/admin/finished/(?P<match_id>[^/]+)', array(
|
||||
'methods' => 'DELETE',
|
||||
'callback' => array($this, 'admin_delete_finished_match'),
|
||||
'permission_callback' => function() { return current_user_can('manage_options'); },
|
||||
'args' => array(
|
||||
'match_id' => array(
|
||||
'validate_callback' => function($param, $request, $key) {
|
||||
return is_string($param) && strlen($param) > 0;
|
||||
}
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
register_rest_route('swi-foot/v1', '/admin/finished', array(
|
||||
'methods' => 'DELETE',
|
||||
'callback' => array($this, 'admin_delete_all_finished_matches'),
|
||||
'permission_callback' => function() { return current_user_can('manage_options'); }
|
||||
));
|
||||
}
|
||||
|
||||
public function get_teams($request) {
|
||||
$api = new Swi_Foot_API();
|
||||
$teams = $api->get_teams();
|
||||
if (is_wp_error($teams)) {
|
||||
return new WP_REST_Response(array('error' => $teams->get_error_message()), 400);
|
||||
}
|
||||
// Normalize team data for consistent responses
|
||||
$normalized = array();
|
||||
if (is_array($teams)) {
|
||||
foreach ($teams as $team) {
|
||||
$normalized[] = $this->normalize_team($team);
|
||||
}
|
||||
}
|
||||
return rest_ensure_response($normalized);
|
||||
}
|
||||
|
||||
public function get_matches_for_team($request) {
|
||||
$team_id = $request->get_param('team_id');
|
||||
if (empty($team_id)) {
|
||||
return new WP_REST_Response(array('error' => 'team_id required'), 400);
|
||||
}
|
||||
$api = new Swi_Foot_API();
|
||||
$schedule = $api->get_schedule($team_id);
|
||||
if (is_wp_error($schedule)) {
|
||||
return new WP_REST_Response(array('error' => $schedule->get_error_message()), 400);
|
||||
}
|
||||
// Normalize matches for consistent responses
|
||||
$normalized = array();
|
||||
if (is_array($schedule)) {
|
||||
foreach ($schedule as $match) {
|
||||
$normalized[] = $this->normalize_match($match);
|
||||
}
|
||||
}
|
||||
return rest_ensure_response($normalized);
|
||||
}
|
||||
|
||||
public function get_commons_ids($request) {
|
||||
$api = new Swi_Foot_API();
|
||||
$commons = $api->get_commons_ids();
|
||||
if (is_wp_error($commons)) {
|
||||
return new WP_REST_Response(array('error' => $commons->get_error_message()), 400);
|
||||
}
|
||||
return rest_ensure_response($commons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return full match details for a given match_id
|
||||
* GET /wp-json/swi-foot/v1/match/<match_id>
|
||||
*/
|
||||
public function get_match_details($request) {
|
||||
$match_id = $request->get_param('match_id');
|
||||
if (empty($match_id)) {
|
||||
return new WP_REST_Response(array('error' => 'match_id required'), 400);
|
||||
}
|
||||
|
||||
$api = new Swi_Foot_API();
|
||||
$match = $api->get_match_details($match_id);
|
||||
if (is_wp_error($match)) {
|
||||
return new WP_REST_Response(array('error' => $match->get_error_message()), 400);
|
||||
}
|
||||
// Normalize match data for consistent API responses
|
||||
$normalized = $this->normalize_match($match);
|
||||
return rest_ensure_response($normalized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get match events with current match status (for frontend refresh polling)
|
||||
*
|
||||
* Endpoint: GET /wp-json/swi-foot/v1/events/<match_id>
|
||||
*
|
||||
* Returns the latest match events (sorted newest first) along with critical match
|
||||
* status information (hasMatchStarted, hasMatchEnded). Frontend polls this endpoint
|
||||
* at configurable intervals to update live event displays.
|
||||
*
|
||||
* Permission: Requires logged-in user (can be overridden with swi_foot_rest_events_public filter)
|
||||
*
|
||||
* @param WP_REST_Request $request The REST request object containing match_id parameter
|
||||
* @return WP_REST_Response Array with keys:
|
||||
* - matchId (string): The match ID
|
||||
* - events (array): Array of event objects with matchMinute, eventTypeName, playerName, teamName, timestamp
|
||||
* - hasMatchStarted (bool): Whether the match has begun
|
||||
* - hasMatchEnded (bool): Whether the match has concluded
|
||||
* - matchStateName (string|null): Current match status label
|
||||
*/
|
||||
public function get_match_events($request) {
|
||||
$match_id = $request->get_param('match_id');
|
||||
if (empty($match_id)) {
|
||||
return new WP_REST_Response(array('error' => 'match_id required'), 400);
|
||||
}
|
||||
|
||||
$api = new Swi_Foot_API();
|
||||
|
||||
// Get match details for status
|
||||
$match = $api->get_match_details($match_id);
|
||||
if (is_wp_error($match)) {
|
||||
return new WP_REST_Response(array('error' => $match->get_error_message()), 400);
|
||||
}
|
||||
|
||||
// Try saved finished match data first
|
||||
$events = array();
|
||||
$saved = $api->get_finished_match_data($match_id);
|
||||
if ($saved) {
|
||||
$events = $saved['events'] ?? array();
|
||||
} else {
|
||||
// Fetch live events
|
||||
$events = $api->get_match_events($match_id);
|
||||
if (is_wp_error($events)) {
|
||||
$events = array();
|
||||
}
|
||||
}
|
||||
|
||||
// Get event ordering preference from query parameter
|
||||
$event_order = $request->get_param('event_order') ?? 'dynamic';
|
||||
|
||||
// Convert exactEventTime to timestamps for sorting
|
||||
if (!empty($events)) {
|
||||
foreach ($events as &$event) {
|
||||
// Convert ISO 8601 format (2026-03-14T17:03:50.437) to timestamp
|
||||
// Strip milliseconds if present, keep only date and time part
|
||||
$event['_timestamp'] = strtotime(substr($event['exactEventTime'], 0, 19));
|
||||
}
|
||||
unset($event);
|
||||
|
||||
// Sort by timestamp
|
||||
if ($event_order === 'dynamic') {
|
||||
// Dynamic: newest first while match is live, chronological after match ends
|
||||
$match_has_ended = !empty($match['hasMatchEnded']);
|
||||
|
||||
usort($events, function ($a, $b) use ($match_has_ended) {
|
||||
if ($match_has_ended) {
|
||||
// Chronological (ascending): oldest first
|
||||
return $a['_timestamp'] - $b['_timestamp'];
|
||||
} else {
|
||||
// Descending: newest first
|
||||
return $b['_timestamp'] - $a['_timestamp'];
|
||||
}
|
||||
});
|
||||
} elseif ($event_order === 'newest_first') {
|
||||
// Always newest first (descending)
|
||||
usort($events, function ($a, $b) {
|
||||
return $b['_timestamp'] - $a['_timestamp'];
|
||||
});
|
||||
} elseif ($event_order === 'oldest_first') {
|
||||
// Always oldest first (ascending)
|
||||
usort($events, function ($a, $b) {
|
||||
return $a['_timestamp'] - $b['_timestamp'];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response(array(
|
||||
'matchId' => $match_id,
|
||||
'events' => (array)$events,
|
||||
'hasMatchStarted' => $match['hasMatchStarted'] ?? false,
|
||||
'hasMatchEnded' => $match['hasMatchEnded'] ?? false,
|
||||
'matchStateName' => $match['matchStateName'] ?? null,
|
||||
));
|
||||
}
|
||||
|
||||
public function admin_refresh_teams($request) {
|
||||
$api = new Swi_Foot_API();
|
||||
|
||||
// Collect debug information to assist troubleshooting 401 responses
|
||||
$debug = array();
|
||||
if (method_exists($api, 'debug_get_token_info')) {
|
||||
$debug = $api->debug_get_token_info();
|
||||
error_log('Swiss Football Debug: refresh-teams token_present=' . ($debug['token_present'] ? '1' : '0') . ' base=' . $debug['base_url'] . ' url=' . $debug['team_list_url']);
|
||||
}
|
||||
|
||||
delete_transient('swi_foot_teams');
|
||||
$teams = $api->get_teams();
|
||||
if (is_wp_error($teams)) {
|
||||
// Try to include debug information in response for admin callers
|
||||
$err = $teams->get_error_message();
|
||||
error_log('Swiss Football API: refresh-teams failed - ' . $err);
|
||||
return new WP_REST_Response(array('error' => $err, 'debug' => $debug), 400);
|
||||
}
|
||||
|
||||
return rest_ensure_response(array('success' => true, 'data' => $teams, 'debug' => $debug));
|
||||
}
|
||||
|
||||
public function admin_clear_cache($request) {
|
||||
$keys = get_transient('swi_foot_match_keys');
|
||||
if (is_array($keys)) {
|
||||
foreach ($keys as $mid) {
|
||||
delete_transient('swi_foot_match_' . $mid);
|
||||
}
|
||||
}
|
||||
delete_transient('swi_foot_match_keys');
|
||||
delete_transient('swi_foot_teams');
|
||||
return rest_ensure_response(array('success' => true));
|
||||
}
|
||||
|
||||
public function admin_test_connection($request) {
|
||||
$api = new Swi_Foot_API();
|
||||
if (method_exists($api, 'test_connection')) {
|
||||
$ok = $api->test_connection();
|
||||
} else {
|
||||
$ok = false;
|
||||
}
|
||||
if ($ok) return rest_ensure_response(array('success' => true));
|
||||
return new WP_REST_Response(array('error' => 'Connection failed'), 400);
|
||||
}
|
||||
|
||||
public function admin_delete_finished_match($request) {
|
||||
$match_id = $request->get_param('match_id');
|
||||
if (empty($match_id)) return new WP_REST_Response(array('error' => 'match_id required'), 400);
|
||||
$data = get_option('swi_foot_finished_matches', array());
|
||||
if (isset($data[$match_id])) {
|
||||
unset($data[$match_id]);
|
||||
update_option('swi_foot_finished_matches', $data);
|
||||
return rest_ensure_response(array('success' => true));
|
||||
}
|
||||
return new WP_REST_Response(array('error' => 'not found'), 404);
|
||||
}
|
||||
|
||||
public function admin_delete_all_finished_matches($request) {
|
||||
delete_option('swi_foot_finished_matches');
|
||||
return rest_ensure_response(array('success' => true));
|
||||
}
|
||||
}
|
||||
|
||||
new Swi_Foot_REST();
|
||||
1077
includes/class-swi-foot-shortcodes.php
Normal file
1077
includes/class-swi-foot-shortcodes.php
Normal file
File diff suppressed because it is too large
Load Diff
BIN
languages/swi_foot_matchdata-de_CH.mo
Normal file
BIN
languages/swi_foot_matchdata-de_CH.mo
Normal file
Binary file not shown.
705
languages/swi_foot_matchdata-de_CH.po
Normal file
705
languages/swi_foot_matchdata-de_CH.po
Normal file
@ -0,0 +1,705 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Swiss Football Matchdata\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-18 14:50+0100\n"
|
||||
"PO-Revision-Date: 2025-02-15 12:00+0000\n"
|
||||
"Language: de_CH\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:18 includes/class-swi-foot-admin.php:249
|
||||
msgid "Swiss Football Matchdata Settings"
|
||||
msgstr "Swiss Football Matchdata Einstellungen"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:19
|
||||
msgid "Swiss Football"
|
||||
msgstr "Swiss Football"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:37
|
||||
#, fuzzy
|
||||
msgid "API Configuration"
|
||||
msgstr "API-Verbindung testen"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:42
|
||||
msgid "API Base URL"
|
||||
msgstr "API Basis-URL"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:43
|
||||
msgid "API Username (Application Key)"
|
||||
msgstr "API Benutzername (Application Key)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:44
|
||||
msgid "API Password (Application Pass)"
|
||||
msgstr "API Passwort (Application Pass)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:45
|
||||
msgid "Verein ID (Club ID)"
|
||||
msgstr "Vereins-ID (Club ID)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:46
|
||||
msgid "Season ID"
|
||||
msgstr "Saison-ID"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:50
|
||||
msgid "Cache Settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:55
|
||||
msgid "Match Data Cache Duration (seconds)"
|
||||
msgstr "Cache-Dauer für Spieldaten (Sekunden)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:60
|
||||
msgid "Configure your Swiss Football API credentials here."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:65
|
||||
msgid "Configure caching settings for match data."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:72
|
||||
msgid "The base URL for the Swiss Football API"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:79
|
||||
#, fuzzy
|
||||
msgid "Your API application key"
|
||||
msgstr "API Benutzername (Application Key)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:86
|
||||
#, fuzzy
|
||||
msgid "Your API application password"
|
||||
msgstr "API Passwort (Application Pass)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:93
|
||||
#, fuzzy
|
||||
msgid "Enter your club's Verein ID (Club ID)"
|
||||
msgstr "Vereins-ID (Club ID)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:100
|
||||
msgid "Current season ID (usually the year)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:107
|
||||
msgid "How long to cache match data in seconds (10-300)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:175
|
||||
#, fuzzy
|
||||
msgid "Swiss Football Shortcodes"
|
||||
msgstr "Swiss Football"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:187
|
||||
msgid "Available Shortcodes:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:190
|
||||
msgid "Full Match Display:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:196
|
||||
msgid "Individual Match Elements:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:209
|
||||
msgid "Parameters:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:219
|
||||
msgid "Click any shortcode above to copy it to clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:227 includes/class-swi-foot-admin.php:237
|
||||
msgid "Shortcode copied to clipboard!"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:260
|
||||
msgid "Connection Test"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:263
|
||||
msgid "Test API Connection"
|
||||
msgstr "API-Verbindung testen"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:270
|
||||
msgid "Team Management"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:273
|
||||
msgid "Refresh Teams List"
|
||||
msgstr "Mannschaftsliste aktualisieren"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:284
|
||||
msgid "Cache Management"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:287
|
||||
msgid "Clear Match Data Cache"
|
||||
msgstr "Spieldaten-Cache leeren"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:295
|
||||
#, fuzzy
|
||||
msgid "Finished Matches Data"
|
||||
msgstr "Alle beendeten Spiele löschen"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:300
|
||||
msgid "Quick Shortcode Reference"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:303
|
||||
msgid "Common Examples:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:304
|
||||
msgid "Display full match info:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:308
|
||||
msgid "Show next match for a team:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:312
|
||||
msgid "Individual elements in text:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "The match between"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "is on"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:328
|
||||
#, php-format
|
||||
msgid "Error loading teams: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:335
|
||||
msgid "No teams found. Please check your API configuration."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:340
|
||||
msgid "Available Teams:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:360
|
||||
#, php-format
|
||||
msgid "Currently caching %d match records with %d second cache duration."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:375
|
||||
msgid "Cache Range:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:376
|
||||
msgid "Oldest:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:377
|
||||
msgid "Newest:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:386
|
||||
msgid "No finished match data stored."
|
||||
msgstr "Keine gespeicherten, beendeten Spiele vorhanden."
|
||||
|
||||
#: includes/class-swi-foot-admin.php:393
|
||||
#, fuzzy
|
||||
msgid "Match ID"
|
||||
msgstr "Spielkader"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:394
|
||||
msgid "Saved At"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:395
|
||||
msgid "Players"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:396
|
||||
#: includes/class-swi-foot-shortcodes.php:768
|
||||
msgid "Bench"
|
||||
msgstr "Ersatzbank"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:397
|
||||
#, fuzzy
|
||||
msgid "Events"
|
||||
msgstr "Spielereignisse"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:415
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:421
|
||||
msgid "Clear All Finished Matches"
|
||||
msgstr "Alle beendeten Spiele löschen"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:428
|
||||
#, fuzzy
|
||||
msgid "Delete this finished match data?"
|
||||
msgstr "Keine gespeicherten, beendeten Spiele vorhanden."
|
||||
|
||||
#: includes/class-swi-foot-admin.php:445
|
||||
#, fuzzy
|
||||
msgid "Clear all finished match data?"
|
||||
msgstr "Alle beendeten Spiele löschen"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:218
|
||||
msgid "Standings: No team provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:226
|
||||
#, php-format
|
||||
msgid "Error loading standings: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:231
|
||||
msgid "No standings data available."
|
||||
msgstr "Keine Ranglistendaten verfügbar."
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:237
|
||||
#: includes/class-swi-foot-shortcodes.php:631
|
||||
msgid "Current Standings"
|
||||
msgstr "Aktuelle Rangliste"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:241
|
||||
#: includes/class-swi-foot-shortcodes.php:635
|
||||
msgid "Pos"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:242
|
||||
#: includes/class-swi-foot-shortcodes.php:636
|
||||
#, fuzzy
|
||||
msgid "Team"
|
||||
msgstr "Heimmannschaft"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:243
|
||||
#: includes/class-swi-foot-shortcodes.php:637
|
||||
msgid "P"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:244
|
||||
#: includes/class-swi-foot-shortcodes.php:638
|
||||
msgid "W"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:245
|
||||
#: includes/class-swi-foot-shortcodes.php:639
|
||||
msgid "D"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:246
|
||||
#: includes/class-swi-foot-shortcodes.php:640
|
||||
msgid "L"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:247
|
||||
#: includes/class-swi-foot-shortcodes.php:641
|
||||
msgid "GF"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:248
|
||||
#: includes/class-swi-foot-shortcodes.php:642
|
||||
msgid "GA"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:249
|
||||
#: includes/class-swi-foot-shortcodes.php:643
|
||||
msgid "Pts"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:286
|
||||
msgid "Schedule: No team provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:294
|
||||
#, php-format
|
||||
msgid "Error loading schedule: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:314
|
||||
msgid "Upcoming Matches"
|
||||
msgstr "Bevorstehende Spiele"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:316
|
||||
msgid "No upcoming matches scheduled."
|
||||
msgstr "Keine anstehenden Spiele geplant."
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:406
|
||||
msgid "Match Roster: No match provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:438
|
||||
msgid "Match Events: No match provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:455
|
||||
msgid "Please configure your API settings and ensure teams are available."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:462
|
||||
#, php-format
|
||||
msgid "Please select a team to display %s:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:464
|
||||
msgid "Select a team..."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:201
|
||||
#, fuzzy
|
||||
msgid "Match data not available"
|
||||
msgstr "Keine Ranglistendaten verfügbar."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:440 src/format-shortcode.js:201
|
||||
msgid "Ja"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:440 src/format-shortcode.js:201
|
||||
msgid "Nein"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:619
|
||||
msgid "Team ID required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:625
|
||||
#, fuzzy
|
||||
msgid "Standings data not available"
|
||||
msgstr "Keine Ranglistendaten verfügbar."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:679
|
||||
msgid "Parameter \"side\" must be \"home\" or \"away\""
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:691
|
||||
#: includes/class-swi-foot-shortcodes.php:812
|
||||
msgid "Match ID required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:703
|
||||
#, fuzzy
|
||||
msgid "Roster data not available"
|
||||
msgstr "Keine Ranglistendaten verfügbar."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:743
|
||||
#, fuzzy
|
||||
msgid "Team Roster"
|
||||
msgstr "Spielkader"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:764
|
||||
#, fuzzy
|
||||
msgid "No roster data available."
|
||||
msgstr "Keine Ranglistendaten verfügbar."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:822
|
||||
#, fuzzy
|
||||
msgid "Events data not available"
|
||||
msgstr "Keine Ranglistendaten verfügbar."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:882
|
||||
#, fuzzy
|
||||
msgid "Live match events"
|
||||
msgstr "Spielereignisse"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:883
|
||||
msgid "Match Events"
|
||||
msgstr "Spielereignisse"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:889
|
||||
#, fuzzy
|
||||
msgid "Match minute"
|
||||
msgstr "Spielereignisse"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:950
|
||||
msgid "No events recorded yet."
|
||||
msgstr "Bisher keine Ereignisse erfasst."
|
||||
|
||||
#: src/editor-blocks.js:59
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:60 src/editor-blocks.js:80
|
||||
msgid "Team is inherited from the container context."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:63
|
||||
msgid "Standings will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:79
|
||||
msgid "Schedule settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:81
|
||||
msgid "Limit"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:84
|
||||
msgid "Upcoming matches will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:99
|
||||
#, fuzzy
|
||||
msgid "Swiss Football Team Data"
|
||||
msgstr "Swiss Football"
|
||||
|
||||
#: src/editor-blocks.js:126
|
||||
msgid "Team Selection"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:128
|
||||
msgid "1. Select Team"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:129
|
||||
msgid "Choose a team..."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
msgid "2. What to display"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
msgid "Choose..."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
#, fuzzy
|
||||
msgid "Standings"
|
||||
msgstr "Aktuelle Rangliste"
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
#, fuzzy
|
||||
msgid "Match"
|
||||
msgstr "Spielkader"
|
||||
|
||||
#: src/editor-blocks.js:138
|
||||
msgid "Team data shortcode generator"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:161
|
||||
msgid "Swiss Football Match Roster"
|
||||
msgstr "Schweizer Fussball Spielerliste"
|
||||
|
||||
#: src/editor-blocks.js:168 src/editor-blocks.js:206
|
||||
msgid "Display Options"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:169 src/editor-blocks.js:207
|
||||
msgid "Match and team are inherited from the container context."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:171
|
||||
#, fuzzy
|
||||
msgid "Team Side"
|
||||
msgstr "Heimmannschaft"
|
||||
|
||||
#: src/editor-blocks.js:173
|
||||
#, fuzzy
|
||||
msgid "Home Team"
|
||||
msgstr "Heimmannschaft"
|
||||
|
||||
#: src/editor-blocks.js:173
|
||||
msgid "Away Team"
|
||||
msgstr "Gastmannschaft"
|
||||
|
||||
#: src/editor-blocks.js:177
|
||||
msgid "Include Bench Players"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:183
|
||||
msgid "Match roster will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:199
|
||||
msgid "Swiss Football Match Events"
|
||||
msgstr "Schweizer Fussball Match-Ereignisse"
|
||||
|
||||
#: src/editor-blocks.js:209
|
||||
msgid "Refresh Interval (seconds)"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:217
|
||||
msgid "Event Order"
|
||||
msgstr "Ereignisreihenfolge"
|
||||
|
||||
#: src/editor-blocks.js:221
|
||||
msgid "Dynamic (Newest first while live, chronological after)"
|
||||
msgstr "Dynamisch (Neueste zuerst während Live-Spiel, chronologisch danach)"
|
||||
|
||||
#: src/editor-blocks.js:222
|
||||
msgid "Newest First"
|
||||
msgstr "Neueste zuerst"
|
||||
|
||||
#: src/editor-blocks.js:223
|
||||
msgid "Oldest First"
|
||||
msgstr "Älteste zuerst"
|
||||
|
||||
#: src/editor-blocks.js:225
|
||||
msgid ""
|
||||
"Dynamic: newest events at top while match is ongoing, chronological (oldest "
|
||||
"first) after match ends"
|
||||
msgstr ""
|
||||
"Dynamisch: Neueste Ereignisse oben während Spiel läuft, chronologisch "
|
||||
"(älteste zuerst) nach Spielende"
|
||||
|
||||
#: src/editor-blocks.js:229
|
||||
msgid "Live events will be displayed on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:31
|
||||
msgid "— Select data point…"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:32
|
||||
msgid "Match-Typ"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:33
|
||||
msgid "Liga"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:34
|
||||
msgid "Gruppe"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:35
|
||||
msgid "Runde"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:36
|
||||
msgid "Spielfeld"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:37
|
||||
msgid "Heimteam"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:38
|
||||
msgid "Gastteam"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:39
|
||||
msgid "Tore Heim"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:40
|
||||
msgid "Tore Gast"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:41
|
||||
msgid "Tore Halbzeit Heim"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:42
|
||||
msgid "Tore Halbzeit Gast"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:43
|
||||
msgid "Spieltag"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:44
|
||||
msgid "Spielzeit"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:45
|
||||
msgid "Spielstatus"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:46
|
||||
msgid "Spiel pausiert"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:47
|
||||
msgid "Spiel beendet"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:52
|
||||
msgid "— Select shortcode…"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:53
|
||||
msgid "Heimteam Logo"
|
||||
msgstr "Heimteam-Logo"
|
||||
|
||||
#: src/format-shortcode.js:54
|
||||
msgid "Gastteam Logo"
|
||||
msgstr "Gastteam-Logo"
|
||||
|
||||
#: src/format-shortcode.js:55
|
||||
msgid "Heimteam Logo (URL)"
|
||||
msgstr "Heimteam-Logo (URL)"
|
||||
|
||||
#: src/format-shortcode.js:56
|
||||
msgid "Gastteam Logo (URL)"
|
||||
msgstr "Gastteam-Logo (URL)"
|
||||
|
||||
#: src/format-shortcode.js:229 src/format-shortcode.js:242
|
||||
#: src/format-shortcode.js:356 src/format-shortcode.js:390
|
||||
#, fuzzy
|
||||
msgid "Insert Match Data"
|
||||
msgstr "Alle beendeten Spiele löschen"
|
||||
|
||||
#: src/format-shortcode.js:234
|
||||
msgid "No match data available on this page. Please add match context first."
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:255
|
||||
#, fuzzy
|
||||
msgid "Match Data"
|
||||
msgstr "Spielkader"
|
||||
|
||||
#: src/format-shortcode.js:265
|
||||
msgid "Shortcodes"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:278
|
||||
msgid "Data Point"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:282
|
||||
msgid "Select which match data to display"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:289
|
||||
msgid "Preview:"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:297
|
||||
msgid "(no value)"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:307
|
||||
msgid "Insert Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:320
|
||||
msgid "Shortcode"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:324
|
||||
msgid "Select a shortcode to insert (logos, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:331
|
||||
msgid "Shortcode:"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:341
|
||||
msgid "Insert Shortcode"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Display match roster for a selected match and side."
|
||||
#~ msgstr ""
|
||||
#~ "Zeigt die Spielerliste für ein ausgewähltes Spiel und eine Mannschaft an."
|
||||
|
||||
#~ msgid "Live match events with optional auto-refresh."
|
||||
#~ msgstr "Live-Match-Ereignisse mit optionaler Selbstaktualisierung."
|
||||
BIN
languages/swi_foot_matchdata-en_US.mo
Normal file
BIN
languages/swi_foot_matchdata-en_US.mo
Normal file
Binary file not shown.
704
languages/swi_foot_matchdata-en_US.po
Normal file
704
languages/swi_foot_matchdata-en_US.po
Normal file
@ -0,0 +1,704 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Swiss Football Matchdata\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-18 14:50+0100\n"
|
||||
"PO-Revision-Date: 2025-02-15 12:00+0000\n"
|
||||
"Language: en_US\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:18 includes/class-swi-foot-admin.php:249
|
||||
msgid "Swiss Football Matchdata Settings"
|
||||
msgstr "Swiss Football Matchdata Settings"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:19
|
||||
msgid "Swiss Football"
|
||||
msgstr "Swiss Football"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:37
|
||||
#, fuzzy
|
||||
msgid "API Configuration"
|
||||
msgstr "Test API Connection"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:42
|
||||
msgid "API Base URL"
|
||||
msgstr "API Base URL"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:43
|
||||
msgid "API Username (Application Key)"
|
||||
msgstr "API Username (Application Key)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:44
|
||||
msgid "API Password (Application Pass)"
|
||||
msgstr "API Password (Application Pass)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:45
|
||||
msgid "Verein ID (Club ID)"
|
||||
msgstr "Verein ID (Club ID)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:46
|
||||
msgid "Season ID"
|
||||
msgstr "Season ID"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:50
|
||||
msgid "Cache Settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:55
|
||||
msgid "Match Data Cache Duration (seconds)"
|
||||
msgstr "Match Data Cache Duration (seconds)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:60
|
||||
msgid "Configure your Swiss Football API credentials here."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:65
|
||||
msgid "Configure caching settings for match data."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:72
|
||||
msgid "The base URL for the Swiss Football API"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:79
|
||||
#, fuzzy
|
||||
msgid "Your API application key"
|
||||
msgstr "API Username (Application Key)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:86
|
||||
#, fuzzy
|
||||
msgid "Your API application password"
|
||||
msgstr "API Password (Application Pass)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:93
|
||||
#, fuzzy
|
||||
msgid "Enter your club's Verein ID (Club ID)"
|
||||
msgstr "Verein ID (Club ID)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:100
|
||||
msgid "Current season ID (usually the year)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:107
|
||||
msgid "How long to cache match data in seconds (10-300)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:175
|
||||
#, fuzzy
|
||||
msgid "Swiss Football Shortcodes"
|
||||
msgstr "Swiss Football"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:187
|
||||
msgid "Available Shortcodes:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:190
|
||||
msgid "Full Match Display:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:196
|
||||
msgid "Individual Match Elements:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:209
|
||||
msgid "Parameters:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:219
|
||||
msgid "Click any shortcode above to copy it to clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:227 includes/class-swi-foot-admin.php:237
|
||||
msgid "Shortcode copied to clipboard!"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:260
|
||||
msgid "Connection Test"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:263
|
||||
msgid "Test API Connection"
|
||||
msgstr "Test API Connection"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:270
|
||||
msgid "Team Management"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:273
|
||||
msgid "Refresh Teams List"
|
||||
msgstr "Refresh Teams List"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:284
|
||||
msgid "Cache Management"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:287
|
||||
msgid "Clear Match Data Cache"
|
||||
msgstr "Clear Match Data Cache"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:295
|
||||
#, fuzzy
|
||||
msgid "Finished Matches Data"
|
||||
msgstr "Clear All Finished Matches"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:300
|
||||
msgid "Quick Shortcode Reference"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:303
|
||||
msgid "Common Examples:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:304
|
||||
msgid "Display full match info:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:308
|
||||
msgid "Show next match for a team:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:312
|
||||
msgid "Individual elements in text:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "The match between"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "is on"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:328
|
||||
#, php-format
|
||||
msgid "Error loading teams: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:335
|
||||
msgid "No teams found. Please check your API configuration."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:340
|
||||
msgid "Available Teams:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:360
|
||||
#, php-format
|
||||
msgid "Currently caching %d match records with %d second cache duration."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:375
|
||||
msgid "Cache Range:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:376
|
||||
msgid "Oldest:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:377
|
||||
msgid "Newest:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:386
|
||||
msgid "No finished match data stored."
|
||||
msgstr "No finished match data stored."
|
||||
|
||||
#: includes/class-swi-foot-admin.php:393
|
||||
#, fuzzy
|
||||
msgid "Match ID"
|
||||
msgstr "Match Roster"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:394
|
||||
msgid "Saved At"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:395
|
||||
msgid "Players"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:396
|
||||
#: includes/class-swi-foot-shortcodes.php:768
|
||||
msgid "Bench"
|
||||
msgstr "Bench"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:397
|
||||
#, fuzzy
|
||||
msgid "Events"
|
||||
msgstr "Match Events"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:415
|
||||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:421
|
||||
msgid "Clear All Finished Matches"
|
||||
msgstr "Clear All Finished Matches"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:428
|
||||
#, fuzzy
|
||||
msgid "Delete this finished match data?"
|
||||
msgstr "No finished match data stored."
|
||||
|
||||
#: includes/class-swi-foot-admin.php:445
|
||||
#, fuzzy
|
||||
msgid "Clear all finished match data?"
|
||||
msgstr "Clear All Finished Matches"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:218
|
||||
msgid "Standings: No team provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:226
|
||||
#, php-format
|
||||
msgid "Error loading standings: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:231
|
||||
msgid "No standings data available."
|
||||
msgstr "No standings data available."
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:237
|
||||
#: includes/class-swi-foot-shortcodes.php:631
|
||||
msgid "Current Standings"
|
||||
msgstr "Current Standings"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:241
|
||||
#: includes/class-swi-foot-shortcodes.php:635
|
||||
msgid "Pos"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:242
|
||||
#: includes/class-swi-foot-shortcodes.php:636
|
||||
#, fuzzy
|
||||
msgid "Team"
|
||||
msgstr "Home Team"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:243
|
||||
#: includes/class-swi-foot-shortcodes.php:637
|
||||
msgid "P"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:244
|
||||
#: includes/class-swi-foot-shortcodes.php:638
|
||||
msgid "W"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:245
|
||||
#: includes/class-swi-foot-shortcodes.php:639
|
||||
msgid "D"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:246
|
||||
#: includes/class-swi-foot-shortcodes.php:640
|
||||
msgid "L"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:247
|
||||
#: includes/class-swi-foot-shortcodes.php:641
|
||||
msgid "GF"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:248
|
||||
#: includes/class-swi-foot-shortcodes.php:642
|
||||
msgid "GA"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:249
|
||||
#: includes/class-swi-foot-shortcodes.php:643
|
||||
msgid "Pts"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:286
|
||||
msgid "Schedule: No team provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:294
|
||||
#, php-format
|
||||
msgid "Error loading schedule: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:314
|
||||
msgid "Upcoming Matches"
|
||||
msgstr "Upcoming Matches"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:316
|
||||
msgid "No upcoming matches scheduled."
|
||||
msgstr "No upcoming matches scheduled."
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:406
|
||||
msgid "Match Roster: No match provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:438
|
||||
msgid "Match Events: No match provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:455
|
||||
msgid "Please configure your API settings and ensure teams are available."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:462
|
||||
#, php-format
|
||||
msgid "Please select a team to display %s:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:464
|
||||
msgid "Select a team..."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:201
|
||||
#, fuzzy
|
||||
msgid "Match data not available"
|
||||
msgstr "No standings data available."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:440 src/format-shortcode.js:201
|
||||
msgid "Ja"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:440 src/format-shortcode.js:201
|
||||
msgid "Nein"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:619
|
||||
msgid "Team ID required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:625
|
||||
#, fuzzy
|
||||
msgid "Standings data not available"
|
||||
msgstr "No standings data available."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:679
|
||||
msgid "Parameter \"side\" must be \"home\" or \"away\""
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:691
|
||||
#: includes/class-swi-foot-shortcodes.php:812
|
||||
msgid "Match ID required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:703
|
||||
#, fuzzy
|
||||
msgid "Roster data not available"
|
||||
msgstr "No standings data available."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:743
|
||||
#, fuzzy
|
||||
msgid "Team Roster"
|
||||
msgstr "Match Roster"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:764
|
||||
#, fuzzy
|
||||
msgid "No roster data available."
|
||||
msgstr "No standings data available."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:822
|
||||
#, fuzzy
|
||||
msgid "Events data not available"
|
||||
msgstr "No standings data available."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:882
|
||||
#, fuzzy
|
||||
msgid "Live match events"
|
||||
msgstr "Match Events"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:883
|
||||
msgid "Match Events"
|
||||
msgstr "Match Events"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:889
|
||||
#, fuzzy
|
||||
msgid "Match minute"
|
||||
msgstr "Match Events"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:950
|
||||
msgid "No events recorded yet."
|
||||
msgstr "No events recorded yet."
|
||||
|
||||
#: src/editor-blocks.js:59
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:60 src/editor-blocks.js:80
|
||||
msgid "Team is inherited from the container context."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:63
|
||||
msgid "Standings will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:79
|
||||
msgid "Schedule settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:81
|
||||
msgid "Limit"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:84
|
||||
msgid "Upcoming matches will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:99
|
||||
#, fuzzy
|
||||
msgid "Swiss Football Team Data"
|
||||
msgstr "Swiss Football"
|
||||
|
||||
#: src/editor-blocks.js:126
|
||||
msgid "Team Selection"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:128
|
||||
msgid "1. Select Team"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:129
|
||||
msgid "Choose a team..."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
msgid "2. What to display"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
msgid "Choose..."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
#, fuzzy
|
||||
msgid "Standings"
|
||||
msgstr "Current Standings"
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
#, fuzzy
|
||||
msgid "Match"
|
||||
msgstr "Match Roster"
|
||||
|
||||
#: src/editor-blocks.js:138
|
||||
msgid "Team data shortcode generator"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:161
|
||||
msgid "Swiss Football Match Roster"
|
||||
msgstr "Swiss Football Match Roster"
|
||||
|
||||
#: src/editor-blocks.js:168 src/editor-blocks.js:206
|
||||
msgid "Display Options"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:169 src/editor-blocks.js:207
|
||||
msgid "Match and team are inherited from the container context."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:171
|
||||
#, fuzzy
|
||||
msgid "Team Side"
|
||||
msgstr "Home Team"
|
||||
|
||||
#: src/editor-blocks.js:173
|
||||
#, fuzzy
|
||||
msgid "Home Team"
|
||||
msgstr "Home Team"
|
||||
|
||||
#: src/editor-blocks.js:173
|
||||
msgid "Away Team"
|
||||
msgstr "Away Team"
|
||||
|
||||
#: src/editor-blocks.js:177
|
||||
msgid "Include Bench Players"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:183
|
||||
msgid "Match roster will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:199
|
||||
msgid "Swiss Football Match Events"
|
||||
msgstr "Swiss Football Match Events"
|
||||
|
||||
#: src/editor-blocks.js:209
|
||||
msgid "Refresh Interval (seconds)"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:217
|
||||
msgid "Event Order"
|
||||
msgstr "Event Order"
|
||||
|
||||
#: src/editor-blocks.js:221
|
||||
msgid "Dynamic (Newest first while live, chronological after)"
|
||||
msgstr "Dynamic (Newest first while live, chronological after)"
|
||||
|
||||
#: src/editor-blocks.js:222
|
||||
msgid "Newest First"
|
||||
msgstr "Newest First"
|
||||
|
||||
#: src/editor-blocks.js:223
|
||||
msgid "Oldest First"
|
||||
msgstr "Oldest First"
|
||||
|
||||
#: src/editor-blocks.js:225
|
||||
msgid ""
|
||||
"Dynamic: newest events at top while match is ongoing, chronological (oldest "
|
||||
"first) after match ends"
|
||||
msgstr ""
|
||||
"Dynamic: newest events at top while match is ongoing, chronological (oldest "
|
||||
"first) after match ends"
|
||||
|
||||
#: src/editor-blocks.js:229
|
||||
msgid "Live events will be displayed on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:31
|
||||
msgid "— Select data point…"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:32
|
||||
msgid "Match-Typ"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:33
|
||||
msgid "Liga"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:34
|
||||
msgid "Gruppe"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:35
|
||||
msgid "Runde"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:36
|
||||
msgid "Spielfeld"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:37
|
||||
msgid "Heimteam"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:38
|
||||
msgid "Gastteam"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:39
|
||||
msgid "Tore Heim"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:40
|
||||
msgid "Tore Gast"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:41
|
||||
msgid "Tore Halbzeit Heim"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:42
|
||||
msgid "Tore Halbzeit Gast"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:43
|
||||
msgid "Spieltag"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:44
|
||||
msgid "Spielzeit"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:45
|
||||
msgid "Spielstatus"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:46
|
||||
msgid "Spiel pausiert"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:47
|
||||
msgid "Spiel beendet"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:52
|
||||
msgid "— Select shortcode…"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:53
|
||||
msgid "Heimteam Logo"
|
||||
msgstr "Home Team Logo"
|
||||
|
||||
#: src/format-shortcode.js:54
|
||||
msgid "Gastteam Logo"
|
||||
msgstr "Away Team Logo"
|
||||
|
||||
#: src/format-shortcode.js:55
|
||||
msgid "Heimteam Logo (URL)"
|
||||
msgstr "Home Team Logo (URL)"
|
||||
|
||||
#: src/format-shortcode.js:56
|
||||
msgid "Gastteam Logo (URL)"
|
||||
msgstr "Away Team Logo (URL)"
|
||||
|
||||
#: src/format-shortcode.js:229 src/format-shortcode.js:242
|
||||
#: src/format-shortcode.js:356 src/format-shortcode.js:390
|
||||
#, fuzzy
|
||||
msgid "Insert Match Data"
|
||||
msgstr "Clear All Finished Matches"
|
||||
|
||||
#: src/format-shortcode.js:234
|
||||
msgid "No match data available on this page. Please add match context first."
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:255
|
||||
#, fuzzy
|
||||
msgid "Match Data"
|
||||
msgstr "Match Roster"
|
||||
|
||||
#: src/format-shortcode.js:265
|
||||
msgid "Shortcodes"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:278
|
||||
msgid "Data Point"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:282
|
||||
msgid "Select which match data to display"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:289
|
||||
msgid "Preview:"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:297
|
||||
msgid "(no value)"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:307
|
||||
msgid "Insert Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:320
|
||||
msgid "Shortcode"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:324
|
||||
msgid "Select a shortcode to insert (logos, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:331
|
||||
msgid "Shortcode:"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:341
|
||||
msgid "Insert Shortcode"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Display match roster for a selected match and side."
|
||||
#~ msgstr "Display match roster for a selected match and side."
|
||||
|
||||
#~ msgid "Live match events with optional auto-refresh."
|
||||
#~ msgstr "Live match events with optional auto-refresh."
|
||||
BIN
languages/swi_foot_matchdata-fr_FR.mo
Normal file
BIN
languages/swi_foot_matchdata-fr_FR.mo
Normal file
Binary file not shown.
704
languages/swi_foot_matchdata-fr_FR.po
Normal file
704
languages/swi_foot_matchdata-fr_FR.po
Normal file
@ -0,0 +1,704 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Swiss Football Matchdata\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-18 14:50+0100\n"
|
||||
"PO-Revision-Date: 2025-02-15 12:00+0000\n"
|
||||
"Language: fr_FR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:18 includes/class-swi-foot-admin.php:249
|
||||
msgid "Swiss Football Matchdata Settings"
|
||||
msgstr "Paramètres Swiss Football Matchdata"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:19
|
||||
msgid "Swiss Football"
|
||||
msgstr "Swiss Football"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:37
|
||||
#, fuzzy
|
||||
msgid "API Configuration"
|
||||
msgstr "Tester la connexion API"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:42
|
||||
msgid "API Base URL"
|
||||
msgstr "URL de base API"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:43
|
||||
msgid "API Username (Application Key)"
|
||||
msgstr "Nom d'utilisateur API (Clé d'application)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:44
|
||||
msgid "API Password (Application Pass)"
|
||||
msgstr "Mot de passe API (Application Pass)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:45
|
||||
msgid "Verein ID (Club ID)"
|
||||
msgstr "ID du club (Club ID)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:46
|
||||
msgid "Season ID"
|
||||
msgstr "ID de la saison"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:50
|
||||
msgid "Cache Settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:55
|
||||
msgid "Match Data Cache Duration (seconds)"
|
||||
msgstr "Durée du cache des données de match (secondes)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:60
|
||||
msgid "Configure your Swiss Football API credentials here."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:65
|
||||
msgid "Configure caching settings for match data."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:72
|
||||
msgid "The base URL for the Swiss Football API"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:79
|
||||
#, fuzzy
|
||||
msgid "Your API application key"
|
||||
msgstr "Nom d'utilisateur API (Clé d'application)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:86
|
||||
#, fuzzy
|
||||
msgid "Your API application password"
|
||||
msgstr "Mot de passe API (Application Pass)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:93
|
||||
#, fuzzy
|
||||
msgid "Enter your club's Verein ID (Club ID)"
|
||||
msgstr "ID du club (Club ID)"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:100
|
||||
msgid "Current season ID (usually the year)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:107
|
||||
msgid "How long to cache match data in seconds (10-300)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:175
|
||||
#, fuzzy
|
||||
msgid "Swiss Football Shortcodes"
|
||||
msgstr "Swiss Football"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:187
|
||||
msgid "Available Shortcodes:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:190
|
||||
msgid "Full Match Display:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:196
|
||||
msgid "Individual Match Elements:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:209
|
||||
msgid "Parameters:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:219
|
||||
msgid "Click any shortcode above to copy it to clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:227 includes/class-swi-foot-admin.php:237
|
||||
msgid "Shortcode copied to clipboard!"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:260
|
||||
msgid "Connection Test"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:263
|
||||
msgid "Test API Connection"
|
||||
msgstr "Tester la connexion API"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:270
|
||||
msgid "Team Management"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:273
|
||||
msgid "Refresh Teams List"
|
||||
msgstr "Actualiser la liste des équipes"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:284
|
||||
msgid "Cache Management"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:287
|
||||
msgid "Clear Match Data Cache"
|
||||
msgstr "Vider le cache des données de matchs"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:295
|
||||
#, fuzzy
|
||||
msgid "Finished Matches Data"
|
||||
msgstr "Effacer tous les matchs terminés"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:300
|
||||
msgid "Quick Shortcode Reference"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:303
|
||||
msgid "Common Examples:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:304
|
||||
msgid "Display full match info:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:308
|
||||
msgid "Show next match for a team:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:312
|
||||
msgid "Individual elements in text:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "The match between"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "is on"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:328
|
||||
#, php-format
|
||||
msgid "Error loading teams: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:335
|
||||
msgid "No teams found. Please check your API configuration."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:340
|
||||
msgid "Available Teams:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:360
|
||||
#, php-format
|
||||
msgid "Currently caching %d match records with %d second cache duration."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:375
|
||||
msgid "Cache Range:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:376
|
||||
msgid "Oldest:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:377
|
||||
msgid "Newest:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:386
|
||||
msgid "No finished match data stored."
|
||||
msgstr "Aucune donnée de match terminé stockée."
|
||||
|
||||
#: includes/class-swi-foot-admin.php:393
|
||||
#, fuzzy
|
||||
msgid "Match ID"
|
||||
msgstr "Composition du match"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:394
|
||||
msgid "Saved At"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:395
|
||||
msgid "Players"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:396
|
||||
#: includes/class-swi-foot-shortcodes.php:768
|
||||
msgid "Bench"
|
||||
msgstr "Remplaçants"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:397
|
||||
#, fuzzy
|
||||
msgid "Events"
|
||||
msgstr "Événements du match"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:415
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:421
|
||||
msgid "Clear All Finished Matches"
|
||||
msgstr "Effacer tous les matchs terminés"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:428
|
||||
#, fuzzy
|
||||
msgid "Delete this finished match data?"
|
||||
msgstr "Aucune donnée de match terminé stockée."
|
||||
|
||||
#: includes/class-swi-foot-admin.php:445
|
||||
#, fuzzy
|
||||
msgid "Clear all finished match data?"
|
||||
msgstr "Effacer tous les matchs terminés"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:218
|
||||
msgid "Standings: No team provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:226
|
||||
#, php-format
|
||||
msgid "Error loading standings: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:231
|
||||
msgid "No standings data available."
|
||||
msgstr "Aucun classement disponible."
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:237
|
||||
#: includes/class-swi-foot-shortcodes.php:631
|
||||
msgid "Current Standings"
|
||||
msgstr "Classement actuel"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:241
|
||||
#: includes/class-swi-foot-shortcodes.php:635
|
||||
msgid "Pos"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:242
|
||||
#: includes/class-swi-foot-shortcodes.php:636
|
||||
#, fuzzy
|
||||
msgid "Team"
|
||||
msgstr "Équipe à domicile"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:243
|
||||
#: includes/class-swi-foot-shortcodes.php:637
|
||||
msgid "P"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:244
|
||||
#: includes/class-swi-foot-shortcodes.php:638
|
||||
msgid "W"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:245
|
||||
#: includes/class-swi-foot-shortcodes.php:639
|
||||
msgid "D"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:246
|
||||
#: includes/class-swi-foot-shortcodes.php:640
|
||||
msgid "L"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:247
|
||||
#: includes/class-swi-foot-shortcodes.php:641
|
||||
msgid "GF"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:248
|
||||
#: includes/class-swi-foot-shortcodes.php:642
|
||||
msgid "GA"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:249
|
||||
#: includes/class-swi-foot-shortcodes.php:643
|
||||
msgid "Pts"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:286
|
||||
msgid "Schedule: No team provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:294
|
||||
#, php-format
|
||||
msgid "Error loading schedule: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:314
|
||||
msgid "Upcoming Matches"
|
||||
msgstr "Matchs à venir"
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:316
|
||||
msgid "No upcoming matches scheduled."
|
||||
msgstr "Aucun match à venir programmé."
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:406
|
||||
msgid "Match Roster: No match provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:438
|
||||
msgid "Match Events: No match provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:455
|
||||
msgid "Please configure your API settings and ensure teams are available."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:462
|
||||
#, php-format
|
||||
msgid "Please select a team to display %s:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:464
|
||||
msgid "Select a team..."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:201
|
||||
#, fuzzy
|
||||
msgid "Match data not available"
|
||||
msgstr "Aucun classement disponible."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:440 src/format-shortcode.js:201
|
||||
msgid "Ja"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:440 src/format-shortcode.js:201
|
||||
msgid "Nein"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:619
|
||||
msgid "Team ID required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:625
|
||||
#, fuzzy
|
||||
msgid "Standings data not available"
|
||||
msgstr "Aucun classement disponible."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:679
|
||||
msgid "Parameter \"side\" must be \"home\" or \"away\""
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:691
|
||||
#: includes/class-swi-foot-shortcodes.php:812
|
||||
msgid "Match ID required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:703
|
||||
#, fuzzy
|
||||
msgid "Roster data not available"
|
||||
msgstr "Aucun classement disponible."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:743
|
||||
#, fuzzy
|
||||
msgid "Team Roster"
|
||||
msgstr "Composition du match"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:764
|
||||
#, fuzzy
|
||||
msgid "No roster data available."
|
||||
msgstr "Aucun classement disponible."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:822
|
||||
#, fuzzy
|
||||
msgid "Events data not available"
|
||||
msgstr "Aucun classement disponible."
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:882
|
||||
#, fuzzy
|
||||
msgid "Live match events"
|
||||
msgstr "Événements du match"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:883
|
||||
msgid "Match Events"
|
||||
msgstr "Événements du match"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:889
|
||||
#, fuzzy
|
||||
msgid "Match minute"
|
||||
msgstr "Événements du match"
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:950
|
||||
msgid "No events recorded yet."
|
||||
msgstr "Aucun événement enregistré."
|
||||
|
||||
#: src/editor-blocks.js:59
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:60 src/editor-blocks.js:80
|
||||
msgid "Team is inherited from the container context."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:63
|
||||
msgid "Standings will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:79
|
||||
msgid "Schedule settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:81
|
||||
msgid "Limit"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:84
|
||||
msgid "Upcoming matches will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:99
|
||||
#, fuzzy
|
||||
msgid "Swiss Football Team Data"
|
||||
msgstr "Swiss Football"
|
||||
|
||||
#: src/editor-blocks.js:126
|
||||
msgid "Team Selection"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:128
|
||||
msgid "1. Select Team"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:129
|
||||
msgid "Choose a team..."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
msgid "2. What to display"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
msgid "Choose..."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
#, fuzzy
|
||||
msgid "Standings"
|
||||
msgstr "Classement actuel"
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
#, fuzzy
|
||||
msgid "Match"
|
||||
msgstr "Composition du match"
|
||||
|
||||
#: src/editor-blocks.js:138
|
||||
msgid "Team data shortcode generator"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:161
|
||||
msgid "Swiss Football Match Roster"
|
||||
msgstr "Feuille d'équipe du football suisse"
|
||||
|
||||
#: src/editor-blocks.js:168 src/editor-blocks.js:206
|
||||
msgid "Display Options"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:169 src/editor-blocks.js:207
|
||||
msgid "Match and team are inherited from the container context."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:171
|
||||
#, fuzzy
|
||||
msgid "Team Side"
|
||||
msgstr "Équipe à domicile"
|
||||
|
||||
#: src/editor-blocks.js:173
|
||||
#, fuzzy
|
||||
msgid "Home Team"
|
||||
msgstr "Équipe à domicile"
|
||||
|
||||
#: src/editor-blocks.js:173
|
||||
msgid "Away Team"
|
||||
msgstr "Équipe à l'extérieur"
|
||||
|
||||
#: src/editor-blocks.js:177
|
||||
msgid "Include Bench Players"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:183
|
||||
msgid "Match roster will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:199
|
||||
msgid "Swiss Football Match Events"
|
||||
msgstr "Événements des matchs de football suisse"
|
||||
|
||||
#: src/editor-blocks.js:209
|
||||
msgid "Refresh Interval (seconds)"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:217
|
||||
msgid "Event Order"
|
||||
msgstr "Ordre des événements"
|
||||
|
||||
#: src/editor-blocks.js:221
|
||||
msgid "Dynamic (Newest first while live, chronological after)"
|
||||
msgstr "Dynamique (Les plus récents en premier pendant le match, chronologiquement après)"
|
||||
|
||||
#: src/editor-blocks.js:222
|
||||
msgid "Newest First"
|
||||
msgstr "Les plus récents en premier"
|
||||
|
||||
#: src/editor-blocks.js:223
|
||||
msgid "Oldest First"
|
||||
msgstr "Les plus anciens en premier"
|
||||
|
||||
#: src/editor-blocks.js:225
|
||||
msgid ""
|
||||
"Dynamic: newest events at top while match is ongoing, chronological (oldest "
|
||||
"first) after match ends"
|
||||
msgstr ""
|
||||
"Dynamique : les plus récents en haut pendant le match, chronologiquement "
|
||||
"(les plus anciens en premier) après la fin du match"
|
||||
|
||||
#: src/editor-blocks.js:229
|
||||
msgid "Live events will be displayed on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:31
|
||||
msgid "— Select data point…"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:32
|
||||
msgid "Match-Typ"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:33
|
||||
msgid "Liga"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:34
|
||||
msgid "Gruppe"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:35
|
||||
msgid "Runde"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:36
|
||||
msgid "Spielfeld"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:37
|
||||
msgid "Heimteam"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:38
|
||||
msgid "Gastteam"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:39
|
||||
msgid "Tore Heim"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:40
|
||||
msgid "Tore Gast"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:41
|
||||
msgid "Tore Halbzeit Heim"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:42
|
||||
msgid "Tore Halbzeit Gast"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:43
|
||||
msgid "Spieltag"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:44
|
||||
msgid "Spielzeit"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:45
|
||||
msgid "Spielstatus"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:46
|
||||
msgid "Spiel pausiert"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:47
|
||||
msgid "Spiel beendet"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:52
|
||||
msgid "— Select shortcode…"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:53
|
||||
msgid "Heimteam Logo"
|
||||
msgstr "Logo de l'équipe à domicile"
|
||||
|
||||
#: src/format-shortcode.js:54
|
||||
msgid "Gastteam Logo"
|
||||
msgstr "Logo de l'équipe en déplacement"
|
||||
|
||||
#: src/format-shortcode.js:55
|
||||
msgid "Heimteam Logo (URL)"
|
||||
msgstr "Logo de l'équipe à domicile (URL)"
|
||||
|
||||
#: src/format-shortcode.js:56
|
||||
msgid "Gastteam Logo (URL)"
|
||||
msgstr "Logo de l'équipe en déplacement (URL)"
|
||||
|
||||
#: src/format-shortcode.js:229 src/format-shortcode.js:242
|
||||
#: src/format-shortcode.js:356 src/format-shortcode.js:390
|
||||
#, fuzzy
|
||||
msgid "Insert Match Data"
|
||||
msgstr "Effacer tous les matchs terminés"
|
||||
|
||||
#: src/format-shortcode.js:234
|
||||
msgid "No match data available on this page. Please add match context first."
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:255
|
||||
#, fuzzy
|
||||
msgid "Match Data"
|
||||
msgstr "Composition du match"
|
||||
|
||||
#: src/format-shortcode.js:265
|
||||
msgid "Shortcodes"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:278
|
||||
msgid "Data Point"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:282
|
||||
msgid "Select which match data to display"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:289
|
||||
msgid "Preview:"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:297
|
||||
msgid "(no value)"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:307
|
||||
msgid "Insert Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:320
|
||||
msgid "Shortcode"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:324
|
||||
msgid "Select a shortcode to insert (logos, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:331
|
||||
msgid "Shortcode:"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:341
|
||||
msgid "Insert Shortcode"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Display match roster for a selected match and side."
|
||||
#~ msgstr "Afficher l'effectif pour un match et une équipe sélectionnés."
|
||||
|
||||
#~ msgid "Live match events with optional auto-refresh."
|
||||
#~ msgstr "Événements en direct avec actualisation automatique optionnelle."
|
||||
678
languages/swi_foot_matchdata.pot
Normal file
678
languages/swi_foot_matchdata.pot
Normal file
@ -0,0 +1,678 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-18 14:50+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: includes/class-swi-foot-admin.php:18 includes/class-swi-foot-admin.php:249
|
||||
msgid "Swiss Football Matchdata Settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:19
|
||||
msgid "Swiss Football"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:37
|
||||
msgid "API Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:42
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:43
|
||||
msgid "API Username (Application Key)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:44
|
||||
msgid "API Password (Application Pass)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:45
|
||||
msgid "Verein ID (Club ID)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:46
|
||||
msgid "Season ID"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:50
|
||||
msgid "Cache Settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:55
|
||||
msgid "Match Data Cache Duration (seconds)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:60
|
||||
msgid "Configure your Swiss Football API credentials here."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:65
|
||||
msgid "Configure caching settings for match data."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:72
|
||||
msgid "The base URL for the Swiss Football API"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:79
|
||||
msgid "Your API application key"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:86
|
||||
msgid "Your API application password"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:93
|
||||
msgid "Enter your club's Verein ID (Club ID)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:100
|
||||
msgid "Current season ID (usually the year)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:107
|
||||
msgid "How long to cache match data in seconds (10-300)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:175
|
||||
msgid "Swiss Football Shortcodes"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:187
|
||||
msgid "Available Shortcodes:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:190
|
||||
msgid "Full Match Display:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:196
|
||||
msgid "Individual Match Elements:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:209
|
||||
msgid "Parameters:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:219
|
||||
msgid "Click any shortcode above to copy it to clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:227 includes/class-swi-foot-admin.php:237
|
||||
msgid "Shortcode copied to clipboard!"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:260
|
||||
msgid "Connection Test"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:263
|
||||
msgid "Test API Connection"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:270
|
||||
msgid "Team Management"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:273
|
||||
msgid "Refresh Teams List"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:284
|
||||
msgid "Cache Management"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:287
|
||||
msgid "Clear Match Data Cache"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:295
|
||||
msgid "Finished Matches Data"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:300
|
||||
msgid "Quick Shortcode Reference"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:303
|
||||
msgid "Common Examples:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:304
|
||||
msgid "Display full match info:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:308
|
||||
msgid "Show next match for a team:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:312
|
||||
msgid "Individual elements in text:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "The match between"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:313
|
||||
msgid "is on"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:328
|
||||
#, php-format
|
||||
msgid "Error loading teams: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:335
|
||||
msgid "No teams found. Please check your API configuration."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:340
|
||||
msgid "Available Teams:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:360
|
||||
#, php-format
|
||||
msgid "Currently caching %d match records with %d second cache duration."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:375
|
||||
msgid "Cache Range:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:376
|
||||
msgid "Oldest:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:377
|
||||
msgid "Newest:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:386
|
||||
msgid "No finished match data stored."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:393
|
||||
msgid "Match ID"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:394
|
||||
msgid "Saved At"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:395
|
||||
msgid "Players"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:396
|
||||
#: includes/class-swi-foot-shortcodes.php:768
|
||||
msgid "Bench"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:397
|
||||
msgid "Events"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:415
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:421
|
||||
msgid "Clear All Finished Matches"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:428
|
||||
msgid "Delete this finished match data?"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-admin.php:445
|
||||
msgid "Clear all finished match data?"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:218
|
||||
msgid "Standings: No team provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:226
|
||||
#, php-format
|
||||
msgid "Error loading standings: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:231
|
||||
msgid "No standings data available."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:237
|
||||
#: includes/class-swi-foot-shortcodes.php:631
|
||||
msgid "Current Standings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:241
|
||||
#: includes/class-swi-foot-shortcodes.php:635
|
||||
msgid "Pos"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:242
|
||||
#: includes/class-swi-foot-shortcodes.php:636
|
||||
msgid "Team"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:243
|
||||
#: includes/class-swi-foot-shortcodes.php:637
|
||||
msgid "P"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:244
|
||||
#: includes/class-swi-foot-shortcodes.php:638
|
||||
msgid "W"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:245
|
||||
#: includes/class-swi-foot-shortcodes.php:639
|
||||
msgid "D"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:246
|
||||
#: includes/class-swi-foot-shortcodes.php:640
|
||||
msgid "L"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:247
|
||||
#: includes/class-swi-foot-shortcodes.php:641
|
||||
msgid "GF"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:248
|
||||
#: includes/class-swi-foot-shortcodes.php:642
|
||||
msgid "GA"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:249
|
||||
#: includes/class-swi-foot-shortcodes.php:643
|
||||
msgid "Pts"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:286
|
||||
msgid "Schedule: No team provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:294
|
||||
#, php-format
|
||||
msgid "Error loading schedule: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:314
|
||||
msgid "Upcoming Matches"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:316
|
||||
msgid "No upcoming matches scheduled."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:406
|
||||
msgid "Match Roster: No match provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:438
|
||||
msgid "Match Events: No match provided in container context."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:455
|
||||
msgid "Please configure your API settings and ensure teams are available."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:462
|
||||
#, php-format
|
||||
msgid "Please select a team to display %s:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-blocks.php:464
|
||||
msgid "Select a team..."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:201
|
||||
msgid "Match data not available"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:440 src/format-shortcode.js:201
|
||||
msgid "Ja"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:440 src/format-shortcode.js:201
|
||||
msgid "Nein"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:619
|
||||
msgid "Team ID required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:625
|
||||
msgid "Standings data not available"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:679
|
||||
msgid "Parameter \"side\" must be \"home\" or \"away\""
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:691
|
||||
#: includes/class-swi-foot-shortcodes.php:812
|
||||
msgid "Match ID required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:703
|
||||
msgid "Roster data not available"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:743
|
||||
msgid "Team Roster"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:764
|
||||
msgid "No roster data available."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:822
|
||||
msgid "Events data not available"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:882
|
||||
msgid "Live match events"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:883
|
||||
msgid "Match Events"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:889
|
||||
msgid "Match minute"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-swi-foot-shortcodes.php:950
|
||||
msgid "No events recorded yet."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:59
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:60 src/editor-blocks.js:80
|
||||
msgid "Team is inherited from the container context."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:63
|
||||
msgid "Standings will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:79
|
||||
msgid "Schedule settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:81
|
||||
msgid "Limit"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:84
|
||||
msgid "Upcoming matches will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:99
|
||||
msgid "Swiss Football Team Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:126
|
||||
msgid "Team Selection"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:128
|
||||
msgid "1. Select Team"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:129
|
||||
msgid "Choose a team..."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
msgid "2. What to display"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
msgid "Choose..."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
msgid "Standings"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:133
|
||||
msgid "Match"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:138
|
||||
msgid "Team data shortcode generator"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:161
|
||||
msgid "Swiss Football Match Roster"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:168 src/editor-blocks.js:206
|
||||
msgid "Display Options"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:169 src/editor-blocks.js:207
|
||||
msgid "Match and team are inherited from the container context."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:171
|
||||
msgid "Team Side"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:173
|
||||
msgid "Home Team"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:173
|
||||
msgid "Away Team"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:177
|
||||
msgid "Include Bench Players"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:183
|
||||
msgid "Match roster will render on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:199
|
||||
msgid "Swiss Football Match Events"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:209
|
||||
msgid "Refresh Interval (seconds)"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:217
|
||||
msgid "Event Order"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:221
|
||||
msgid "Dynamic (Newest first while live, chronological after)"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:222
|
||||
msgid "Newest First"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:223
|
||||
msgid "Oldest First"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:225
|
||||
msgid ""
|
||||
"Dynamic: newest events at top while match is ongoing, chronological (oldest "
|
||||
"first) after match ends"
|
||||
msgstr ""
|
||||
|
||||
#: src/editor-blocks.js:229
|
||||
msgid "Live events will be displayed on the front-end."
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:31
|
||||
msgid "— Select data point…"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:32
|
||||
msgid "Match-Typ"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:33
|
||||
msgid "Liga"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:34
|
||||
msgid "Gruppe"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:35
|
||||
msgid "Runde"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:36
|
||||
msgid "Spielfeld"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:37
|
||||
msgid "Heimteam"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:38
|
||||
msgid "Gastteam"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:39
|
||||
msgid "Tore Heim"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:40
|
||||
msgid "Tore Gast"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:41
|
||||
msgid "Tore Halbzeit Heim"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:42
|
||||
msgid "Tore Halbzeit Gast"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:43
|
||||
msgid "Spieltag"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:44
|
||||
msgid "Spielzeit"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:45
|
||||
msgid "Spielstatus"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:46
|
||||
msgid "Spiel pausiert"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:47
|
||||
msgid "Spiel beendet"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:52
|
||||
msgid "— Select shortcode…"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:53
|
||||
msgid "Heimteam Logo"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:54
|
||||
msgid "Gastteam Logo"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:55
|
||||
msgid "Heimteam Logo (URL)"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:56
|
||||
msgid "Gastteam Logo (URL)"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:229 src/format-shortcode.js:242
|
||||
#: src/format-shortcode.js:356 src/format-shortcode.js:390
|
||||
msgid "Insert Match Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:234
|
||||
msgid "No match data available on this page. Please add match context first."
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:255
|
||||
msgid "Match Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:265
|
||||
msgid "Shortcodes"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:278
|
||||
msgid "Data Point"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:282
|
||||
msgid "Select which match data to display"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:289
|
||||
msgid "Preview:"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:297
|
||||
msgid "(no value)"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:307
|
||||
msgid "Insert Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:320
|
||||
msgid "Shortcode"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:324
|
||||
msgid "Select a shortcode to insert (logos, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:331
|
||||
msgid "Shortcode:"
|
||||
msgstr ""
|
||||
|
||||
#: src/format-shortcode.js:341
|
||||
msgid "Insert Shortcode"
|
||||
msgstr ""
|
||||
12
package.json
Normal file
12
package.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "swi-foot-matchdata-blocks",
|
||||
"version": "1.0.0",
|
||||
"description": "Build tooling for Swiss Football Matchdata Gutenberg blocks",
|
||||
"scripts": {
|
||||
"build": "wp-scripts build --output-path=assets/build",
|
||||
"start": "wp-scripts start"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wordpress/scripts": "^31.5.0"
|
||||
}
|
||||
}
|
||||
279
src/editor-blocks.js
Normal file
279
src/editor-blocks.js
Normal file
@ -0,0 +1,279 @@
|
||||
// Editor entry (ESNext) for Swiss Football blocks.
|
||||
// This registers blocks and the shortcode insertion toolbar button.
|
||||
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { InspectorControls, useBlockProps, RawHTML } from '@wordpress/block-editor';
|
||||
import { PanelBody, SelectControl, ToggleControl, RangeControl, Spinner } from '@wordpress/components';
|
||||
import { useState, useEffect, createElement } from '@wordpress/element';
|
||||
import { registerFormatType, applyFormat } from '@wordpress/rich-text';
|
||||
import './format-shortcode';
|
||||
|
||||
function makeAjaxCall(action, data = {}) {
|
||||
const root = swiFootEditorData.rest_url.replace(/\/$/, '');
|
||||
let url = root;
|
||||
const opts = { credentials: 'same-origin', headers: { 'X-WP-Nonce': swiFootEditorData.rest_nonce } };
|
||||
|
||||
if (action === 'swi_foot_get_teams_for_editor') {
|
||||
url += '/teams'; opts.method = 'GET';
|
||||
} else if (action === 'swi_foot_get_matches_for_team') {
|
||||
url += '/matches?team_id=' + encodeURIComponent(data.team_id || ''); opts.method = 'GET';
|
||||
} else if (action === 'swi_foot_get_commons_ids') {
|
||||
url += '/commons-ids'; opts.method = 'GET';
|
||||
} else {
|
||||
return Promise.reject({ error: 'Unknown action' });
|
||||
}
|
||||
|
||||
return fetch(url, opts)
|
||||
.then(r => { if (!r.ok) return r.json().then(j => Promise.reject(j)); return r.json(); })
|
||||
.then(data => {
|
||||
// Transform API response into expected format for editor blocks
|
||||
if (action === 'swi_foot_get_teams_for_editor') {
|
||||
return {
|
||||
success: true,
|
||||
data: Array.isArray(data) ? data.map(team => ({
|
||||
value: team.teamId,
|
||||
label: team.teamName
|
||||
})) : []
|
||||
};
|
||||
} else if (action === 'swi_foot_get_matches_for_team') {
|
||||
return {
|
||||
success: true,
|
||||
data: Array.isArray(data) ? data.map(match => ({
|
||||
value: match.matchId,
|
||||
label: `${match.teamNameA} vs ${match.teamNameB} (${match.matchDate})`
|
||||
})) : []
|
||||
};
|
||||
}
|
||||
return { success: true, data: data };
|
||||
});
|
||||
}
|
||||
|
||||
// Standings block (editor UI only, server renders)
|
||||
// Gets team from container context, no user selection needed
|
||||
registerBlockType('swi-foot/standings', {
|
||||
title: __('Swiss Football Standings', 'swi_foot_matchdata'),
|
||||
category: 'embed',
|
||||
supports: { align: true, anchor: true },
|
||||
edit: ({ attributes, setAttributes, isSelected }) => {
|
||||
const blockProps = useBlockProps({ style: isSelected ? {outline: '2px solid #0073aa'} : {} });
|
||||
return (
|
||||
<div {...blockProps}>
|
||||
<InspectorControls>
|
||||
<PanelBody title={__('Settings', 'swi_foot_matchdata')}>
|
||||
<p style={{color: '#666', fontSize: '13px'}}>{__('Team is inherited from the container context.', 'swi_foot_matchdata')}</p>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
<div className="swi-foot-editor-placeholder" style={{padding: '24px', cursor: 'pointer', background: '#f5f5f5', border: '2px solid #ddd', borderRadius: '4px'}}>{__('Standings will render on the front-end.', 'swi_foot_matchdata')}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
save: () => null
|
||||
});
|
||||
|
||||
// Schedule block (editor UI only, server renders)
|
||||
// Gets team from container context, only allows limiting results
|
||||
registerBlockType('swi-foot/schedule', {
|
||||
title: __('Swiss Football Schedule', 'swi_foot_matchdata'),
|
||||
category: 'embed',
|
||||
supports: { align: true, anchor: true },
|
||||
attributes: {
|
||||
limit: { type: 'number', default: 10 }
|
||||
},
|
||||
edit: ({ attributes, setAttributes, isSelected }) => {
|
||||
const blockProps = useBlockProps({ style: isSelected ? {outline: '2px solid #0073aa'} : {} });
|
||||
const { limit } = attributes;
|
||||
|
||||
return (
|
||||
<div {...blockProps}>
|
||||
<InspectorControls>
|
||||
<PanelBody title={__('Schedule settings', 'swi_foot_matchdata')}>
|
||||
<p style={{color: '#666', fontSize: '13px', marginBottom: '12px'}}>{__('Team is inherited from the container context.', 'swi_foot_matchdata')}</p>
|
||||
<RangeControl label={__('Limit', 'swi_foot_matchdata')} value={limit || 10} onChange={val => setAttributes({ limit: val })} min={1} max={20} />
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
<div className="swi-foot-editor-placeholder" style={{padding: '24px', cursor: 'pointer', background: '#f5f5f5', border: '2px solid #ddd', borderRadius: '4px'}}>{__('Upcoming matches will render on the front-end.', 'swi_foot_matchdata')}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
save: ({ attributes }) => null
|
||||
});
|
||||
|
||||
/**
|
||||
* NOTE: The shortcode-inserter block has been deprecated in favor of the
|
||||
* paragraph toolbar button (SWI Inline Format) which provides a better UX.
|
||||
* Users should use the toolbar button or the team-data block below.
|
||||
*/
|
||||
|
||||
// Team Data composite block (generates shortcodes)
|
||||
registerBlockType('swi-foot/team-data', {
|
||||
title: __('Swiss Football Team Data', 'swi_foot_matchdata'),
|
||||
category: 'embed',
|
||||
supports: { align: true, anchor: true },
|
||||
attributes: {
|
||||
selectedTeam: { type: 'string', default: '' },
|
||||
dataType: { type: 'string', default: '' },
|
||||
selectedMatch: { type: 'string', default: '' },
|
||||
shortcodeType: { type: 'string', default: 'standings' },
|
||||
format: { type: 'string', default: '' },
|
||||
separator: { type: 'string', default: ':' }
|
||||
},
|
||||
edit: ({ attributes, setAttributes, isSelected }) => {
|
||||
const blockProps = useBlockProps({ style: isSelected ? {outline: '2px solid #0073aa'} : {} });
|
||||
const { selectedTeam, dataType, selectedMatch, shortcodeType, format, separator } = attributes;
|
||||
const [teams, setTeams] = useState([]);
|
||||
const [matches, setMatches] = useState([]);
|
||||
const [loadingTeams, setLoadingTeams] = useState(true);
|
||||
const [loadingMatches, setLoadingMatches] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
makeAjaxCall('swi_foot_get_teams_for_editor').then(r => { if (r.success) setTeams(r.data); setLoadingTeams(false); }).catch(()=>setLoadingTeams(false));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTeam && dataType === 'match') {
|
||||
setLoadingMatches(true);
|
||||
makeAjaxCall('swi_foot_get_matches_for_team', { team_id: selectedTeam })
|
||||
.then(r => { if (r.success) setMatches(r.data); setLoadingMatches(false); })
|
||||
.catch(()=>setLoadingMatches(false));
|
||||
} else {
|
||||
setMatches([]);
|
||||
}
|
||||
}, [selectedTeam, dataType]);
|
||||
|
||||
// preview/controls omitted for brevity — editor-blocks.js contains full UI
|
||||
return (
|
||||
<div {...blockProps}>
|
||||
<InspectorControls>
|
||||
<PanelBody title={__('Team Selection', 'swi_foot_matchdata')}>
|
||||
{loadingTeams ? <Spinner /> : (
|
||||
<SelectControl label={__('1. Select Team', 'swi_foot_matchdata')} value={selectedTeam}
|
||||
options={[{ value: '', label: __('Choose a team...', 'swi_foot_matchdata') }].concat(teams.map(t=>({value:t.value,label:t.label})))}
|
||||
onChange={val=>setAttributes({selectedTeam:val, dataType:'', selectedMatch:''})} />)}
|
||||
|
||||
{selectedTeam && (
|
||||
<SelectControl label={__('2. What to display', 'swi_foot_matchdata')} value={dataType} options={[{value:'',label:__('Choose...', 'swi_foot_matchdata')},{value:'stats',label:__('Standings','swi_foot_matchdata')},{value:'match',label:__('Match','swi_foot_matchdata')}]}
|
||||
onChange={val=>setAttributes({dataType:val, selectedMatch:''})} />
|
||||
)}
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
<div className="swi-foot-editor-placeholder" style={{padding: '24px', cursor: 'pointer', background: '#f5f5f5', border: '2px solid #ddd', borderRadius: '4px'}}>{__('Team data shortcode generator', 'swi_foot_matchdata')}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
save: ({ attributes }) => {
|
||||
const { selectedTeam, dataType, selectedMatch, shortcodeType, format, separator } = attributes;
|
||||
if (selectedTeam && dataType === 'stats') return createElement(RawHTML, null, `[swi_foot_standings team_id="${selectedTeam}"]`);
|
||||
if (selectedTeam && dataType === 'match' && selectedMatch) {
|
||||
let s = '';
|
||||
if (selectedMatch === 'current') s = `[swi_foot_${shortcodeType} team_id="${selectedTeam}" show_current="true"`;
|
||||
else s = `[swi_foot_${shortcodeType} match_id="${selectedMatch}"`;
|
||||
if (format && (shortcodeType==='match_date' || shortcodeType==='match_time')) s += ` format="${format}"`;
|
||||
if (separator!==':' && shortcodeType==='match_score') s += ` separator="${separator}"`;
|
||||
s += ']';
|
||||
return createElement(RawHTML, null, s);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
// Match Roster
|
||||
// Gets match and team from container context, only allows selecting which side (home/away) and bench option
|
||||
registerBlockType('swi-foot/match-roster', {
|
||||
title: __('Swiss Football Match Roster', 'swi_foot_matchdata'),
|
||||
category: 'embed',
|
||||
supports: { align: true, anchor: true },
|
||||
attributes: {
|
||||
side: { type: 'string', default: 'home' },
|
||||
withBench: { type: 'boolean', default: false }
|
||||
},
|
||||
edit: ({ attributes, setAttributes, isSelected }) => {
|
||||
const blockProps = useBlockProps({ style: isSelected ? {outline: '2px solid #0073aa'} : {} });
|
||||
const { side, withBench } = attributes;
|
||||
|
||||
return (
|
||||
<div {...blockProps}>
|
||||
<InspectorControls>
|
||||
<PanelBody title={__('Display Options', 'swi_foot_matchdata')}>
|
||||
<p style={{color: '#666', fontSize: '13px', marginBottom: '12px'}}>{__('Match and team are inherited from the container context.', 'swi_foot_matchdata')}</p>
|
||||
<SelectControl
|
||||
label={__('Team Side', 'swi_foot_matchdata')}
|
||||
value={side}
|
||||
options={[{value:'home',label:__('Home Team', 'swi_foot_matchdata')},{value:'away',label:__('Away Team', 'swi_foot_matchdata')}]}
|
||||
onChange={v=>setAttributes({side:v})}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={__('Include Bench Players', 'swi_foot_matchdata')}
|
||||
checked={withBench}
|
||||
onChange={v=>setAttributes({withBench:v})}
|
||||
/>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
<div className="swi-foot-editor-placeholder" style={{padding: '24px', cursor: 'pointer', background: '#f5f5f5', border: '2px solid #ddd', borderRadius: '4px'}}>{__('Match roster will render on the front-end.', 'swi_foot_matchdata')}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
save: ({ attributes }) => {
|
||||
const { side, withBench } = attributes;
|
||||
let shortcode = '[swi_foot_roster side="' + side + '"';
|
||||
if (withBench) shortcode += ' with_bench="true"';
|
||||
shortcode += ']';
|
||||
return createElement(RawHTML, null, shortcode);
|
||||
}
|
||||
});
|
||||
|
||||
// Match Events
|
||||
// Gets match and team from container context, only allows setting refresh interval
|
||||
registerBlockType('swi-foot/match-events', {
|
||||
title: __('Swiss Football Match Events', 'swi_foot_matchdata'),
|
||||
category: 'embed',
|
||||
supports: { align: true, anchor: true },
|
||||
attributes: {
|
||||
refreshInterval: { type: 'number', default: 30 },
|
||||
eventOrder: { type: 'string', default: 'dynamic' }
|
||||
},
|
||||
edit: ({ attributes, setAttributes, isSelected }) => {
|
||||
const blockProps = useBlockProps({ style: isSelected ? {outline: '2px solid #0073aa'} : {} });
|
||||
const { refreshInterval, eventOrder } = attributes;
|
||||
|
||||
return (
|
||||
<div {...blockProps}>
|
||||
<InspectorControls>
|
||||
<PanelBody title={__('Display Options', 'swi_foot_matchdata')}>
|
||||
<p style={{color: '#666', fontSize: '13px', marginBottom: '12px'}}>{__('Match and team are inherited from the container context.', 'swi_foot_matchdata')}</p>
|
||||
<RangeControl
|
||||
label={__('Refresh Interval (seconds)', 'swi_foot_matchdata')}
|
||||
value={refreshInterval || 30}
|
||||
onChange={v=>setAttributes({refreshInterval:v})}
|
||||
min={10}
|
||||
max={300}
|
||||
step={10}
|
||||
/>
|
||||
<SelectControl
|
||||
label={__('Event Order', 'swi_foot_matchdata')}
|
||||
value={eventOrder || 'dynamic'}
|
||||
onChange={v=>setAttributes({eventOrder:v})}
|
||||
options={[
|
||||
{ label: __('Dynamic (Newest first while live, chronological after)', 'swi_foot_matchdata'), value: 'dynamic' },
|
||||
{ label: __('Newest First', 'swi_foot_matchdata'), value: 'newest_first' },
|
||||
{ label: __('Oldest First', 'swi_foot_matchdata'), value: 'oldest_first' }
|
||||
]}
|
||||
help={__('Dynamic: newest events at top while match is ongoing, chronological (oldest first) after match ends', 'swi_foot_matchdata')}
|
||||
/>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
<div className="swi-foot-editor-placeholder" style={{padding: '24px', cursor: 'pointer', background: '#f5f5f5', border: '2px solid #ddd', borderRadius: '4px'}}>{__('Live events will be displayed on the front-end.', 'swi_foot_matchdata')}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
save: ({ attributes }) => {
|
||||
const { refreshInterval, eventOrder } = attributes;
|
||||
let shortcode = '[swi_foot_events refresh_interval="' + (refreshInterval || 30) + '"';
|
||||
if (eventOrder && eventOrder !== 'dynamic') {
|
||||
shortcode += ' event_order="' + eventOrder + '"';
|
||||
}
|
||||
shortcode += ']';
|
||||
return createElement(RawHTML, null, shortcode);
|
||||
}
|
||||
});
|
||||
|
||||
405
src/format-shortcode.js
Normal file
405
src/format-shortcode.js
Normal file
@ -0,0 +1,405 @@
|
||||
/**
|
||||
* SWI Football Shortcode Insertion
|
||||
*
|
||||
* Adds a toolbar button to paragraph blocks for inserting shortcodes directly as text.
|
||||
* Properly handles cursor position and text selection replacement.
|
||||
*/
|
||||
|
||||
import { registerFormatType, insert } from '@wordpress/rich-text';
|
||||
import { RichTextToolbarButton } from '@wordpress/block-editor';
|
||||
import { Button, Modal, SelectControl, PanelRow, Spinner } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { useState, useEffect, useCallback } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Soccer ball SVG icon component
|
||||
*/
|
||||
const SoccerBallIcon = () => (
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" aria-hidden="true" focusable="false">
|
||||
<g transform="scale(0.02583)">
|
||||
<g>
|
||||
<path d="M361.2,578.801c4,12.398,15.5,20.699,28.5,20.699h149c13,0,24.5-8.4,28.5-20.699L613.3,437.1c4-12.4-0.4-25.9-10.9-33.5l-120.5-87.7c-10.5-7.6-24.8-7.6-35.3,0L326,403.5c-10.5,7.601-14.9,21.2-10.9,33.5L361.2,578.801z"></path>
|
||||
<path d="M136,792.6c42.6,42.6,92.3,76.1,147.6,99.5c57.3,24.201,118.101,36.5,180.7,36.5s123.5-12.299,180.7-36.5c55.3-23.4,104.899-56.9,147.6-99.5C835.2,750,868.7,700.301,892.1,645c24.2-57.301,36.5-118.1,36.5-180.699c0-62.701-12.3-123.501-36.5-180.701C868.7,228.3,835.2,178.7,792.6,136C750,93.4,700.3,59.9,645,36.5C587.8,12.3,527,0,464.3,0S340.8,12.3,283.6,36.5C228.3,59.9,178.6,93.4,136,136c-42.6,42.6-76.1,92.3-99.5,147.6C12.3,340.8,0,401.7,0,464.301C0,527,12.3,587.801,36.5,645C59.9,700.4,93.4,750,136,792.6z M189,189c40.6-40.6,88.6-71.1,141-90.3l-10.5,22.7c-6.9,14.7-0.7,32.3,13.8,39.5l116.5,58c8.5,4.2,18.5,4.2,26.9-0.1l116-58.9c14.6-7.4,20.6-25,13.6-39.7l-10.7-22.6c53.5,19.1,102.601,50,144,91.4c7.9,7.9,15.4,16,22.5,24.4l-24.7-2.2c-16.199-1.4-30.6,10.3-32.399,26.5L690.2,367c-1.101,9.4,2.399,18.801,9.3,25.301l94.899,89c11.9,11.1,30.4,10.799,41.9-0.801l17.4-17.6c0,0.4,0,0.9,0,1.299c0,76.102-21.7,149-62.2,211.4l-5.2-24.4c-3.4-15.898-18.8-26.199-34.8-23.199l-128,23.699C614.2,653.4,606.2,659.5,602,668l-57.3,116.9c-7.2,14.6-1.4,32.199,13,39.799l22.1,11.602c-36.9,11.398-75.8,17.299-115.5,17.299s-78.6-5.9-115.5-17.299l22.1-11.602c14.4-7.5,20.2-25.199,13-39.799L326.6,668.1c-4.2-8.5-12.1-14.6-21.5-16.299l-128-23.701c-16-3-31.4,7.301-34.8,23.201l-5.2,24.398C96.6,613.301,74.9,540.5,74.9,464.301c0-0.401,0-0.901,0-1.301l17.4,17.6c11.4,11.6,30,11.9,41.9,0.801l94.9-89c6.9,6.5,10.4-15.9,9.3-25.3l-14.8-129.3c-1.8-16.2-16.2-27.9-32.4-26.5l-24.7,2.2C173.6,205.1,181.1,196.9,189,189z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Inline data point options for displaying individual match data
|
||||
const INLINE_DATA_POINTS = [
|
||||
{ value: '', label: __( '— Select data point…', 'swi_foot_matchdata' ) },
|
||||
{ value: 'matchTypeName', label: __( 'Match-Typ', 'swi_foot_matchdata' ) },
|
||||
{ value: 'leagueName', label: __( 'Liga', 'swi_foot_matchdata' ) },
|
||||
{ value: 'divisionName', label: __( 'Gruppe', 'swi_foot_matchdata' ) },
|
||||
{ value: 'roundNbr', label: __( 'Runde', 'swi_foot_matchdata' ) },
|
||||
{ value: 'stadiumFieldName', label: __( 'Spielfeld', 'swi_foot_matchdata' ) },
|
||||
{ value: 'teamNameA', label: __( 'Heimteam', 'swi_foot_matchdata' ) },
|
||||
{ value: 'teamNameB', label: __( 'Gastteam', 'swi_foot_matchdata' ) },
|
||||
{ value: 'scoreTeamA', label: __( 'Tore Heim', 'swi_foot_matchdata' ) },
|
||||
{ value: 'scoreTeamB', label: __( 'Tore Gast', 'swi_foot_matchdata' ) },
|
||||
{ value: 'intermediateResults/scoreTeamA', label: __( 'Tore Halbzeit Heim', 'swi_foot_matchdata' ) },
|
||||
{ value: 'intermediateResults/scoreTeamB', label: __( 'Tore Halbzeit Gast', 'swi_foot_matchdata' ) },
|
||||
{ value: 'matchDate_date', label: __( 'Spieltag', 'swi_foot_matchdata' ) },
|
||||
{ value: 'matchDate_time', label: __( 'Spielzeit', 'swi_foot_matchdata' ) },
|
||||
{ value: 'hasMatchStarted', label: __( 'Spielstatus', 'swi_foot_matchdata' ) },
|
||||
{ value: 'isMatchPause', label: __( 'Spiel pausiert', 'swi_foot_matchdata' ) },
|
||||
{ value: 'hasMatchEnded', label: __( 'Spiel beendet', 'swi_foot_matchdata' ) },
|
||||
];
|
||||
|
||||
// Shortcode options for inserting team logos and other shortcodes
|
||||
const SHORTCODE_OPTIONS = [
|
||||
{ value: '', label: __( '— Select shortcode…', 'swi_foot_matchdata' ) },
|
||||
{ value: 'swi_foot_match_home_team_logo', label: __( 'Heimteam Logo', 'swi_foot_matchdata' ) },
|
||||
{ value: 'swi_foot_match_away_team_logo', label: __( 'Gastteam Logo', 'swi_foot_matchdata' ) },
|
||||
{ value: 'swi_foot_match_home_team_logo_url', label: __( 'Heimteam Logo (URL)', 'swi_foot_matchdata' ) },
|
||||
{ value: 'swi_foot_match_away_team_logo_url', label: __( 'Gastteam Logo (URL)', 'swi_foot_matchdata' ) },
|
||||
];
|
||||
|
||||
/**
|
||||
* Fetch a specific match by ID from REST API
|
||||
*
|
||||
* @param {string} matchId - The numeric match ID to fetch
|
||||
* @returns {Promise<Object|null>} Match data object or null if request fails
|
||||
*/
|
||||
async function fetchMatch( matchId ) {
|
||||
try {
|
||||
const url = `${ wpApiSettings.root }swi-foot/v1/match/${ encodeURIComponent( matchId ) }`;
|
||||
const response = await fetch( url, {
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-WP-Nonce': wpApiSettings.nonce },
|
||||
} );
|
||||
if ( ! response.ok ) return null;
|
||||
return await response.json();
|
||||
} catch ( e ) {
|
||||
console.warn( 'swi-foot: fetchMatch error', e );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract nested property values using slash-delimited path notation
|
||||
*
|
||||
* @param {Object} obj - The object to extract from
|
||||
* @param {string} path - Path using slash notation (e.g., 'intermediateResults/scoreTeamA')
|
||||
* @returns {*|null} The value at the path, or null if not found
|
||||
*/
|
||||
function getNestedValue( obj, path ) {
|
||||
if ( ! obj || ! path ) return null;
|
||||
const keys = path.split( '/' );
|
||||
let current = obj;
|
||||
for ( const key of keys ) {
|
||||
if ( ! current || typeof current !== 'object' ) return null;
|
||||
current = current[ key ];
|
||||
if ( current === undefined ) return null;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate inline data shortcode for match data insertion
|
||||
*
|
||||
* Format: [swi_foot_match_data data_point="fieldName"]
|
||||
* Match ID is automatically retrieved from container context, not embedded in shortcode
|
||||
* Supports nested fields via slash notation (e.g., intermediateResults/scoreTeamA)
|
||||
*
|
||||
* @param {string} dataPoint - The field name or path to extract
|
||||
* @returns {string} Shortcode string, or empty string if input invalid
|
||||
*/
|
||||
function generateDataShortcode( dataPoint ) {
|
||||
if ( ! dataPoint ) {
|
||||
return '';
|
||||
}
|
||||
return `[swi_foot_match_data data_point="${ dataPoint }"]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate shortcode string for regular shortcodes (logos, etc.)
|
||||
*
|
||||
* Format: [shortcode_name]
|
||||
*
|
||||
* @param {string} shortcodeValue - The shortcode name/identifier
|
||||
* @returns {string} Shortcode string, or empty string if input invalid
|
||||
*/
|
||||
function generateShortcode( shortcodeValue ) {
|
||||
if ( ! shortcodeValue ) {
|
||||
return '';
|
||||
}
|
||||
return `[${ shortcodeValue }]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modal component for selecting and previewing match data or other shortcodes to insert
|
||||
*
|
||||
* Allows users to:
|
||||
* - Choose between inserting match data or shortcodes (logos, etc.)
|
||||
* - Select a data point or shortcode from available options
|
||||
* - Preview the actual value for data points
|
||||
* - Generate and insert the shortcode with one click
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {Function} props.onInsert - Callback when shortcode is inserted (receives shortcode string)
|
||||
* @param {Function} props.onClose - Callback to close the modal
|
||||
* @param {string|null} props.matchId - The match ID (from page context), or null if unavailable
|
||||
* @returns {React.ReactElement} Modal or empty state message
|
||||
*/
|
||||
function InlineDataModal( { onInsert, onClose, matchId } ) {
|
||||
const [ tabMode, setTabMode ] = useState( 'data-points' );
|
||||
const [ selectedDataPoint, setSelectedDataPoint ] = useState( '' );
|
||||
const [ selectedShortcode, setSelectedShortcode ] = useState( '' );
|
||||
const [ matchData, setMatchData ] = useState( null );
|
||||
const [ loading, setLoading ] = useState( true );
|
||||
const [ actualValue, setActualValue ] = useState( '' );
|
||||
|
||||
// Fetch match data on mount
|
||||
useEffect( () => {
|
||||
if ( matchId ) {
|
||||
fetchMatch( matchId ).then( data => {
|
||||
setMatchData( data );
|
||||
setLoading( false );
|
||||
} );
|
||||
} else {
|
||||
setLoading( false );
|
||||
}
|
||||
}, [ matchId ] );
|
||||
|
||||
// Update preview value when data point selection changes
|
||||
useEffect( () => {
|
||||
if ( selectedDataPoint && matchData ) {
|
||||
let value;
|
||||
|
||||
// Handle special date/time formatting
|
||||
if ( selectedDataPoint === 'matchDate_date' ) {
|
||||
const matchDate = matchData.matchDate;
|
||||
if ( matchDate ) {
|
||||
try {
|
||||
const date = new Date( matchDate );
|
||||
// Show date preview in browser locale
|
||||
value = date.toLocaleDateString();
|
||||
} catch ( e ) {
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
} else if ( selectedDataPoint === 'matchDate_time' ) {
|
||||
const matchDate = matchData.matchDate;
|
||||
if ( matchDate ) {
|
||||
try {
|
||||
const date = new Date( matchDate );
|
||||
// Show time preview in browser locale
|
||||
value = date.toLocaleTimeString( undefined, { hour: '2-digit', minute: '2-digit' } );
|
||||
} catch ( e ) {
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value = getNestedValue( matchData, selectedDataPoint );
|
||||
}
|
||||
|
||||
// Convert boolean to readable text
|
||||
if ( typeof value === 'boolean' ) {
|
||||
setActualValue( value ? __( 'Ja', 'swi_foot_matchdata' ) : __( 'Nein', 'swi_foot_matchdata' ) );
|
||||
} else if ( value === null || value === undefined ) {
|
||||
setActualValue( '' );
|
||||
} else {
|
||||
setActualValue( String( value ) );
|
||||
}
|
||||
} else {
|
||||
setActualValue( '' );
|
||||
}
|
||||
}, [ selectedDataPoint, matchData ] );
|
||||
|
||||
const handleInsertData = useCallback( () => {
|
||||
const shortcode = generateDataShortcode( selectedDataPoint );
|
||||
if ( shortcode ) {
|
||||
onInsert( shortcode );
|
||||
}
|
||||
}, [ selectedDataPoint, onInsert ] );
|
||||
|
||||
const handleInsertShortcode = useCallback( () => {
|
||||
const shortcode = generateShortcode( selectedShortcode );
|
||||
if ( shortcode ) {
|
||||
onInsert( shortcode );
|
||||
}
|
||||
}, [ selectedShortcode, onInsert ] );
|
||||
|
||||
if ( ! matchId ) {
|
||||
return (
|
||||
<Modal
|
||||
title={ __( 'Insert Match Data', 'swi_foot_matchdata' ) }
|
||||
onRequestClose={ onClose }
|
||||
className="swi-foot-inline-data-modal"
|
||||
>
|
||||
<div style={ { padding: '16px', color: '#666' } }>
|
||||
{ __( 'No match data available on this page. Please add match context first.', 'swi_foot_matchdata' ) }
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={ __( 'Insert Match Data', 'swi_foot_matchdata' ) }
|
||||
onRequestClose={ onClose }
|
||||
className="swi-foot-inline-data-modal"
|
||||
>
|
||||
<div style={ { marginBottom: '16px', display: 'flex', gap: '8px' } }>
|
||||
<Button
|
||||
variant={ tabMode === 'data-points' ? 'primary' : 'secondary' }
|
||||
size="small"
|
||||
onClick={ () => {
|
||||
setTabMode( 'data-points' );
|
||||
setSelectedShortcode( '' );
|
||||
} }
|
||||
>
|
||||
{ __( 'Match Data', 'swi_foot_matchdata' ) }
|
||||
</Button>
|
||||
<Button
|
||||
variant={ tabMode === 'shortcodes' ? 'primary' : 'secondary' }
|
||||
size="small"
|
||||
onClick={ () => {
|
||||
setTabMode( 'shortcodes' );
|
||||
setSelectedDataPoint( '' );
|
||||
} }
|
||||
>
|
||||
{ __( 'Shortcodes', 'swi_foot_matchdata' ) }
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{ tabMode === 'data-points' ? (
|
||||
<form
|
||||
onSubmit={ e => {
|
||||
e.preventDefault();
|
||||
handleInsertData();
|
||||
} }
|
||||
>
|
||||
<PanelRow>
|
||||
<SelectControl
|
||||
label={ __( 'Data Point', 'swi_foot_matchdata' ) }
|
||||
value={ selectedDataPoint }
|
||||
options={ INLINE_DATA_POINTS }
|
||||
onChange={ setSelectedDataPoint }
|
||||
help={ __( 'Select which match data to display', 'swi_foot_matchdata' ) }
|
||||
/>
|
||||
</PanelRow>
|
||||
|
||||
{ selectedDataPoint && (
|
||||
<PanelRow style={ { marginTop: '16px', padding: '12px', backgroundColor: '#f5f5f5', borderRadius: '4px' } }>
|
||||
<div>
|
||||
<strong>{ __( 'Preview:', 'swi_foot_matchdata' ) }</strong>
|
||||
{ loading ? (
|
||||
<div style={ { marginTop: '8px' } }>
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div style={ { marginTop: '8px', padding: '8px', backgroundColor: '#fff', borderRadius: '2px', fontFamily: 'monospace', fontSize: '14px', fontWeight: 'bold' } }>
|
||||
{ actualValue || __( '(no value)', 'swi_foot_matchdata' ) }
|
||||
</div>
|
||||
</>
|
||||
) }
|
||||
</div>
|
||||
</PanelRow>
|
||||
) }
|
||||
|
||||
<PanelRow style={ { marginTop: '16px' } }>
|
||||
<Button variant="primary" type="submit" disabled={ ! selectedDataPoint || loading }>
|
||||
{ __( 'Insert Data', 'swi_foot_matchdata' ) }
|
||||
</Button>
|
||||
</PanelRow>
|
||||
</form>
|
||||
) : (
|
||||
<form
|
||||
onSubmit={ e => {
|
||||
e.preventDefault();
|
||||
handleInsertShortcode();
|
||||
} }
|
||||
>
|
||||
<PanelRow>
|
||||
<SelectControl
|
||||
label={ __( 'Shortcode', 'swi_foot_matchdata' ) }
|
||||
value={ selectedShortcode }
|
||||
options={ SHORTCODE_OPTIONS }
|
||||
onChange={ setSelectedShortcode }
|
||||
help={ __( 'Select a shortcode to insert (logos, etc.)', 'swi_foot_matchdata' ) }
|
||||
/>
|
||||
</PanelRow>
|
||||
|
||||
{ selectedShortcode && (
|
||||
<PanelRow style={ { marginTop: '16px', padding: '12px', backgroundColor: '#f5f5f5', borderRadius: '4px' } }>
|
||||
<div>
|
||||
<strong>{ __( 'Shortcode:', 'swi_foot_matchdata' ) }</strong>
|
||||
<div style={ { marginTop: '8px', padding: '8px', backgroundColor: '#fff', borderRadius: '2px', fontFamily: 'monospace', fontSize: '14px', fontWeight: 'bold' } }>
|
||||
{ generateShortcode( selectedShortcode ) }
|
||||
</div>
|
||||
</div>
|
||||
</PanelRow>
|
||||
) }
|
||||
|
||||
<PanelRow style={ { marginTop: '16px' } }>
|
||||
<Button variant="primary" type="submit" disabled={ ! selectedShortcode }>
|
||||
{ __( 'Insert Shortcode', 'swi_foot_matchdata' ) }
|
||||
</Button>
|
||||
</PanelRow>
|
||||
</form>
|
||||
) }
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register format type with toolbar button for inline data insertion
|
||||
* Shows a modal for selecting data point to insert
|
||||
* Match is automatically taken from page/container context
|
||||
*/
|
||||
registerFormatType( 'swi-foot/shortcode-inserter', {
|
||||
title: __( 'Insert Match Data', 'swi_foot_matchdata' ),
|
||||
tagName: 'span',
|
||||
className: 'swi-foot-shortcode-inserter',
|
||||
edit: function FormatEdit( { value, onChange, isActive } ) {
|
||||
const [ isModalOpen, setIsModalOpen ] = useState( false );
|
||||
|
||||
// Get post context for match data
|
||||
const postContext = useSelect( select => {
|
||||
const meta = select( 'core/editor' )?.getEditedPostAttribute?.( 'meta' ) || {};
|
||||
let context = meta.swi_foot_context || {};
|
||||
|
||||
// Handle case where context might be stringified
|
||||
if ( typeof context === 'string' ) {
|
||||
try {
|
||||
context = JSON.parse( context );
|
||||
} catch ( e ) {
|
||||
context = {};
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
} );
|
||||
|
||||
const handleInsertShortcode = useCallback( ( shortcode ) => {
|
||||
// Insert the shortcode text directly at cursor position
|
||||
const newValue = insert( value, shortcode );
|
||||
onChange( newValue );
|
||||
setIsModalOpen( false );
|
||||
}, [ value, onChange ] );
|
||||
|
||||
return (
|
||||
<>
|
||||
<RichTextToolbarButton
|
||||
icon={ <SoccerBallIcon /> }
|
||||
title={ __( 'Insert Match Data', 'swi_foot_matchdata' ) }
|
||||
onClick={ () => setIsModalOpen( true ) }
|
||||
isActive={ isActive }
|
||||
className="swi-foot-shortcode-btn"
|
||||
/>
|
||||
{ isModalOpen && (
|
||||
<InlineDataModal
|
||||
onInsert={ handleInsertShortcode }
|
||||
onClose={ () => setIsModalOpen( false ) }
|
||||
matchId={ postContext?.match_id || postContext?.matchId }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
},
|
||||
} );
|
||||
2
src/index.js
Normal file
2
src/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
// Entry point for wp-scripts build
|
||||
import './editor-blocks';
|
||||
107
swiss-football-matchdata.php
Normal file
107
swiss-football-matchdata.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Swiss Football Matchdata
|
||||
* Description: WordPress plugin to consume Swiss Football Association API for team standings, schedules, and match details with shortcode support
|
||||
* Version: 1.0.0
|
||||
* Author: David Reindl (assisted by Claude AI)
|
||||
* Text Domain: swi_foot_matchdata
|
||||
* Domain Path: /languages
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Define plugin constants
|
||||
define('SWI_FOOT_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||
define('SWI_FOOT_PLUGIN_PATH', plugin_dir_path(__FILE__));
|
||||
define('SWI_FOOT_PLUGIN_VERSION', '1.0.0');
|
||||
|
||||
// Include required files
|
||||
require_once SWI_FOOT_PLUGIN_PATH . 'includes/class-swi-foot-api.php';
|
||||
require_once SWI_FOOT_PLUGIN_PATH . 'includes/class-swi-foot-admin.php';
|
||||
require_once SWI_FOOT_PLUGIN_PATH . 'includes/class-swi-foot-blocks.php';
|
||||
require_once SWI_FOOT_PLUGIN_PATH . 'includes/class-swi-foot-shortcodes.php';
|
||||
require_once SWI_FOOT_PLUGIN_PATH . 'includes/class-swi-foot-rest.php';
|
||||
|
||||
class Swiss_Football_Matchdata {
|
||||
|
||||
public function __construct() {
|
||||
add_action('init', array($this, 'init'));
|
||||
register_activation_hook(__FILE__, array($this, 'activate'));
|
||||
register_deactivation_hook(__FILE__, array($this, 'deactivate'));
|
||||
add_action('plugins_loaded', array($this, 'load_textdomain'));
|
||||
}
|
||||
|
||||
public function init() {
|
||||
// Register post meta to store per-post context (season/team/match) for posts and pages
|
||||
$meta_args = array(
|
||||
'type' => 'object',
|
||||
'single' => true,
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'season' => array('type' => array('string','integer')),
|
||||
'team_id' => array('type' => array('string','integer')),
|
||||
'match_id' => array('type' => array('string','integer')),
|
||||
),
|
||||
'additionalProperties' => true,
|
||||
),
|
||||
),
|
||||
'auth_callback' => function( $allowed, $meta_key, $post_id, $user ) {
|
||||
return current_user_can('edit_post', $post_id);
|
||||
}
|
||||
);
|
||||
|
||||
register_post_meta('post', 'swi_foot_context', $meta_args);
|
||||
register_post_meta('page', 'swi_foot_context', $meta_args);
|
||||
|
||||
new Swi_Foot_API();
|
||||
new Swi_Foot_Admin();
|
||||
new Swi_Foot_Blocks();
|
||||
new Swi_Foot_Shortcodes();
|
||||
}
|
||||
|
||||
public function load_textdomain() {
|
||||
load_plugin_textdomain(
|
||||
'swi_foot_matchdata',
|
||||
false,
|
||||
dirname(plugin_basename(__FILE__)) . '/languages'
|
||||
);
|
||||
}
|
||||
|
||||
public function activate() {
|
||||
// Create options with defaults
|
||||
add_option('swi_foot_api_base_url', 'https://club-api-services.football.ch/api/v1');
|
||||
add_option('swi_foot_api_username', '');
|
||||
add_option('swi_foot_api_password', '');
|
||||
add_option('swi_foot_verein_id', '');
|
||||
add_option('swi_foot_season_id', date('Y')); // Current year as default
|
||||
add_option('swi_foot_match_cache_duration', 30);
|
||||
|
||||
// Flush rewrite rules
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
|
||||
public function deactivate() {
|
||||
// Clean up transient cache data (do not remove finished/permanent records)
|
||||
delete_transient('swi_foot_access_token');
|
||||
delete_transient('swi_foot_teams');
|
||||
$keys = get_transient('swi_foot_match_keys');
|
||||
if (is_array($keys)) {
|
||||
foreach ($keys as $mid) {
|
||||
delete_transient('swi_foot_match_' . $mid);
|
||||
}
|
||||
}
|
||||
delete_transient('swi_foot_match_keys');
|
||||
|
||||
// Flush rewrite rules
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the plugin
|
||||
new Swiss_Football_Matchdata();
|
||||
?>
|
||||
85
test/register-check.js
Normal file
85
test/register-check.js
Normal file
@ -0,0 +1,85 @@
|
||||
// Simple Node-based headless-like test for built bundle
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// capture registrations
|
||||
const blockRegs = [];
|
||||
const formatRegs = [];
|
||||
const regsByName = {};
|
||||
const formatsByName = {};
|
||||
|
||||
// Minimal fake implementations expected by the bundle
|
||||
global.window = {
|
||||
prompt: function (msg) {
|
||||
return null;
|
||||
},
|
||||
wp: {
|
||||
blocks: {
|
||||
registerBlockType: function (name, settings) {
|
||||
blockRegs.push(name);
|
||||
regsByName[name] = settings || {};
|
||||
},
|
||||
getBlockType: function (name) { return null; }
|
||||
},
|
||||
element: {
|
||||
createElement: function (tag, props) {
|
||||
// return a serializable placeholder
|
||||
const children = Array.prototype.slice.call(arguments, 2);
|
||||
return { tag: tag, props: props || null, children: children };
|
||||
},
|
||||
useState: function () { return [null, function () {}]; },
|
||||
useEffect: function () {}
|
||||
},
|
||||
blockEditor: {
|
||||
InnerBlocks: function () {},
|
||||
InspectorControls: function () {},
|
||||
RichText: { applyFormat: null, toggleFormat: null },
|
||||
BlockControls: function () {},
|
||||
ToolbarButton: function () {}
|
||||
},
|
||||
components: {
|
||||
PanelBody: function () {},
|
||||
SelectControl: function () {},
|
||||
Spinner: function () {},
|
||||
TextControl: function () {},
|
||||
RangeControl: function () {},
|
||||
ToolbarButton: function () {}
|
||||
},
|
||||
richText: {
|
||||
registerFormatType: function (name, settings) {
|
||||
formatRegs.push(name);
|
||||
formatsByName[name] = settings || {};
|
||||
},
|
||||
getFormatType: function (name) { return null; }
|
||||
},
|
||||
i18n: {
|
||||
__: function (s, domain) { return s; }
|
||||
},
|
||||
data: {}
|
||||
}
|
||||
};
|
||||
|
||||
// require the built bundle
|
||||
const bundlePath = path.resolve(__dirname, '../assets/build/index.js');
|
||||
if (!fs.existsSync(bundlePath)) {
|
||||
console.error('Built bundle not found at', bundlePath);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
try {
|
||||
require(bundlePath);
|
||||
} catch (err) {
|
||||
console.error('Error while executing bundle:', err && err.stack ? err.stack : err);
|
||||
process.exit(3);
|
||||
}
|
||||
|
||||
// report findings
|
||||
const out = {
|
||||
blockRegistrations: blockRegs,
|
||||
formatRegistrations: formatRegs,
|
||||
hasContextBlock: blockRegs.indexOf('swi-foot/context') !== -1,
|
||||
hasInlineFormat: formatRegs.indexOf('swi-foot/inline-format') !== -1
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(out, null, 2));
|
||||
process.exit(0);
|
||||
28
uninstall.php
Normal file
28
uninstall.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
// If uninstall not called from WordPress, exit
|
||||
if (!defined('WP_UNINSTALL_PLUGIN')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Remove plugin options
|
||||
delete_option('swi_foot_api_base_url');
|
||||
delete_option('swi_foot_api_username');
|
||||
delete_option('swi_foot_api_password');
|
||||
delete_option('swi_foot_verein_id');
|
||||
delete_option('swi_foot_season_id');
|
||||
delete_option('swi_foot_match_cache_duration');
|
||||
|
||||
// Remove transients and cached match data
|
||||
delete_transient('swi_foot_access_token');
|
||||
delete_transient('swi_foot_teams');
|
||||
$keys = get_transient('swi_foot_match_keys');
|
||||
if (is_array($keys)) {
|
||||
foreach ($keys as $mid) {
|
||||
delete_transient('swi_foot_match_' . $mid);
|
||||
}
|
||||
}
|
||||
delete_transient('swi_foot_match_keys');
|
||||
delete_transient('swi_foot_commons_ids');
|
||||
|
||||
// Remove permanently saved finished matches
|
||||
delete_option('swi_foot_finished_matches');
|
||||
10
webpack.config.js
Normal file
10
webpack.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const defaultConfig = require('@wordpress/scripts/config/webpack.config');
|
||||
|
||||
module.exports = {
|
||||
...defaultConfig,
|
||||
output: {
|
||||
...defaultConfig.output,
|
||||
// Ensure built editor bundle filename matches plugin expectation
|
||||
filename: 'editor-blocks.js',
|
||||
},
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user