various improvements to make release 1 complete
This commit is contained in:
parent
9ea962131a
commit
6b9c3fefa1
4
.gitignore
vendored
4
.gitignore
vendored
@ -46,6 +46,10 @@ dist/
|
||||
builds/
|
||||
*.zip
|
||||
|
||||
# Development Documentation
|
||||
# Internal notes and summaries, not for distribution
|
||||
dev-docs/
|
||||
|
||||
# Environment Files
|
||||
.env
|
||||
.env.local
|
||||
|
||||
121
README-DEV.md
121
README-DEV.md
@ -51,7 +51,6 @@ 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/`
|
||||
@ -87,7 +86,7 @@ Press `Ctrl+C` to stop the server.
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
```tree
|
||||
swiss-football-matchdata/
|
||||
├── src/
|
||||
│ ├── editor-blocks.js # Main editor block components
|
||||
@ -97,10 +96,11 @@ swiss-football-matchdata/
|
||||
├── blocks/ # Block definitions (metadata)
|
||||
│ ├── context/
|
||||
│ ├── match-events/
|
||||
│ ├── match-bench/
|
||||
│ ├── match-referees/
|
||||
│ ├── match-roster/
|
||||
│ ├── schedule/
|
||||
│ ├── standings/
|
||||
│ ├── team-data/
|
||||
│ └── shortcode-inserter/
|
||||
├── assets/
|
||||
│ ├── build/
|
||||
@ -130,6 +130,7 @@ swiss-football-matchdata/
|
||||
### 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`
|
||||
|
||||
@ -141,12 +142,14 @@ Each block is defined in its own directory under `blocks/`:
|
||||
- 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)
|
||||
|
||||
- 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
|
||||
@ -154,6 +157,7 @@ Each block is defined in its own directory under `blocks/`:
|
||||
- Look at page source to verify shortcode is generated correctly
|
||||
|
||||
4. **Build for production**:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
@ -190,36 +194,107 @@ Translation files include:
|
||||
|
||||
**Workflow when adding new translatable strings:**
|
||||
|
||||
1. Add translation strings to PHP or JavaScript with proper functions:
|
||||
1. Add translation strings to PHP or JavaScript with proper functions and translator comments:
|
||||
|
||||
```php
|
||||
__('Text to translate', 'swi_foot_matchdata')
|
||||
/* translators: %s is the API error message */
|
||||
__('Error loading data: %s', '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)
|
||||
**Important**: Always add `/* translators: ... */` comments above i18n functions with placeholders to clarify their meaning for translators.
|
||||
|
||||
3. Update existing `.po` translation files from the new template (done by translators in PoEdit or similar)
|
||||
2. Extract strings and update translation files using the i18n management script:
|
||||
|
||||
4. Generate binary `.mo` files:
|
||||
```bash
|
||||
wp i18n make-mo languages/
|
||||
./dev-scripts/i18n-manage.sh extract
|
||||
```
|
||||
|
||||
5. Commit both `.po` and `.mo` files to git:
|
||||
This extracts all strings to `languages/swi_foot_matchdata.pot`.
|
||||
|
||||
3. Generate `.po` files for translation:
|
||||
|
||||
```bash
|
||||
./dev-scripts/i18n-manage.sh translate
|
||||
```
|
||||
|
||||
Creates/updates `.po` files for: German (de_DE), French (fr_FR), Italian (it_IT), English (en_US)
|
||||
|
||||
4. Provide `.po` files to translators:
|
||||
|
||||
- Translators edit `languages/swi_foot_matchdata-de_DE.po`, `-fr_FR.po`, `-it_IT.po`
|
||||
- They can use PoEdit, Crowdin, or any PO file editor
|
||||
- Translator comments help clarify placeholder meanings
|
||||
|
||||
5. After translations are complete, compile `.mo` files:
|
||||
|
||||
```bash
|
||||
./dev-scripts/i18n-manage.sh build
|
||||
```
|
||||
|
||||
Generates binary `.mo` files from `.po` files.
|
||||
|
||||
6. Commit both `.po` and `.mo` files to git:
|
||||
|
||||
```bash
|
||||
git add languages/*.po languages/*.mo
|
||||
git commit -m "Update translations for [feature/language]"
|
||||
```
|
||||
|
||||
7. (Optional) Clean up regional variants:
|
||||
|
||||
```bash
|
||||
./dev-scripts/i18n-manage.sh clean
|
||||
```
|
||||
|
||||
Removes regional variants (de_AT, de_CH) to keep only core languages.
|
||||
|
||||
**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.
|
||||
|
||||
### Complete i18n Workflow with Script
|
||||
|
||||
The `i18n-manage.sh` script automates the entire translation pipeline:
|
||||
|
||||
```bash
|
||||
# Run complete workflow (extract → translate → build → clean)
|
||||
./dev-scripts/i18n-manage.sh all
|
||||
```
|
||||
|
||||
This is equivalent to running:
|
||||
```bash
|
||||
./dev-scripts/i18n-manage.sh extract # Extract strings to .pot
|
||||
./dev-scripts/i18n-manage.sh translate # Generate .po files for each language
|
||||
./dev-scripts/i18n-manage.sh build # Compile .po to .mo files
|
||||
./dev-scripts/i18n-manage.sh clean # Remove regional variants
|
||||
```
|
||||
|
||||
**Supported Languages (by default)**:
|
||||
- de_DE — German
|
||||
- fr_FR — French
|
||||
- it_IT — Italian
|
||||
- en_US — English
|
||||
|
||||
**Script Configuration**:
|
||||
|
||||
Edit `dev-scripts/i18n-manage.sh` lines 39-60 to customize:
|
||||
- `WP_CLI_CMD` — WordPress CLI command (for Docker or local)
|
||||
- `LANGUAGES` — Supported language codes
|
||||
- `REGIONAL_VARIANTS` — Variants to remove during cleanup
|
||||
|
||||
**Best Practices**:
|
||||
|
||||
- Always add translator comments for strings with placeholders
|
||||
- Use ordered placeholder syntax: `%1$d`, `%2$s` (not just `%d`, `%s`)
|
||||
- Keep strings complete and translatable (don't break into fragments)
|
||||
- Commit `.po` and `.mo` files to git along with source changes
|
||||
|
||||
For detailed documentation, see:
|
||||
- `dev-docs/I18N_QUICK_REFERENCE.md` — Quick start guide
|
||||
- `dev-docs/I18N_OPTIMIZATIONS.md` — Best practices and examples
|
||||
|
||||
## Testing in Development
|
||||
|
||||
### Local WordPress Installation
|
||||
@ -283,10 +358,11 @@ test -f assets/build/editor-blocks.asset.php && echo "✓ Asset manifest present
|
||||
A convenience bash script is included to automate distribution packaging:
|
||||
|
||||
```bash
|
||||
./build-distribution.sh
|
||||
./dev-scripts/build-distribution.sh
|
||||
```
|
||||
|
||||
This script:
|
||||
|
||||
- Creates a clean working directory
|
||||
- Excludes all development files and dependencies
|
||||
- Includes only files needed for distribution
|
||||
@ -294,11 +370,13 @@ This script:
|
||||
- Displays summary information
|
||||
|
||||
**Excluded from the distribution**:
|
||||
|
||||
- `node_modules/` (development dependencies)
|
||||
- `src/` (source code for building)
|
||||
- `test/` (test files)
|
||||
- `package.json` / `package-lock.json`
|
||||
- `webpack.config.js` (build config)
|
||||
- `dev-scripts/` (development scripts)
|
||||
- `README-DEV.md` (developer guide)
|
||||
- `.git`, `.gitignore`, `.DS_Store`, and other system files
|
||||
|
||||
@ -360,6 +438,7 @@ unzip -l dist/swiss-football-matchdata.zip | head -30
|
||||
### 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
|
||||
|
||||
@ -440,6 +519,7 @@ jobs:
|
||||
```
|
||||
|
||||
This ensures:
|
||||
|
||||
- Every push/PR has a valid build
|
||||
- Missing build artifacts fail visibly
|
||||
- PHP code is syntactically correct
|
||||
@ -501,7 +581,8 @@ npm start
|
||||
| Install deps | `npm install` |
|
||||
| Build for production | `npm run build` |
|
||||
| Development with hot reload | `npm start` |
|
||||
| Create distribution ZIP | `./build-distribution.sh` |
|
||||
| Extract and translate strings | `./dev-scripts/i18n-manage.sh all` |
|
||||
| Create distribution ZIP | `./dev-scripts/build-distribution.sh` |
|
||||
| Check PHP syntax | `php -l includes/class-swi-foot-blocks.php` |
|
||||
| Fix linting issues | `npx eslint src/ --fix` |
|
||||
|
||||
@ -512,6 +593,7 @@ This plugin integrates with the Swiss Football Association Club API. The API str
|
||||
**Swagger UI**: https://stg-club-api-services.football.ch/swagger/index.html
|
||||
|
||||
This documentation provides:
|
||||
|
||||
- All available REST endpoints
|
||||
- Request/response schemas
|
||||
- Authentication requirements
|
||||
@ -519,6 +601,7 @@ This documentation provides:
|
||||
- Example requests and responses
|
||||
|
||||
Refer to this documentation when:
|
||||
|
||||
- Adding new API integrations
|
||||
- Understanding the data structures used in the plugin
|
||||
- Debugging API-related issues
|
||||
|
||||
103
README.md
103
README.md
@ -35,19 +35,7 @@ This section explains how the plugin's Gutenberg editor blocks are intended to b
|
||||
- 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)
|
||||
#### 2. 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**:
|
||||
@ -56,7 +44,7 @@ This section explains how the plugin's Gutenberg editor blocks are intended to b
|
||||
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)
|
||||
#### 3. Specialized Blocks (Standings, Schedule, Roster, Events)
|
||||
|
||||
- **Swiss Football Standings**
|
||||
- Purpose: Display a league standings table for a team.
|
||||
@ -80,15 +68,76 @@ This section explains how the plugin's Gutenberg editor blocks are intended to b
|
||||
|
||||
## Shortcodes
|
||||
|
||||
Shortcodes provide additional flexibility where blocks are not desirable. Examples:
|
||||
Shortcodes provide additional flexibility where blocks are not desirable.
|
||||
|
||||
### Context Inheritance
|
||||
|
||||
Most shortcodes listed below support **context inheritance** from a parent `[swi_foot_context]` block. When a `match_id` or `team_id` is not explicitly provided as a parameter, the shortcode will attempt to use values from the current block context.
|
||||
|
||||
**Example with context:**
|
||||
```html
|
||||
[swi_foot_context match_id="12345"]
|
||||
[swi_foot_events] <!-- Automatically uses match_id from context -->
|
||||
[swi_foot_roster side="home"] <!-- Automatically uses match_id from context -->
|
||||
[/swi_foot_context]
|
||||
```
|
||||
|
||||
**Example without context (explicit parameters required):**
|
||||
```html
|
||||
[swi_foot_events match_id="12345"]
|
||||
[swi_foot_roster match_id="12345" side="home"]
|
||||
```
|
||||
|
||||
### Match Display & Elements
|
||||
|
||||
```html
|
||||
[swi_foot_match match_id="12345"]
|
||||
[swi_foot_match_home_team match_id="12345"]
|
||||
[swi_foot_match_away_team match_id="12345"]
|
||||
[swi_foot_match_date match_id="12345" format="d.m.Y"]
|
||||
[swi_foot_match_time match_id="12345"]
|
||||
[swi_foot_match_venue match_id="12345"]
|
||||
[swi_foot_match_score match_id="12345"]
|
||||
[swi_foot_match_status match_id="12345"]
|
||||
[swi_foot_match_league match_id="12345"]
|
||||
[swi_foot_match_round match_id="12345"]
|
||||
```
|
||||
|
||||
Refer to the original `readme.txt` for a complete list of shortcodes and their parameters.
|
||||
### Team & Standings
|
||||
|
||||
```html
|
||||
[swi_foot_standings team_id="42"]
|
||||
```
|
||||
|
||||
### Match Roster & Bench
|
||||
|
||||
```html
|
||||
[swi_foot_roster match_id="12345" side="home|away" starting_squad="true|false" bench="true|false"]
|
||||
[swi_foot_bench match_id="12345"]
|
||||
```
|
||||
|
||||
- `[swi_foot_roster]` — Display players for a match.
|
||||
- `side="home"` or `side="away"` — Show only one team (defaults to "home").
|
||||
- `starting_squad="true|false"` — Include starting squad (defaults to true).
|
||||
- `bench="true|false"` — Include bench/substitutes (defaults to false).
|
||||
- Players are distinguished by `assignmentRoleId`: 0 = starting squad, non-zero = bench.
|
||||
- When both are shown, lists are separated by a horizontal rule.
|
||||
|
||||
- `[swi_foot_bench]` — Display team staff and substitutes.
|
||||
- Automatically shows both home and away team staff.
|
||||
- `match_id` required; will auto-populate from context if in a context provider.
|
||||
|
||||
### Match Events & Referees
|
||||
|
||||
```html
|
||||
[swi_foot_events match_id="12345" event_order="dynamic|newest_first|oldest_first"]
|
||||
[swi_foot_referees match_id="12345"]
|
||||
```
|
||||
|
||||
- `[swi_foot_events]` — Show match events (goals, cards, substitutions, etc.) with configurable sort order.
|
||||
- `[swi_foot_referees]` — Display match referees with their roles formatted as "Role: Firstname Name" (name in bold).
|
||||
|
||||
Refer to the original `readme.txt` for additional shortcode details and parameters.
|
||||
|
||||
## Installation
|
||||
|
||||
@ -104,28 +153,6 @@ Refer to the original `readme.txt` for a complete list of shortcodes and their p
|
||||
- **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.
|
||||
|
||||
@ -12,6 +12,35 @@
|
||||
color: #23282d;
|
||||
}
|
||||
|
||||
/* Form actions with buttons */
|
||||
.swi-foot-form-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.swi-foot-form-actions button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Connection status messages */
|
||||
#connection-status {
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
#connection-status.success {
|
||||
color: #046b3a;
|
||||
}
|
||||
|
||||
#connection-status.error {
|
||||
color: #d63638;
|
||||
}
|
||||
|
||||
/* Teams grid layout */
|
||||
.swi-foot-teams-grid {
|
||||
display: grid;
|
||||
|
||||
133
assets/admin.js
133
assets/admin.js
@ -2,6 +2,96 @@
|
||||
jQuery(document).ready(function($) {
|
||||
'use strict';
|
||||
|
||||
// Settings form with Save and Test functionality
|
||||
$('#swi-foot-settings-form').on('submit', function(e) {
|
||||
var $form = $(this);
|
||||
var $status = $('#connection-status');
|
||||
var action = $('[name="swi_foot_action"]:focus').val() || 'save_only';
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// Get form data
|
||||
var formData = new FormData($form[0]);
|
||||
|
||||
// Submit form via WordPress options.php
|
||||
fetch($form.attr('action'), {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
body: formData
|
||||
}).then(function(resp) {
|
||||
// After form saves successfully, test if requested
|
||||
if (action === 'save_and_test') {
|
||||
return test_api_connection($status);
|
||||
} else {
|
||||
$('<div class="notice notice-info is-dismissible"><p>Settings saved!</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
$('<div class="notice notice-error is-dismissible"><p>Error saving settings: ' + err.message + '</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
});
|
||||
});
|
||||
|
||||
// Function to test API connection
|
||||
function test_api_connection($statusElement) {
|
||||
// Get current field values from form
|
||||
var apiUrl = $('input[name="swi_foot_api_base_url"]').val();
|
||||
var username = $('input[name="swi_foot_api_username"]').val();
|
||||
var password = $('input[name="swi_foot_api_password"]').val();
|
||||
|
||||
// Validate credentials first
|
||||
if (!username || !password) {
|
||||
$('<div class="notice notice-error is-dismissible"><p>API credentials not configured. Please configure Username and Password.</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!apiUrl) {
|
||||
$('<div class="notice notice-error is-dismissible"><p>API Base URL not configured.</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1: Check if URL is reachable with a HEAD request
|
||||
var urlTest = apiUrl.replace(/\/$/, '') + '/';
|
||||
fetch(urlTest, {
|
||||
method: 'HEAD',
|
||||
credentials: 'same-origin',
|
||||
mode: 'no-cors'
|
||||
}).then(function(resp) {
|
||||
// Step 2: Make actual API call via REST endpoint
|
||||
return 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
|
||||
},
|
||||
body: JSON.stringify({ test: true })
|
||||
});
|
||||
}).then(function(resp) {
|
||||
return resp.json();
|
||||
}).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$('<div class="notice notice-success is-dismissible"><p>Settings saved and connection test successful!</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
} else {
|
||||
var errorMsg = (response.error || 'Connection failed');
|
||||
var details = response.details ? ': ' + response.details : '';
|
||||
$('<div class="notice notice-error is-dismissible"><p>' + errorMsg + details + '</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
$('<div class="notice notice-error is-dismissible"><p>API URL not reachable: ' + apiUrl + '</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle button clicks to set which action was used
|
||||
$('button[name="swi_foot_action"]').on('click', function() {
|
||||
$(this).focus();
|
||||
});
|
||||
|
||||
// Refresh teams functionality
|
||||
$('#refresh-teams').on('click', function() {
|
||||
var $button = $(this);
|
||||
@ -18,13 +108,13 @@ jQuery(document).ready(function($) {
|
||||
return resp.json();
|
||||
}).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$status.text('Teams refreshed successfully!').addClass('success');
|
||||
$status.text('✓ Teams refreshed successfully!').addClass('success');
|
||||
setTimeout(function() { location.reload(); }, 1500);
|
||||
} else {
|
||||
$status.text('Error: ' + (response.error || 'Unknown')).addClass('error');
|
||||
$status.text('✗ Error: ' + (response.error || 'Unknown')).addClass('error');
|
||||
}
|
||||
}).catch(function() {
|
||||
$status.text('Network error occurred.').addClass('error');
|
||||
$status.text('✗ Network error occurred.').addClass('error');
|
||||
}).finally(function() {
|
||||
$button.prop('disabled', false).text('Refresh Teams List');
|
||||
});
|
||||
@ -48,47 +138,16 @@ jQuery(document).ready(function($) {
|
||||
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');
|
||||
$status.text('✓ Cache cleared successfully!').addClass('success');
|
||||
setTimeout(function() { location.reload(); }, 1000);
|
||||
} else {
|
||||
$status.text('Error clearing cache.').addClass('error');
|
||||
$status.text('✗ Error clearing cache.').addClass('error');
|
||||
}
|
||||
}).catch(function() {
|
||||
$status.text('Error clearing cache.').addClass('error');
|
||||
$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');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -143,7 +143,7 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Error messages */
|
||||
/* Error messages - for configuration/setup issues */
|
||||
.swi-foot-error {
|
||||
color: #721c24;
|
||||
padding: 12px 16px;
|
||||
@ -154,6 +154,39 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Warning messages - yellow style for "data not yet available" (406) */
|
||||
.swi-foot-data-warning {
|
||||
color: #856404;
|
||||
padding: 12px 16px;
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 6px;
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Unavailable data messages - grey style for "no data available" */
|
||||
.swi-foot-data-unavailable {
|
||||
color: #6c757d;
|
||||
padding: 12px 16px;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Notice messages - informational style (deprecated, use data-warning or data-unavailable) */
|
||||
.swi-foot-notice {
|
||||
color: #6c757d;
|
||||
padding: 12px 16px;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.swi-foot-table {
|
||||
@ -379,3 +412,40 @@
|
||||
max-height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Roster and Bench Player Formatting */
|
||||
|
||||
.swi-foot-position {
|
||||
color: #6c757d;
|
||||
font-size: 0.9em;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.swi-foot-role {
|
||||
color: #6c757d;
|
||||
font-size: 0.9em;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.roster-player-captain {
|
||||
font-weight: 500;
|
||||
color: #d4a017;
|
||||
}
|
||||
|
||||
.swi-foot-captain-badge {
|
||||
display: inline-block;
|
||||
background-color: #d4a017;
|
||||
color: #fff;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
653
assets/editor-blocks.js
Normal file
653
assets/editor-blocks.js
Normal file
@ -0,0 +1,653 @@
|
||||
// Swiss Football Gutenberg Blocks - Refactored User Flow
|
||||
(function (blocks, element, editor, components, i18n, data) {
|
||||
'use strict';
|
||||
|
||||
const { registerBlockType } = blocks;
|
||||
const { createElement: el, useState, useEffect } = element;
|
||||
const { InspectorControls } = editor;
|
||||
const { PanelBody, SelectControl, TextControl, ToggleControl, Spinner, RangeControl } = components;
|
||||
const { __ } = i18n;
|
||||
|
||||
// Helper function to make AJAX calls
|
||||
function makeAjaxCall(action, data = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jQuery.post(swiFootEditorData.ajax_url, {
|
||||
action: action,
|
||||
nonce: swiFootEditorData.nonce,
|
||||
...data
|
||||
}, function (response) {
|
||||
if (response && response.success) {
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
}).fail(reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Main Swiss Football Block - Refactored Logic
|
||||
registerBlockType('swi-foot/team-data', {
|
||||
title: __('Swiss Football Team Data', 'swi_foot_matchdata'),
|
||||
description: __('Display team standings or match data with guided selection', 'swi_foot_matchdata'),
|
||||
icon: 'universal-access-alt',
|
||||
category: 'widgets',
|
||||
keywords: [
|
||||
__('football', 'swi_foot_matchdata'),
|
||||
__('soccer', 'swi_foot_matchdata'),
|
||||
__('team', 'swi_foot_matchdata'),
|
||||
__('swiss', 'swi_foot_matchdata'),
|
||||
],
|
||||
|
||||
attributes: {
|
||||
selectedTeam: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
dataType: {
|
||||
type: 'string',
|
||||
default: '' // 'match' or 'stats'
|
||||
},
|
||||
selectedMatch: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
shortcodeType: {
|
||||
type: 'string',
|
||||
default: 'match'
|
||||
},
|
||||
format: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
separator: {
|
||||
type: 'string',
|
||||
default: ':'
|
||||
}
|
||||
},
|
||||
|
||||
edit: function (props) {
|
||||
const { attributes, setAttributes } = props;
|
||||
const { selectedTeam, dataType, selectedMatch, shortcodeType, format, separator } = attributes;
|
||||
|
||||
// State for dynamic data
|
||||
const [teams, setTeams] = useState([]);
|
||||
const [matches, setMatches] = useState([]);
|
||||
const [loadingTeams, setLoadingTeams] = useState(true);
|
||||
const [loadingMatches, setLoadingMatches] = useState(false);
|
||||
|
||||
// Load teams on component mount
|
||||
useEffect(() => {
|
||||
makeAjaxCall('swi_foot_get_teams_for_editor')
|
||||
.then(response => {
|
||||
if (response.success && response.data) {
|
||||
setTeams(response.data);
|
||||
}
|
||||
setLoadingTeams(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoadingTeams(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Load matches when team is selected and dataType is 'match'
|
||||
useEffect(() => {
|
||||
if (selectedTeam && dataType === 'match') {
|
||||
setLoadingMatches(true);
|
||||
makeAjaxCall('swi_foot_get_matches_for_team', { team_id: selectedTeam })
|
||||
.then(response => {
|
||||
if (response.success && response.data) {
|
||||
setMatches(response.data);
|
||||
}
|
||||
setLoadingMatches(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoadingMatches(false);
|
||||
});
|
||||
} else {
|
||||
setMatches([]);
|
||||
}
|
||||
}, [selectedTeam, dataType]);
|
||||
|
||||
// Build shortcode/content preview
|
||||
let content = '';
|
||||
if (selectedTeam && dataType === 'stats') {
|
||||
content = '[swi_foot_standings team_id="' + selectedTeam + '"]';
|
||||
} else if (selectedTeam && dataType === 'match' && selectedMatch) {
|
||||
if (selectedMatch === 'current') {
|
||||
content = '[swi_foot_' + shortcodeType + ' team_id="' + selectedTeam + '" show_current="true"';
|
||||
} else {
|
||||
content = '[swi_foot_' + shortcodeType + ' match_id="' + selectedMatch + '"';
|
||||
}
|
||||
|
||||
if (format && (shortcodeType === 'match_date' || shortcodeType === 'match_time')) {
|
||||
content += ' format="' + format + '"';
|
||||
}
|
||||
if (separator !== ':' && shortcodeType === 'match_score') {
|
||||
content += ' separator="' + separator + '"';
|
||||
}
|
||||
content += ']';
|
||||
}
|
||||
|
||||
// Data type options
|
||||
const dataTypeOptions = [
|
||||
{ value: '', label: __('Choose what to display...', 'swi_foot_matchdata') },
|
||||
{ value: 'stats', label: __('Team Statistics (Standings/Ranking)', 'swi_foot_matchdata') },
|
||||
{ value: 'match', label: __('Match Data', 'swi_foot_matchdata') }
|
||||
];
|
||||
|
||||
// Match data type options
|
||||
const matchDataOptions = [
|
||||
{ value: 'match', label: __('Complete Match Information', 'swi_foot_matchdata') },
|
||||
{ value: 'match_home_team', label: __('Home Team Name Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_away_team', label: __('Away Team Name Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_date', label: __('Match Date Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_time', label: __('Match Time Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_venue', label: __('Venue/Location Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_score', label: __('Score Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_status', label: __('Match Status Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_league', label: __('League Name Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_round', label: __('Round Number Only', 'swi_foot_matchdata') }
|
||||
];
|
||||
|
||||
return el('div', { className: 'swi-foot-team-data-block' },
|
||||
el(InspectorControls, {},
|
||||
el(PanelBody, { title: __('Team Selection', 'swi_foot_matchdata') },
|
||||
// Step 1: Team selection
|
||||
loadingTeams ? el(Spinner) : el(SelectControl, {
|
||||
label: __('1. Select Team', 'swi_foot_matchdata'),
|
||||
value: selectedTeam,
|
||||
options: [{ value: '', label: __('Choose a team...', 'swi_foot_matchdata') }].concat(
|
||||
teams.map(team => ({
|
||||
value: team.value,
|
||||
label: team.label
|
||||
}))
|
||||
),
|
||||
onChange: function (value) {
|
||||
setAttributes({
|
||||
selectedTeam: value,
|
||||
dataType: '', // Reset when team changes
|
||||
selectedMatch: ''
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
// Step 2: Data type selection (only show if team is selected)
|
||||
selectedTeam && el(SelectControl, {
|
||||
label: __('2. What would you like to display?', 'swi_foot_matchdata'),
|
||||
value: dataType,
|
||||
options: dataTypeOptions,
|
||||
onChange: function (value) {
|
||||
setAttributes({
|
||||
dataType: value,
|
||||
selectedMatch: '' // Reset match when data type changes
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
|
||||
// Match-specific settings
|
||||
dataType === 'match' && el(PanelBody, { title: __('Match Settings', 'swi_foot_matchdata') },
|
||||
// Match selection
|
||||
loadingMatches ? el(Spinner) : el(SelectControl, {
|
||||
label: __('3. Select Match', 'swi_foot_matchdata'),
|
||||
value: selectedMatch,
|
||||
options: [{ value: '', label: __('Choose a match...', 'swi_foot_matchdata') }].concat(
|
||||
[{ value: 'current', label: __('Current Match (Next upcoming or recent)', 'swi_foot_matchdata') }],
|
||||
matches.map(match => ({
|
||||
value: match.value,
|
||||
label: match.label
|
||||
}))
|
||||
),
|
||||
onChange: function (value) { setAttributes({ selectedMatch: value }); }
|
||||
}),
|
||||
|
||||
// Match data type (what part of match to show)
|
||||
selectedMatch && el(SelectControl, {
|
||||
label: __('4. What match information to display?', 'swi_foot_matchdata'),
|
||||
value: shortcodeType,
|
||||
options: matchDataOptions,
|
||||
onChange: function (value) { setAttributes({ shortcodeType: value }); }
|
||||
})
|
||||
),
|
||||
|
||||
// Advanced options for match data
|
||||
dataType === 'match' && selectedMatch && el(PanelBody, { title: __('Advanced Options', 'swi_foot_matchdata'), initialOpen: false },
|
||||
(shortcodeType === 'match_date' || shortcodeType === 'match_time') && el(SelectControl, {
|
||||
label: __('Date/Time Format', 'swi_foot_matchdata'),
|
||||
value: format,
|
||||
options: shortcodeType === 'match_date' ? [
|
||||
{ value: '', label: __('Default (WordPress setting)', 'swi_foot_matchdata') },
|
||||
{ value: 'd.m.Y', label: '31.12.2024 (European)' },
|
||||
{ value: 'm/d/Y', label: '12/31/2024 (US)' },
|
||||
{ value: 'F j, Y', label: 'December 31, 2024' },
|
||||
{ value: 'D, M j', label: 'Tue, Dec 31' }
|
||||
] : [
|
||||
{ value: '', label: __('Default (WordPress setting)', 'swi_foot_matchdata') },
|
||||
{ value: 'H:i', label: '14:30 (24-hour)' },
|
||||
{ value: 'g:i A', label: '2:30 PM (12-hour)' },
|
||||
{ value: 'g:i', label: '2:30 (12-hour no AM/PM)' }
|
||||
],
|
||||
onChange: function (value) { setAttributes({ format: value }); }
|
||||
}),
|
||||
|
||||
shortcodeType === 'match_score' && el(SelectControl, {
|
||||
label: __('Score Separator', 'swi_foot_matchdata'),
|
||||
value: separator,
|
||||
options: [
|
||||
{ value: ':', label: '2 : 1 (colon with spaces)' },
|
||||
{ value: '-', label: '2 - 1 (dash with spaces)' },
|
||||
{ value: ' vs ', label: '2 vs 1 (versus)' },
|
||||
{ value: '|', label: '2 | 1 (pipe)' }
|
||||
],
|
||||
onChange: function (value) { setAttributes({ separator: value }); }
|
||||
})
|
||||
)
|
||||
),
|
||||
|
||||
// Main preview area
|
||||
el('div', { className: 'swi-foot-preview' },
|
||||
el('div', {
|
||||
style: {
|
||||
padding: '30px',
|
||||
textAlign: 'center',
|
||||
background: '#f8f9fa',
|
||||
border: '2px dashed #ddd',
|
||||
borderRadius: '8px'
|
||||
}
|
||||
},
|
||||
el('h4', { style: { marginTop: 0 } }, __('Swiss Football Team Data', 'swi_foot_matchdata')),
|
||||
|
||||
!selectedTeam && el('p', { style: { color: '#666' } },
|
||||
__('1. Please select a team to get started', 'swi_foot_matchdata')
|
||||
),
|
||||
|
||||
selectedTeam && !dataType && el('p', { style: { color: '#666' } },
|
||||
__('2. Please choose what you want to display', 'swi_foot_matchdata')
|
||||
),
|
||||
|
||||
selectedTeam && dataType === 'stats' && el('p', { style: { color: '#28a745' } },
|
||||
'✓ ' + __('Will display team standings/ranking', 'swi_foot_matchdata')
|
||||
),
|
||||
|
||||
selectedTeam && dataType === 'match' && !selectedMatch && el('p', { style: { color: '#666' } },
|
||||
__('3. Please select a match', 'swi_foot_matchdata')
|
||||
),
|
||||
|
||||
selectedTeam && dataType === 'match' && selectedMatch && el('div', {},
|
||||
el('p', { style: { color: '#28a745' } },
|
||||
'✓ ' + __('Ready to display match data', 'swi_foot_matchdata')
|
||||
),
|
||||
el('p', { style: { fontSize: '14px', color: '#666' } },
|
||||
__('Generated shortcode:', 'swi_foot_matchdata')
|
||||
),
|
||||
el('code', {
|
||||
style: {
|
||||
display: 'block',
|
||||
padding: '10px',
|
||||
background: '#fff',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
fontFamily: 'Monaco, Menlo, monospace',
|
||||
fontSize: '12px',
|
||||
wordBreak: 'break-all'
|
||||
}
|
||||
}, content)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
save: function (props) {
|
||||
const { attributes } = props;
|
||||
const { selectedTeam, dataType, selectedMatch, shortcodeType, format, separator } = attributes;
|
||||
|
||||
// Build shortcode for saving
|
||||
let shortcode = '';
|
||||
if (selectedTeam && dataType === 'stats') {
|
||||
shortcode = '[swi_foot_standings team_id="' + selectedTeam + '"]';
|
||||
} else if (selectedTeam && dataType === 'match' && selectedMatch) {
|
||||
if (selectedMatch === 'current') {
|
||||
shortcode = '[swi_foot_' + shortcodeType + ' team_id="' + selectedTeam + '" show_current="true"';
|
||||
} else {
|
||||
shortcode = '[swi_foot_' + shortcodeType + ' match_id="' + selectedMatch + '"';
|
||||
}
|
||||
|
||||
if (format && (shortcodeType === 'match_date' || shortcodeType === 'match_time')) {
|
||||
shortcode += ' format="' + format + '"';
|
||||
}
|
||||
if (separator !== ':' && shortcodeType === 'match_score') {
|
||||
shortcode += ' separator="' + separator + '"';
|
||||
}
|
||||
shortcode += ']';
|
||||
}
|
||||
|
||||
return shortcode;
|
||||
}
|
||||
});
|
||||
|
||||
// Match Roster Block with Side Selector and Bench Option
|
||||
registerBlockType('swi-foot/match-roster', {
|
||||
title: __('Swiss Football Match Roster', 'swi_foot_matchdata'),
|
||||
description: __('Display match roster for home or away team, with optional bench players', 'swi_foot_matchdata'),
|
||||
icon: 'groups',
|
||||
category: 'widgets',
|
||||
keywords: [
|
||||
__('roster', 'swi_foot_matchdata'),
|
||||
__('players', 'swi_foot_matchdata'),
|
||||
__('lineup', 'swi_foot_matchdata'),
|
||||
__('team', 'swi_foot_matchdata'),
|
||||
],
|
||||
|
||||
attributes: {
|
||||
selectedTeam: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
selectedMatch: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
side: {
|
||||
type: 'string',
|
||||
default: 'home' // home or away
|
||||
},
|
||||
withBench: {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
edit: function (props) {
|
||||
const { attributes, setAttributes } = props;
|
||||
const { selectedTeam, selectedMatch, side, withBench } = attributes;
|
||||
|
||||
const [teams, setTeams] = wp.element.useState([]);
|
||||
const [matches, setMatches] = wp.element.useState([]);
|
||||
const [loadingTeams, setLoadingTeams] = wp.element.useState(true);
|
||||
const [loadingMatches, setLoadingMatches] = wp.element.useState(false);
|
||||
|
||||
// Helper AJAX call
|
||||
function makeAjaxCall(action, data = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jQuery.post(swiFootEditorData.ajax_url, {
|
||||
action: action,
|
||||
nonce: swiFootEditorData.nonce,
|
||||
...data
|
||||
}, function (response) {
|
||||
if (response && response.success) {
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
}).fail(reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Load teams
|
||||
wp.element.useEffect(() => {
|
||||
setLoadingTeams(true);
|
||||
makeAjaxCall('swi_foot_get_teams_for_editor')
|
||||
.then(response => {
|
||||
if (Array.isArray(response.data)) {
|
||||
setTeams(response.data);
|
||||
}
|
||||
setLoadingTeams(false);
|
||||
})
|
||||
.catch(() => setLoadingTeams(false));
|
||||
}, []);
|
||||
|
||||
// Load matches when team changes
|
||||
wp.element.useEffect(() => {
|
||||
if (selectedTeam) {
|
||||
setLoadingMatches(true);
|
||||
makeAjaxCall('swi_foot_get_matches_for_team', { team_id: selectedTeam })
|
||||
.then(response => {
|
||||
if (Array.isArray(response.data)) {
|
||||
setMatches(response.data);
|
||||
}
|
||||
setLoadingMatches(false);
|
||||
})
|
||||
.catch(() => setLoadingMatches(false));
|
||||
}
|
||||
}, [selectedTeam]);
|
||||
|
||||
return el('div', { className: 'swi-foot-roster-block' },
|
||||
el(InspectorControls, {},
|
||||
el(PanelBody, { title: __('Match Selection', 'swi_foot_matchdata') },
|
||||
|
||||
// Team selector
|
||||
loadingTeams ? el(Spinner) : el(SelectControl, {
|
||||
label: __('Select Team', 'swi_foot_matchdata'),
|
||||
value: selectedTeam,
|
||||
options: [{ value: '', label: __('Choose a team...', 'swi_foot_matchdata') }].concat(
|
||||
teams.map(team => ({ value: team.value, label: team.label }))
|
||||
),
|
||||
onChange: function (value) {
|
||||
setAttributes({ selectedTeam: value, selectedMatch: '' });
|
||||
}
|
||||
}),
|
||||
|
||||
// Match selector (shows after team is selected)
|
||||
selectedTeam && (loadingMatches ? el(Spinner) : el(SelectControl, {
|
||||
label: __('Select Match', 'swi_foot_matchdata'),
|
||||
value: selectedMatch,
|
||||
options: [{ value: '', label: __('Choose a match...', 'swi_foot_matchdata') }].concat(
|
||||
[{ value: 'current', label: __('Current Match', 'swi_foot_matchdata') }],
|
||||
matches.map(match => ({ value: match.value, label: match.label }))
|
||||
),
|
||||
onChange: function (value) { setAttributes({ selectedMatch: value }); }
|
||||
})),
|
||||
|
||||
// Side selector (Home/Away)
|
||||
selectedMatch && el(SelectControl, {
|
||||
label: __('Select Side', 'swi_foot_matchdata'),
|
||||
value: side,
|
||||
options: [
|
||||
{ value: 'home', label: __('Home Team', 'swi_foot_matchdata') },
|
||||
{ value: 'away', label: __('Away Team', 'swi_foot_matchdata') }
|
||||
],
|
||||
onChange: function (value) { setAttributes({ side: value }); }
|
||||
}),
|
||||
|
||||
// Include bench toggle
|
||||
selectedMatch && el(ToggleControl, {
|
||||
label: __('Include Bench Players', 'swi_foot_matchdata'),
|
||||
help: __('When enabled, bench players will also be displayed', 'swi_foot_matchdata'),
|
||||
checked: withBench,
|
||||
onChange: function (value) { setAttributes({ withBench: value }); }
|
||||
})
|
||||
)
|
||||
),
|
||||
|
||||
// Block preview
|
||||
el('div', {
|
||||
style: {
|
||||
padding: '30px',
|
||||
textAlign: 'center',
|
||||
background: '#f8f9fa',
|
||||
border: '2px dashed #ddd',
|
||||
borderRadius: '8px'
|
||||
}
|
||||
},
|
||||
el('h4', { style: { marginTop: 0 } }, __('Match Roster', 'swi_foot_matchdata')),
|
||||
(!selectedTeam || !selectedMatch) ?
|
||||
el('p', { style: { color: '#666' } },
|
||||
__('Please select team, match, side, and bench option', 'swi_foot_matchdata')
|
||||
)
|
||||
:
|
||||
el('p', { style: { color: '#28a745' } },
|
||||
'✓ ' + __('Roster will be displayed for ' + side + ' team' + (withBench ? ' (with bench)' : ''), 'swi_foot_matchdata')
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
save: function (props) {
|
||||
const { selectedTeam, selectedMatch, side, withBench } = props.attributes;
|
||||
if (!selectedTeam || !selectedMatch || !side) return '';
|
||||
|
||||
let shortcode = '[swi_foot_roster ';
|
||||
if (selectedMatch === 'current') {
|
||||
shortcode += 'team_id="' + selectedTeam + '" show_current="true"';
|
||||
} else {
|
||||
shortcode += 'match_id="' + selectedMatch + '"';
|
||||
}
|
||||
shortcode += ' side="' + side + '"';
|
||||
if (withBench) {
|
||||
shortcode += ' with_bench="true"';
|
||||
}
|
||||
shortcode += ']';
|
||||
|
||||
return shortcode;
|
||||
}
|
||||
});
|
||||
|
||||
// Match Events Block
|
||||
registerBlockType('swi-foot/match-events', {
|
||||
title: __('Swiss Football Match Events', 'swi_foot_matchdata'),
|
||||
description: __('Display live match events with auto-refresh', 'swi_foot_matchdata'),
|
||||
icon: 'list-view',
|
||||
category: 'widgets',
|
||||
keywords: [
|
||||
__('events', 'swi_foot_matchdata'),
|
||||
__('live', 'swi_foot_matchdata'),
|
||||
__('timeline', 'swi_foot_matchdata'),
|
||||
__('match', 'swi_foot_matchdata'),
|
||||
],
|
||||
|
||||
attributes: {
|
||||
selectedTeam: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
selectedMatch: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
refreshInterval: {
|
||||
type: 'number',
|
||||
default: 30
|
||||
}
|
||||
},
|
||||
|
||||
edit: function (props) {
|
||||
const { attributes, setAttributes } = props;
|
||||
const { selectedTeam, selectedMatch, refreshInterval } = attributes;
|
||||
|
||||
const [teams, setTeams] = useState([]);
|
||||
const [matches, setMatches] = useState([]);
|
||||
const [loadingTeams, setLoadingTeams] = useState(true);
|
||||
const [loadingMatches, setLoadingMatches] = useState(false);
|
||||
|
||||
// Load teams
|
||||
useEffect(() => {
|
||||
makeAjaxCall('swi_foot_get_teams_for_editor')
|
||||
.then(response => {
|
||||
if (response.success && response.data) {
|
||||
setTeams(response.data);
|
||||
}
|
||||
setLoadingTeams(false);
|
||||
})
|
||||
.catch(() => setLoadingTeams(false));
|
||||
}, []);
|
||||
|
||||
// Load matches when team changes
|
||||
useEffect(() => {
|
||||
if (selectedTeam) {
|
||||
setLoadingMatches(true);
|
||||
makeAjaxCall('swi_foot_get_matches_for_team', { team_id: selectedTeam })
|
||||
.then(response => {
|
||||
if (response.success && response.data) {
|
||||
setMatches(response.data);
|
||||
}
|
||||
setLoadingMatches(false);
|
||||
})
|
||||
.catch(() => setLoadingMatches(false));
|
||||
}
|
||||
}, [selectedTeam]);
|
||||
|
||||
return el('div', { className: 'swi-foot-events-block' },
|
||||
el(InspectorControls, {},
|
||||
el(PanelBody, { title: __('Match Selection', 'swi_foot_matchdata') },
|
||||
loadingTeams ? el(Spinner) : el(SelectControl, {
|
||||
label: __('Select Team', 'swi_foot_matchdata'),
|
||||
value: selectedTeam,
|
||||
options: [{ value: '', label: __('Choose a team...', 'swi_foot_matchdata') }].concat(
|
||||
teams.map(team => ({ value: team.value, label: team.label }))
|
||||
),
|
||||
onChange: function (value) {
|
||||
setAttributes({ selectedTeam: value, selectedMatch: '' });
|
||||
}
|
||||
}),
|
||||
|
||||
selectedTeam && (loadingMatches ? el(Spinner) : el(SelectControl, {
|
||||
label: __('Select Match', 'swi_foot_matchdata'),
|
||||
value: selectedMatch,
|
||||
options: [{ value: '', label: __('Choose a match...', 'swi_foot_matchdata') }].concat(
|
||||
[{ value: 'current', label: __('Current Match', 'swi_foot_matchdata') }],
|
||||
matches.map(match => ({ value: match.value, label: match.label }))
|
||||
),
|
||||
onChange: function (value) { setAttributes({ selectedMatch: value }); }
|
||||
}))
|
||||
),
|
||||
|
||||
el(PanelBody, { title: __('Auto-Refresh Settings', 'swi_foot_matchdata') },
|
||||
el(RangeControl, {
|
||||
label: __('Refresh Interval (seconds)', 'swi_foot_matchdata'),
|
||||
value: refreshInterval,
|
||||
onChange: function (value) { setAttributes({ refreshInterval: value }); },
|
||||
min: 10,
|
||||
max: 300,
|
||||
step: 10
|
||||
})
|
||||
)
|
||||
),
|
||||
|
||||
el('div', {
|
||||
style: {
|
||||
padding: '30px',
|
||||
textAlign: 'center',
|
||||
background: '#f8f9fa',
|
||||
border: '2px dashed #ddd',
|
||||
borderRadius: '8px'
|
||||
}
|
||||
},
|
||||
el('h4', { style: { marginTop: 0 } }, __('Live Match Events', 'swi_foot_matchdata')),
|
||||
(!selectedTeam || !selectedMatch) ?
|
||||
el('p', { style: { color: '#666' } }, __('Please select team and match', 'swi_foot_matchdata')) :
|
||||
el('div', {},
|
||||
el('p', { style: { color: '#28a745' } }, '✓ ' + __('Will display live match events', 'swi_foot_matchdata')),
|
||||
el('p', { style: { fontSize: '12px', color: '#666' } },
|
||||
__('Auto-refresh every', 'swi_foot_matchdata') + ' ' + refreshInterval + ' ' + __('seconds', 'swi_foot_matchdata'))
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
save: function (props) {
|
||||
const { attributes } = props;
|
||||
const { selectedTeam, selectedMatch, refreshInterval } = attributes;
|
||||
|
||||
if (!selectedTeam || !selectedMatch) return '';
|
||||
|
||||
let shortcode = '[swi_foot_events ';
|
||||
if (selectedMatch === 'current') {
|
||||
shortcode += 'team_id="' + selectedTeam + '" show_current="true"';
|
||||
} else {
|
||||
shortcode += 'match_id="' + selectedMatch + '"';
|
||||
}
|
||||
shortcode += ' refresh_interval="' + refreshInterval + '"]';
|
||||
|
||||
return shortcode;
|
||||
}
|
||||
});
|
||||
|
||||
})(
|
||||
window.wp.blocks,
|
||||
window.wp.element,
|
||||
window.wp.blockEditor,
|
||||
window.wp.components,
|
||||
window.wp.i18n,
|
||||
window.wp.data
|
||||
);
|
||||
@ -2,7 +2,7 @@
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/context",
|
||||
"title": "Swiss Football Context (container)",
|
||||
"category": "widgets",
|
||||
"category": "swi-football",
|
||||
"icon": "admin-site",
|
||||
"description": "Provides a team/season/match context to child blocks. Children will inherit these settings unless they override them.",
|
||||
"providesContext": {
|
||||
|
||||
12
blocks/match-bench/block.json
Normal file
12
blocks/match-bench/block.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/match-bench",
|
||||
"title": "Swiss Football Match Bench",
|
||||
"category": "swi-football",
|
||||
"icon": "clipboard-user",
|
||||
"description": "Display team staff and substitutes for a match",
|
||||
"attributes": {
|
||||
"side": { "type": "string", "default": "home" }
|
||||
},
|
||||
"usesContext": ["swi-foot/context"]
|
||||
}
|
||||
@ -2,8 +2,8 @@
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/match-events",
|
||||
"title": "Swiss Football Match Events",
|
||||
"category": "widgets",
|
||||
"icon": "list-view",
|
||||
"category": "swi-football",
|
||||
"icon": "info",
|
||||
"description": "Live match events with optional auto-refresh.",
|
||||
"attributes": {
|
||||
"selectedTeam": { "type": "string", "default": "" },
|
||||
|
||||
15
blocks/match-referees/block.json
Normal file
15
blocks/match-referees/block.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/match-referees",
|
||||
"title": "Swiss Football Match Referees",
|
||||
"category": "swi-football",
|
||||
"icon": "whistle",
|
||||
"description": "Display match officials and referees",
|
||||
"attributes": {
|
||||
"selectedMatch": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
"usesContext": ["swi-foot/context"]
|
||||
}
|
||||
@ -2,14 +2,15 @@
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/match-roster",
|
||||
"title": "Swiss Football Match Roster",
|
||||
"category": "widgets",
|
||||
"category": "swi-football",
|
||||
"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 }
|
||||
"showStartingSquad": { "type": "boolean", "default": true },
|
||||
"showBench": { "type": "boolean", "default": false }
|
||||
}
|
||||
,
|
||||
"usesContext": ["swi-foot/context"]
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/schedule",
|
||||
"title": "Swiss Football Schedule",
|
||||
"category": "widgets",
|
||||
"icon": "schedule",
|
||||
"category": "swi-football",
|
||||
"icon": "calendar",
|
||||
"description": "Display upcoming matches for a team.",
|
||||
"attributes": {
|
||||
"teamId": {
|
||||
@ -13,6 +13,11 @@
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"default": 5
|
||||
},
|
||||
"matchFilter": {
|
||||
"type": "string",
|
||||
"default": "all",
|
||||
"enum": ["all", "home", "away"]
|
||||
}
|
||||
}
|
||||
,
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"apiVersion": 3,
|
||||
"name": "swi-foot/standings",
|
||||
"title": "Swiss Football Standings",
|
||||
"category": "widgets",
|
||||
"category": "swi-football",
|
||||
"icon": "analytics",
|
||||
"description": "Display current standings for a team.",
|
||||
"attributes": {
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
@ -5,8 +5,12 @@
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PLUGIN_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
PLUGIN_NAME="swiss-football-matchdata"
|
||||
DIST_DIR="dist"
|
||||
DIST_DIR="$PLUGIN_ROOT/dist"
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
@ -19,9 +23,9 @@ echo -e "${BLUE}=== Building Distribution Package ===${NC}"
|
||||
echo "Creating distribution directory..."
|
||||
mkdir -p "$DIST_DIR"
|
||||
|
||||
# Check if we're in the plugin root directory
|
||||
if [ ! -f "package.json" ]; then
|
||||
echo "Error: package.json not found. Run this script from the plugin root directory."
|
||||
# Check if plugin root has package.json
|
||||
if [ ! -f "$PLUGIN_ROOT/package.json" ]; then
|
||||
echo "Error: package.json not found in $PLUGIN_ROOT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -31,10 +35,11 @@ rm -rf "$DIST_DIR/$PLUGIN_NAME" "$DIST_DIR/$PLUGIN_NAME.zip"
|
||||
|
||||
# Copy all plugin files, excluding development artifacts
|
||||
echo "Copying plugin files (excluding development files)..."
|
||||
rsync -av \
|
||||
rsync -av "$PLUGIN_ROOT/" \
|
||||
--exclude='.git' \
|
||||
--exclude='node_modules' \
|
||||
--exclude='.npm-cache' \
|
||||
--exclude='package.json' \
|
||||
--exclude='package-lock.json' \
|
||||
--exclude='.gitignore' \
|
||||
--exclude='README-DEV.md' \
|
||||
@ -45,9 +50,10 @@ rsync -av \
|
||||
--exclude='test/' \
|
||||
--exclude='src/' \
|
||||
--exclude='webpack.config.js' \
|
||||
--exclude='build-distribution.sh' \
|
||||
--exclude='dev-scripts/' \
|
||||
--exclude='dev-docs/' \
|
||||
--exclude='dist/' \
|
||||
. "$DIST_DIR/$PLUGIN_NAME/" \
|
||||
"$DIST_DIR/$PLUGIN_NAME/" \
|
||||
> /dev/null 2>&1 || true
|
||||
|
||||
# Verify essential files are present
|
||||
@ -64,9 +70,7 @@ fi
|
||||
|
||||
# Create ZIP archive
|
||||
echo "Creating ZIP archive..."
|
||||
cd "$DIST_DIR"
|
||||
zip -r -q "$PLUGIN_NAME.zip" "$PLUGIN_NAME/"
|
||||
cd ..
|
||||
(cd "$DIST_DIR" && zip -r -q "$PLUGIN_NAME.zip" "$PLUGIN_NAME/")
|
||||
|
||||
# Get file sizes
|
||||
DIST_SIZE=$(du -sh "$DIST_DIR/$PLUGIN_NAME" | cut -f1)
|
||||
@ -95,6 +99,8 @@ echo "🚫 Excluded from distribution:"
|
||||
echo " ✗ node_modules/ (development dependencies)"
|
||||
echo " ✗ src/ (source files)"
|
||||
echo " ✗ test/ (test files)"
|
||||
echo " ✗ dev-scripts/ (scripts for development)"
|
||||
echo " ✗ dev-docs/ (development documentation)"
|
||||
echo " ✗ package.json / package-lock.json"
|
||||
echo " ✗ webpack.config.js"
|
||||
echo " ✗ README-DEV.md"
|
||||
522
dev-scripts/i18n-manage.sh
Executable file
522
dev-scripts/i18n-manage.sh
Executable file
@ -0,0 +1,522 @@
|
||||
#!/bin/bash
|
||||
|
||||
################################################################################
|
||||
# Swiss Football Matchdata - i18n Management Script
|
||||
#
|
||||
# Complete i18n workflow using wp-cli:
|
||||
# 1. Extract strings from PHP/JS to .pot file
|
||||
# 2. Translate .pot to specified languages
|
||||
# 3. Generate .mo files from .po files
|
||||
# 4. Clean up regional language variants
|
||||
#
|
||||
# Usage:
|
||||
# ./dev-scripts/i18n-manage.sh [extract|translate|build|clean|all]
|
||||
#
|
||||
# Commands:
|
||||
# extract - Extract i18n strings and generate .pot file
|
||||
# translate - Generate .po files for all languages (requires jq)
|
||||
# build - Generate .mo files from .po files
|
||||
# clean - Remove regional language variants
|
||||
# all - Run all steps: extract → translate → build → clean
|
||||
#
|
||||
# Configuration:
|
||||
# Modify the CONFIG section below to match your environment
|
||||
#
|
||||
# Dependencies:
|
||||
# - wp-cli (via docker compose when WP_CLI_WRAPPER is used)
|
||||
# - jq (for JSON translation data handling)
|
||||
# - msgfmt (for generating .mo files)
|
||||
#
|
||||
################################################################################
|
||||
|
||||
set -e
|
||||
|
||||
################################################################################
|
||||
# CONFIGURATION - Modify these values for your environment
|
||||
################################################################################
|
||||
|
||||
# WordPress CLI command wrapper (for docker environments)
|
||||
# Use local wp-cli: WP_CLI_CMD="wp"
|
||||
# Use docker compose with working directory: WP_CLI_CMD="docker compose -f ~/Development/wordpress-dev/wp-dev-compose.yml run -w /var/www/html/wp-content/plugins/swiss-football-matchdata --rm wp-cli"
|
||||
WP_CLI_CMD="docker compose -f ~/Development/wordpress-dev/wp-dev-compose.yml run -w /var/www/html/wp-content/plugins/swiss-football-matchdata --rm wp-cli"
|
||||
|
||||
# Plugin slug
|
||||
PLUGIN_SLUG="swi_foot_matchdata"
|
||||
|
||||
# Text domain (must match in plugin)
|
||||
TEXT_DOMAIN="swi_foot_matchdata"
|
||||
|
||||
# Languages to support: language_code:language_name
|
||||
# Format: LANGUAGE_CODE|LANGUAGE_NAME (space-separated for compatibility)
|
||||
LANGUAGES="de_DE:German fr_FR:French it_IT:Italian en_US:English"
|
||||
|
||||
# Regional variants to clean up (remove if they exist)
|
||||
# These will be removed to keep only the main language codes
|
||||
REGIONAL_VARIANTS=(
|
||||
"de_AT" # Austrian German
|
||||
"de_CH" # Swiss German
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# COLORS & OUTPUT
|
||||
################################################################################
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✓${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}✗${NC} $1"
|
||||
}
|
||||
|
||||
log_section() {
|
||||
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${CYAN} $1${NC}"
|
||||
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# UTILITY FUNCTIONS
|
||||
################################################################################
|
||||
|
||||
check_dependencies() {
|
||||
local missing_deps=0
|
||||
|
||||
# Check for wp-cli
|
||||
if ! eval "$WP_CLI_CMD --version" &>/dev/null; then
|
||||
log_error "wp-cli not found or not accessible via: $WP_CLI_CMD"
|
||||
missing_deps=1
|
||||
fi
|
||||
|
||||
# Check for msgfmt (gettext tools)
|
||||
if ! command -v msgfmt &>/dev/null; then
|
||||
log_error "msgfmt not found. Install gettext tools:"
|
||||
echo " macOS: brew install gettext"
|
||||
echo " Ubuntu: sudo apt-get install gettext"
|
||||
missing_deps=1
|
||||
fi
|
||||
|
||||
# Check for jq (optional but recommended)
|
||||
if ! command -v jq &>/dev/null; then
|
||||
log_warning "jq not installed (optional). Install for better translation support:"
|
||||
echo " macOS: brew install jq"
|
||||
echo " Ubuntu: sudo apt-get install jq"
|
||||
fi
|
||||
|
||||
if [[ $missing_deps -eq 1 ]]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
get_language_name() {
|
||||
local code=$1
|
||||
for pair in $LANGUAGES; do
|
||||
local lang_code="${pair%:*}"
|
||||
local lang_name="${pair#*:}"
|
||||
if [[ "$lang_code" == "$code" ]]; then
|
||||
echo "$lang_name"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "Unknown"
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# EXTRACTION
|
||||
################################################################################
|
||||
|
||||
extract_strings() {
|
||||
log_section "Extracting i18n Strings"
|
||||
|
||||
local pot_file="languages/${PLUGIN_SLUG}.pot"
|
||||
local exclude_patterns="node_modules,vendor,assets/build,wp-json"
|
||||
|
||||
log_info "Extracting strings to: $pot_file"
|
||||
|
||||
# Create languages directory if it doesn't exist
|
||||
mkdir -p languages
|
||||
|
||||
# Use wp-cli to extract strings
|
||||
# Note: wp-cli automatically handles:
|
||||
# - Translators comments (/* translators: ... */)
|
||||
# - Multiple placeholders with ordered syntax (%1$d, %2$s, etc.)
|
||||
# - WordPress i18n functions (__, _e, _x, _n, etc.)
|
||||
# Syntax: wp i18n make-pot <source-directory> <output-file-path>
|
||||
log_info "Running wp-cli extraction command..."
|
||||
log_info " Working directory (Docker): /var/www/html/wp-content/plugins/swiss-football-matchdata"
|
||||
log_info " Plugin directory (local): $(pwd)"
|
||||
|
||||
# Execute the extraction with proper output handling
|
||||
# WP_CLI_CMD is already configured with -w flag, so . refers to the plugin directory
|
||||
local extract_output
|
||||
extract_output=$(eval "$WP_CLI_CMD i18n make-pot --exclude=$exclude_patterns --domain=$TEXT_DOMAIN . languages/${PLUGIN_SLUG}.pot 2>&1" || echo "FAILED")
|
||||
|
||||
# Small delay to ensure file is written
|
||||
sleep 1
|
||||
|
||||
# Check if extraction was successful by verifying the file exists
|
||||
local retries=3
|
||||
local pot_exists=0
|
||||
|
||||
while [[ $retries -gt 0 ]] && [[ $pot_exists -eq 0 ]]; do
|
||||
if [[ -f "$pot_file" ]]; then
|
||||
pot_exists=1
|
||||
break
|
||||
fi
|
||||
|
||||
((retries--))
|
||||
if [[ $retries -gt 0 ]]; then
|
||||
log_warning "Pot file not found yet, retrying... ($retries attempts left)"
|
||||
sleep 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $pot_exists -eq 1 ]]; then
|
||||
log_success "Extracted strings to: $pot_file"
|
||||
|
||||
# Show statistics
|
||||
local string_count=$(grep -c "^msgid \"" "$pot_file" 2>/dev/null || echo "0")
|
||||
string_count=$(echo "$string_count" | tr -d ' \n')
|
||||
log_info "Total strings found: $string_count"
|
||||
|
||||
# Check for obsolete entries
|
||||
local obsolete_count=$(grep -c "^#~" "$pot_file" 2>/dev/null || echo "0")
|
||||
obsolete_count=$(echo "$obsolete_count" | tr -d ' \n')
|
||||
if [[ $obsolete_count -gt 0 ]]; then
|
||||
log_warning "Found $obsolete_count obsolete entries (marked with #~)"
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to generate .pot file"
|
||||
log_error "wp-cli output: $extract_output"
|
||||
log_error "Checked path: $pot_file (local filesystem)"
|
||||
log_info "Directory context:"
|
||||
log_info " • Local plugin dir: $(pwd)"
|
||||
log_info " • Docker plugin dir: /var/www/html/wp-content/plugins/swiss-football-matchdata"
|
||||
log_info " • Expected output (local): $(pwd)/languages/${PLUGIN_SLUG}.pot"
|
||||
log_info ""
|
||||
log_info "If the file exists in Docker but not locally, verify:"
|
||||
log_info " 1. Docker volume mounting is correct"
|
||||
log_info " 2. Run: docker compose -f ~/Development/wordpress-dev/wp-dev-compose.yml exec wp-cli ls -la /var/www/html/wp-content/plugins/swiss-football-matchdata/"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# TRANSLATION
|
||||
################################################################################
|
||||
|
||||
translate_strings() {
|
||||
log_section "Generating .po files from .pot"
|
||||
|
||||
local pot_file="languages/${PLUGIN_SLUG}.pot"
|
||||
|
||||
if [[ ! -f "$pot_file" ]]; then
|
||||
log_error ".pot file not found: $pot_file"
|
||||
log_info "Run 'extract' command first"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local created_count=0
|
||||
|
||||
for pair in $LANGUAGES; do
|
||||
local lang_code="${pair%:*}"
|
||||
local lang_name="${pair#*:}"
|
||||
local po_file="languages/${PLUGIN_SLUG}-${lang_code}.po"
|
||||
|
||||
log_info "Processing: $lang_name ($lang_code)"
|
||||
|
||||
if [[ -f "$po_file" ]]; then
|
||||
# Update existing .po file
|
||||
log_info " Updating existing .po file..."
|
||||
eval "$WP_CLI_CMD i18n update-po languages/${PLUGIN_SLUG}.pot languages/${PLUGIN_SLUG}-${lang_code}.po 2>/dev/null" || true
|
||||
log_success " Updated: $po_file"
|
||||
else
|
||||
# Create new .po file from .pot
|
||||
log_info " Creating new .po file..."
|
||||
cp "$pot_file" "$po_file"
|
||||
|
||||
# Update language headers in .po file
|
||||
sed -i.bak "s/Language: /Language: ${lang_code}/" "$po_file"
|
||||
rm -f "${po_file}.bak"
|
||||
|
||||
log_success " Created: $po_file"
|
||||
((created_count++))
|
||||
fi
|
||||
done
|
||||
|
||||
log_success "Created/Updated $created_count translation files"
|
||||
return 0
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# BUILD .MO FILES
|
||||
################################################################################
|
||||
|
||||
build_mo_files() {
|
||||
log_section "Generating .mo files"
|
||||
|
||||
local compiled_count=0
|
||||
local errors=0
|
||||
|
||||
for pair in $LANGUAGES; do
|
||||
local lang_code="${pair%:*}"
|
||||
local lang_name="${pair#*:}"
|
||||
local po_file="languages/${PLUGIN_SLUG}-${lang_code}.po"
|
||||
local mo_file="languages/${PLUGIN_SLUG}-${lang_code}.mo"
|
||||
|
||||
if [[ -f "$po_file" ]]; then
|
||||
log_info "Compiling: $lang_name ($lang_code)"
|
||||
|
||||
if msgfmt -o "$mo_file" "$po_file" 2>/dev/null; then
|
||||
log_success " Generated: $mo_file"
|
||||
((compiled_count++))
|
||||
else
|
||||
log_error " Failed to compile: $po_file"
|
||||
((errors++))
|
||||
fi
|
||||
else
|
||||
log_warning " .po file not found: $po_file"
|
||||
fi
|
||||
done
|
||||
|
||||
log_success "Compiled $compiled_count .mo files"
|
||||
|
||||
if [[ $errors -gt 0 ]]; then
|
||||
log_warning "Encountered $errors errors during compilation"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# CLEANUP
|
||||
################################################################################
|
||||
|
||||
cleanup_regional_variants() {
|
||||
log_section "Cleaning up regional language variants"
|
||||
|
||||
local removed_count=0
|
||||
|
||||
for variant in "${REGIONAL_VARIANTS[@]}"; do
|
||||
# Check for .po files
|
||||
local po_file="languages/${PLUGIN_SLUG}-${variant}.po"
|
||||
if [[ -f "$po_file" ]]; then
|
||||
log_info "Removing: $po_file"
|
||||
rm -f "$po_file"
|
||||
((removed_count++))
|
||||
fi
|
||||
|
||||
# Check for .mo files
|
||||
local mo_file="languages/${PLUGIN_SLUG}-${variant}.mo"
|
||||
if [[ -f "$mo_file" ]]; then
|
||||
log_info "Removing: $mo_file"
|
||||
rm -f "$mo_file"
|
||||
((removed_count++))
|
||||
fi
|
||||
|
||||
# Also check for backup files created by sed
|
||||
local po_backup="${po_file}.bak"
|
||||
if [[ -f "$po_backup" ]]; then
|
||||
rm -f "$po_backup"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $removed_count -eq 0 ]]; then
|
||||
log_info "No regional variants found to remove"
|
||||
else
|
||||
log_success "Removed $removed_count regional variant files"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# DISPLAY SUMMARY
|
||||
################################################################################
|
||||
|
||||
show_summary() {
|
||||
log_section "i18n Management Summary"
|
||||
|
||||
echo -e "${YELLOW}📁 Language Files:${NC}\n"
|
||||
|
||||
for pair in $LANGUAGES; do
|
||||
local lang_code="${pair%:*}"
|
||||
local lang_name="${pair#*:}"
|
||||
local po_file="languages/${PLUGIN_SLUG}-${lang_code}.po"
|
||||
local mo_file="languages/${PLUGIN_SLUG}-${lang_code}.mo"
|
||||
|
||||
printf " %-20s %-20s " "$lang_code" "$lang_name"
|
||||
|
||||
if [[ -f "$po_file" ]] && [[ -f "$mo_file" ]]; then
|
||||
local po_size=$(du -h "$po_file" | cut -f1)
|
||||
local mo_size=$(du -h "$mo_file" | cut -f1)
|
||||
echo -e "${GREEN}✓${NC} PO: $po_size, MO: $mo_size"
|
||||
elif [[ -f "$po_file" ]]; then
|
||||
local po_size=$(du -h "$po_file" | cut -f1)
|
||||
echo -e "${YELLOW}⚠${NC} PO: $po_size (no .mo)"
|
||||
else
|
||||
echo -e "${RED}✗${NC} Missing files"
|
||||
fi
|
||||
done
|
||||
|
||||
local lang_count=$(echo "$LANGUAGES" | wc -w)
|
||||
|
||||
echo -e "\n${YELLOW}Configuration:${NC}\n"
|
||||
echo " Plugin slug: $PLUGIN_SLUG"
|
||||
echo " Text domain: $TEXT_DOMAIN"
|
||||
echo " Languages: $lang_count"
|
||||
echo " Languages dir: languages/"
|
||||
|
||||
echo -e "\n${YELLOW}Next Steps:${NC}\n"
|
||||
echo " • Verify .po files contain all necessary translations"
|
||||
echo " • Update empty translation entries in .po files"
|
||||
echo " • Re-run 'build' to regenerate .mo files after translations"
|
||||
echo ""
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# HELP
|
||||
################################################################################
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Usage: ./dev-scripts/i18n-manage.sh [COMMAND]
|
||||
|
||||
COMMANDS:
|
||||
extract Extract i18n strings from PHP/JS files to .pot
|
||||
translate Generate .po files for all configured languages
|
||||
build Compile .po files to .mo files
|
||||
clean Remove regional language variants
|
||||
all Run all steps: extract → translate → build → clean
|
||||
help Show this help message
|
||||
|
||||
EXAMPLES:
|
||||
# Extract strings and generate translations
|
||||
./dev-scripts/i18n-manage.sh extract
|
||||
|
||||
# Generate .po files for translation
|
||||
./dev-scripts/i18n-manage.sh translate
|
||||
|
||||
# Compile .po to .mo files
|
||||
./dev-scripts/i18n-manage.sh build
|
||||
|
||||
# Run complete workflow
|
||||
./dev-scripts/i18n-manage.sh all
|
||||
|
||||
# Clean up regional variants
|
||||
./dev-scripts/i18n-manage.sh clean
|
||||
|
||||
CONFIGURATION:
|
||||
Edit the script's CONFIG section to adjust:
|
||||
- WP_CLI_CMD: WordPress CLI command wrapper
|
||||
- LANGUAGES: Supported language codes
|
||||
- REGIONAL_VARIANTS: Language variants to remove
|
||||
|
||||
TRANSLATORS COMMENTS:
|
||||
In PHP code, add translators comments above i18n functions:
|
||||
|
||||
/* translators: %s is the error message from the API */
|
||||
__('Error loading teams: %s', 'swi_foot_matchdata')
|
||||
|
||||
For multiple placeholders, use ordered syntax:
|
||||
|
||||
/* translators: %1$d is count, %2$d is duration in seconds */
|
||||
__('Currently caching %1$d records with %2$d second duration.', 'swi_foot_matchdata')
|
||||
|
||||
SUPPORTED LANGUAGES (default):
|
||||
de_DE - German
|
||||
fr_FR - French
|
||||
it_IT - Italian
|
||||
en_US - English (US)
|
||||
|
||||
DEPENDENCIES:
|
||||
- wp-cli (WordPress CLI)
|
||||
- msgfmt (gettext tools)
|
||||
- jq (optional, for translation management)
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# MAIN
|
||||
################################################################################
|
||||
|
||||
main() {
|
||||
local command="${1:-all}"
|
||||
|
||||
# Show banner
|
||||
echo -e "${CYAN}╔════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN}║ Swiss Football Matchdata - i18n Management ║${NC}"
|
||||
echo -e "${CYAN}╚════════════════════════════════════════════════╝${NC}\n"
|
||||
|
||||
# Check dependencies
|
||||
log_info "Checking dependencies..."
|
||||
if ! check_dependencies; then
|
||||
log_error "Missing required dependencies. Please install and try again."
|
||||
exit 1
|
||||
fi
|
||||
log_success "All dependencies available\n"
|
||||
|
||||
# Execute command
|
||||
case "$command" in
|
||||
extract)
|
||||
extract_strings
|
||||
;;
|
||||
translate)
|
||||
translate_strings
|
||||
show_summary
|
||||
;;
|
||||
build)
|
||||
build_mo_files
|
||||
show_summary
|
||||
;;
|
||||
clean)
|
||||
cleanup_regional_variants
|
||||
;;
|
||||
all)
|
||||
extract_strings && \
|
||||
translate_strings && \
|
||||
build_mo_files && \
|
||||
cleanup_regional_variants && \
|
||||
show_summary
|
||||
;;
|
||||
help|--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown command: $command"
|
||||
echo ""
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
local exit_code=$?
|
||||
if [[ $exit_code -eq 0 ]]; then
|
||||
log_success "\nTask completed successfully!"
|
||||
else
|
||||
log_error "\nTask failed with exit code $exit_code"
|
||||
fi
|
||||
|
||||
exit $exit_code
|
||||
}
|
||||
|
||||
# Run main function with all arguments
|
||||
main "$@"
|
||||
@ -31,6 +31,7 @@ class Swi_Foot_Admin
|
||||
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');
|
||||
register_setting('swi_foot_settings', 'swi_foot_api_language');
|
||||
|
||||
add_settings_section(
|
||||
'swi_foot_api_section',
|
||||
@ -44,6 +45,7 @@ class Swi_Foot_Admin
|
||||
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_field('swi_foot_api_language', __('API Response Language', 'swi_foot_matchdata'), array($this, 'language_render'), 'swi_foot_settings', 'swi_foot_api_section');
|
||||
|
||||
add_settings_section(
|
||||
'swi_foot_cache_section',
|
||||
@ -107,6 +109,17 @@ class Swi_Foot_Admin
|
||||
echo '<p class="description">' . __('How long to cache match data in seconds (10-300)', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function language_render()
|
||||
{
|
||||
$language = get_option('swi_foot_api_language', '1');
|
||||
echo '<select name="swi_foot_api_language">';
|
||||
echo '<option value="1" ' . selected($language, '1', false) . '>' . __('German', 'swi_foot_matchdata') . '</option>';
|
||||
echo '<option value="2" ' . selected($language, '2', false) . '>' . __('French', 'swi_foot_matchdata') . '</option>';
|
||||
echo '<option value="3" ' . selected($language, '3', false) . '>' . __('Italian', 'swi_foot_matchdata') . '</option>';
|
||||
echo '</select>';
|
||||
echo '<p class="description">' . __('Select the language for API responses (German, French, or Italian)', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function admin_scripts($hook)
|
||||
{
|
||||
if ($hook === 'settings_page_swiss-football-matchdata') {
|
||||
@ -248,23 +261,21 @@ class Swi_Foot_Admin
|
||||
<div class="wrap">
|
||||
<h1><?php _e('Swiss Football Matchdata Settings', 'swi_foot_matchdata'); ?></h1>
|
||||
|
||||
<form action="options.php" method="post">
|
||||
<form action="options.php" method="post" id="swi-foot-settings-form">
|
||||
<?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'); ?>
|
||||
<div class="swi-foot-form-actions">
|
||||
<button type="submit" name="swi_foot_action" value="save_and_test" class="button button-primary">
|
||||
<?php _e('Save and Test', 'swi_foot_matchdata'); ?>
|
||||
</button>
|
||||
<span id="connection-status"></span>
|
||||
</p>
|
||||
</div>
|
||||
<button type="submit" name="swi_foot_action" value="save_only" class="button">
|
||||
<?php _e('Save without Test', 'swi_foot_matchdata'); ?>
|
||||
</button>
|
||||
</div>
|
||||
<span id="connection-status"></span>
|
||||
</form>
|
||||
|
||||
<div class="swi-foot-admin-section">
|
||||
<h2><?php _e('Team Management', 'swi_foot_matchdata'); ?></h2>
|
||||
@ -325,7 +336,11 @@ class Swi_Foot_Admin
|
||||
|
||||
if (is_wp_error($teams)) {
|
||||
echo '<p class="notice notice-error">' .
|
||||
sprintf(__('Error loading teams: %s', 'swi_foot_matchdata'), $teams->get_error_message()) .
|
||||
sprintf(
|
||||
/* translators: %s is the error message from the API */
|
||||
__('Error loading teams: %s', 'swi_foot_matchdata'),
|
||||
$teams->get_error_message()
|
||||
) .
|
||||
'</p>';
|
||||
return;
|
||||
}
|
||||
@ -357,7 +372,12 @@ class Swi_Foot_Admin
|
||||
$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>';
|
||||
echo '<p>' . sprintf(
|
||||
/* translators: %1$d is the number of cached match records, %2$d is the cache duration in seconds */
|
||||
__('Currently caching %1$d match records with %2$d second cache duration.', 'swi_foot_matchdata'),
|
||||
$cache_count,
|
||||
$cache_duration
|
||||
) . '</p>';
|
||||
|
||||
if ($cache_count > 0) {
|
||||
$timestamps = array();
|
||||
@ -460,7 +480,5 @@ class Swi_Foot_Admin
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
?>
|
||||
@ -9,6 +9,7 @@ class Swi_Foot_API
|
||||
private $verein_id;
|
||||
private $season_id;
|
||||
private $cache_duration;
|
||||
private $language;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -18,6 +19,7 @@ class Swi_Foot_API
|
||||
$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);
|
||||
$this->language = get_option('swi_foot_api_language', '1');
|
||||
|
||||
// AJAX actions were migrated to REST endpoints (see includes/class-swi-foot-rest.php)
|
||||
|
||||
@ -176,6 +178,7 @@ class Swi_Foot_API
|
||||
$response = wp_remote_get($url, array(
|
||||
'headers' => array(
|
||||
'X-User-Token' => $token,
|
||||
'X-User-Language' => $this->language,
|
||||
'Content-Type' => 'application/json'
|
||||
),
|
||||
'timeout' => 30
|
||||
@ -204,7 +207,13 @@ class Swi_Foot_API
|
||||
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);
|
||||
|
||||
// Handle 406 Not Acceptable - data not yet available
|
||||
if ($response_code === 406) {
|
||||
return new WP_Error('data_not_available', 'Data not yet available', array('status_code' => 406));
|
||||
}
|
||||
|
||||
return new WP_Error('api_error', 'API request failed with code ' . $response_code, array('status_code' => $response_code));
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
@ -348,6 +357,11 @@ class Swi_Foot_API
|
||||
return $this->api_request('/api/match/' . $match_id . '/events');
|
||||
}
|
||||
|
||||
public function get_match_referees($match_id)
|
||||
{
|
||||
return $this->api_request('/api/match/' . $match_id . '/referees');
|
||||
}
|
||||
|
||||
public function get_team_picture($team_id)
|
||||
{
|
||||
// Special handling for team picture endpoint which returns 200 with data or 204 No Content
|
||||
@ -364,6 +378,7 @@ class Swi_Foot_API
|
||||
$response = wp_remote_get($url, array(
|
||||
'headers' => array(
|
||||
'X-User-Token' => $token,
|
||||
'X-User-Language' => $this->language,
|
||||
'Content-Type' => 'application/json'
|
||||
),
|
||||
'timeout' => 30
|
||||
@ -410,9 +425,9 @@ class Swi_Foot_API
|
||||
return $body;
|
||||
}
|
||||
|
||||
public function get_commons_ids()
|
||||
public function get_common_ids($params = array())
|
||||
{
|
||||
return $this->api_request('/api/commons/ids');
|
||||
return $this->api_request('/api/common/ids', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -441,17 +456,54 @@ class Swi_Foot_API
|
||||
*/
|
||||
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;
|
||||
// First, check if credentials are configured
|
||||
if (empty($this->username) || empty($this->password)) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'error' => 'API credentials not configured',
|
||||
'details' => 'Please configure API Username and Password in Settings → Swiss Football'
|
||||
);
|
||||
}
|
||||
|
||||
// Check if Verein ID is configured
|
||||
if (empty($this->verein_id)) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'error' => 'Verein ID (Club ID) not configured',
|
||||
'details' => 'Please configure Verein ID in Settings → Swiss Football'
|
||||
);
|
||||
}
|
||||
|
||||
// Verify we got a response (array means success)
|
||||
return is_array($result);
|
||||
// Test connection using the /api/common/ids endpoint with required parameters
|
||||
$result = $this->get_common_ids(array(
|
||||
'ClubId' => $this->verein_id,
|
||||
'Language' => 1 // 1 = German
|
||||
));
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
$error_msg = $result->get_error_message();
|
||||
error_log('Swiss Football API: Connection test failed - ' . $error_msg);
|
||||
|
||||
return array(
|
||||
'success' => false,
|
||||
'error' => 'API connection failed',
|
||||
'details' => $error_msg
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_array($result)) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'error' => 'Invalid API response',
|
||||
'details' => 'The API returned unexpected data format'
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'error' => null,
|
||||
'details' => 'Connection successful!'
|
||||
);
|
||||
}
|
||||
|
||||
public function get_current_match($team_id)
|
||||
|
||||
@ -17,6 +17,23 @@ class Swi_Foot_Blocks
|
||||
|
||||
public function register_blocks()
|
||||
{
|
||||
// Register custom block category for Swiss Football blocks
|
||||
add_filter('block_categories_all', function($categories) {
|
||||
// Check if category already exists
|
||||
foreach ($categories as $cat) {
|
||||
if ($cat['slug'] === 'swi-football') {
|
||||
return $categories; // Already registered
|
||||
}
|
||||
}
|
||||
// Add custom category with translated label
|
||||
$categories[] = array(
|
||||
'slug' => 'swi-football',
|
||||
'title' => __('Swiss Football', 'swi_foot_matchdata'),
|
||||
'icon' => 'soccer'
|
||||
);
|
||||
return $categories;
|
||||
});
|
||||
|
||||
// Register blocks from metadata (block.json) and provide server-side render callbacks
|
||||
$base = dirname(__DIR__) . '/blocks';
|
||||
|
||||
@ -90,14 +107,6 @@ class Swi_Foot_Blocks
|
||||
) );
|
||||
}
|
||||
|
||||
// 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' ),
|
||||
@ -113,6 +122,22 @@ class Swi_Foot_Blocks
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
if ( file_exists( $base . '/match-bench/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/match-bench', array(
|
||||
'render_callback' => array( $this, 'render_match_bench_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
if ( file_exists( $base . '/match-referees/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/match-referees', array(
|
||||
'render_callback' => array( $this, 'render_match_referees_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
public function enqueue_scripts()
|
||||
@ -223,7 +248,11 @@ class Swi_Foot_Blocks
|
||||
|
||||
if (is_wp_error($standings)) {
|
||||
return '<div class="swi-foot-error">' .
|
||||
sprintf(__('Error loading standings: %s', 'swi_foot_matchdata'), $standings->get_error_message()) .
|
||||
sprintf(
|
||||
/* translators: %s is the error message from the API */
|
||||
__('Error loading standings: %s', 'swi_foot_matchdata'),
|
||||
$standings->get_error_message()
|
||||
) .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
@ -281,6 +310,7 @@ class Swi_Foot_Blocks
|
||||
// Team must come from context only
|
||||
$team_id = $ctx['team_id'] ?? null;
|
||||
$limit = isset($attributes['limit']) ? $attributes['limit'] : 5;
|
||||
$match_filter = isset($attributes['matchFilter']) ? $attributes['matchFilter'] : 'all';
|
||||
|
||||
if (empty($team_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Schedule: No team provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
@ -291,18 +321,47 @@ class Swi_Foot_Blocks
|
||||
|
||||
if (is_wp_error($events)) {
|
||||
return '<div class="swi-foot-error">' .
|
||||
sprintf(__('Error loading schedule: %s', 'swi_foot_matchdata'), $events->get_error_message()) .
|
||||
sprintf(
|
||||
/* translators: %s is the error message from the API */
|
||||
__('Error loading schedule: %s', 'swi_foot_matchdata'),
|
||||
$events->get_error_message()
|
||||
) .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// Filter upcoming events and limit results
|
||||
// Filter upcoming events by match type (home/away/all) and limit results
|
||||
$upcoming_events = array();
|
||||
if (is_array($events)) {
|
||||
foreach ($events as $event) {
|
||||
// Check if match is in the future
|
||||
if (strtotime($event['matchDate']) >= time()) {
|
||||
$upcoming_events[] = $event;
|
||||
if (count($upcoming_events) >= $limit) {
|
||||
break;
|
||||
// Apply match filter (home/away/all)
|
||||
$match_should_be_included = true;
|
||||
|
||||
if ($match_filter !== 'all') {
|
||||
// Determine if this is a home or away match
|
||||
// teamAId is home team, teamBId is away team
|
||||
$is_home_match = isset($event['teamAId']) && $event['teamAId'] == $team_id;
|
||||
$is_away_match = isset($event['teamBId']) && $event['teamBId'] == $team_id;
|
||||
|
||||
// If team IDs aren't available, try to infer from team names
|
||||
if (!isset($event['teamAId']) || !isset($event['teamBId'])) {
|
||||
// This is a fallback - not ideal but works if IDs aren't present
|
||||
$is_home_match = true; // Default to home
|
||||
}
|
||||
|
||||
if ($match_filter === 'home' && !$is_home_match) {
|
||||
$match_should_be_included = false;
|
||||
} elseif ($match_filter === 'away' && !$is_away_match) {
|
||||
$match_should_be_included = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($match_should_be_included) {
|
||||
$upcoming_events[] = $event;
|
||||
if (count($upcoming_events) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -400,7 +459,8 @@ class Swi_Foot_Blocks
|
||||
// Match must come from context
|
||||
$match_id = $ctx['match_id'] ?? '';
|
||||
$side = $attributes['side'] ?? 'home';
|
||||
$with_bench = $attributes['withBench'] ? 'true' : 'false';
|
||||
$show_starting_squad = isset($attributes['showStartingSquad']) ? $attributes['showStartingSquad'] : true;
|
||||
$show_bench = isset($attributes['showBench']) ? $attributes['showBench'] : false;
|
||||
|
||||
if (empty($match_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Match Roster: No match provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
@ -408,8 +468,11 @@ class Swi_Foot_Blocks
|
||||
|
||||
// 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"';
|
||||
if ($show_starting_squad) {
|
||||
$shortcode .= ' show_starting="true"';
|
||||
}
|
||||
if ($show_bench) {
|
||||
$shortcode .= ' show_bench="true"';
|
||||
}
|
||||
$shortcode .= ']';
|
||||
|
||||
@ -446,6 +509,61 @@ class Swi_Foot_Blocks
|
||||
return do_shortcode($shortcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render match bench block
|
||||
* Gets match from container context, displays team staff/substitutes
|
||||
*/
|
||||
public function render_match_bench_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';
|
||||
|
||||
if (empty($match_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Match Bench: No match provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
// Build shortcode using match from context and side attribute
|
||||
$shortcode = '[swi_foot_bench match_id="' . esc_attr($match_id) . '" side="' . esc_attr($side) . '"]';
|
||||
|
||||
// Process and return the shortcode
|
||||
return do_shortcode($shortcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render match referees block
|
||||
* Gets match from container context, displays match officials/referees
|
||||
*/
|
||||
public function render_match_referees_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'] ?? '';
|
||||
|
||||
if (empty($match_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Match Referees: No match provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
// Build shortcode using match from context
|
||||
$shortcode = '[swi_foot_referees match_id="' . esc_attr($match_id) . '"]';
|
||||
|
||||
// Process and return the shortcode
|
||||
return do_shortcode($shortcode);
|
||||
}
|
||||
|
||||
private function render_team_selector($block_type)
|
||||
{
|
||||
$api = new Swi_Foot_API();
|
||||
|
||||
@ -84,18 +84,10 @@ class Swi_Foot_REST {
|
||||
)
|
||||
));
|
||||
|
||||
// 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(
|
||||
register_rest_route('swi-foot/v1', '/block-status/(?P<match_id>\d+)/(?P<endpoint>[a-z_]+)', 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);
|
||||
}
|
||||
)
|
||||
)
|
||||
'callback' => array($this, 'check_block_status'),
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
// Single match details: /wp-json/swi-foot/v1/match/<match_id>
|
||||
@ -129,9 +121,9 @@ class Swi_Foot_REST {
|
||||
)
|
||||
));
|
||||
|
||||
register_rest_route('swi-foot/v1', '/commons-ids', array(
|
||||
register_rest_route('swi-foot/v1', '/common-ids', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array($this, 'get_commons_ids'),
|
||||
'callback' => array($this, 'get_common_ids'),
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
@ -210,7 +202,7 @@ class Swi_Foot_REST {
|
||||
return rest_ensure_response($normalized);
|
||||
}
|
||||
|
||||
public function get_commons_ids($request) {
|
||||
public function get_common_ids($request) {
|
||||
$api = new Swi_Foot_API();
|
||||
$commons = $api->get_commons_ids();
|
||||
if (is_wp_error($commons)) {
|
||||
@ -370,12 +362,77 @@ class Swi_Foot_REST {
|
||||
public function admin_test_connection($request) {
|
||||
$api = new Swi_Foot_API();
|
||||
if (method_exists($api, 'test_connection')) {
|
||||
$ok = $api->test_connection();
|
||||
$result = $api->test_connection();
|
||||
} else {
|
||||
$ok = false;
|
||||
$result = array(
|
||||
'success' => false,
|
||||
'error' => 'test_connection method not found',
|
||||
'details' => 'Plugin may be incorrectly installed'
|
||||
);
|
||||
}
|
||||
|
||||
// Return response with full error details
|
||||
if (is_array($result) && isset($result['success'])) {
|
||||
if ($result['success']) {
|
||||
return rest_ensure_response(array(
|
||||
'success' => true,
|
||||
'message' => isset($result['details']) ? $result['details'] : 'Connection successful!'
|
||||
));
|
||||
} else {
|
||||
return new WP_REST_Response(array(
|
||||
'success' => false,
|
||||
'error' => isset($result['error']) ? $result['error'] : 'Connection failed',
|
||||
'details' => isset($result['details']) ? $result['details'] : 'Unknown error'
|
||||
), 400);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for unexpected response format
|
||||
return new WP_REST_Response(array(
|
||||
'success' => false,
|
||||
'error' => 'Unexpected response from API test',
|
||||
'details' => 'Unable to determine connection status'
|
||||
), 400);
|
||||
}
|
||||
|
||||
public function check_block_status($request) {
|
||||
$match_id = $request->get_param('match_id');
|
||||
$endpoint = $request->get_param('endpoint');
|
||||
|
||||
if (empty($match_id) || empty($endpoint)) {
|
||||
return rest_ensure_response(array('status' => 'error', 'message' => 'Missing parameters'));
|
||||
}
|
||||
|
||||
$api = new Swi_Foot_API();
|
||||
|
||||
// Map endpoint names to API methods
|
||||
$method_map = array(
|
||||
'roster' => 'get_match_players',
|
||||
'bench' => 'get_match_bench',
|
||||
'referees' => 'get_match_referees',
|
||||
'events' => 'get_match_events'
|
||||
);
|
||||
|
||||
if (!isset($method_map[$endpoint])) {
|
||||
return rest_ensure_response(array('status' => 'error', 'message' => __('Unknown endpoint', 'swi_foot_matchdata')));
|
||||
}
|
||||
|
||||
$method = $method_map[$endpoint];
|
||||
$result = $api->$method($match_id);
|
||||
|
||||
// Determine status based on result
|
||||
if (is_wp_error($result)) {
|
||||
$code = $result->get_error_code();
|
||||
if ($code === 'data_not_available') {
|
||||
return rest_ensure_response(array('status' => 'warning', 'message' => __('Data not yet available', 'swi_foot_matchdata')));
|
||||
} else {
|
||||
return rest_ensure_response(array('status' => 'error', 'message' => __('No data available', 'swi_foot_matchdata')));
|
||||
}
|
||||
} elseif (empty($result)) {
|
||||
return rest_ensure_response(array('status' => 'error', 'message' => __('No data available', 'swi_foot_matchdata')));
|
||||
} else {
|
||||
return rest_ensure_response(array('status' => 'success', 'message' => __('Data available', 'swi_foot_matchdata')));
|
||||
}
|
||||
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) {
|
||||
|
||||
@ -22,6 +22,8 @@ class Swi_Foot_Shortcodes
|
||||
add_shortcode('swi_foot_match_round', array($this, 'match_round_shortcode'));
|
||||
add_shortcode('swi_foot_standings', array($this, 'standings_shortcode'));
|
||||
add_shortcode('swi_foot_roster', array($this, 'roster_shortcode'));
|
||||
add_shortcode('swi_foot_bench', array($this, 'bench_shortcode'));
|
||||
add_shortcode('swi_foot_referees', array($this, 'referees_shortcode'));
|
||||
add_shortcode('swi_foot_events', array($this, 'events_shortcode'));
|
||||
add_shortcode('swi_foot_match_data', array($this, 'match_data_shortcode'));
|
||||
add_shortcode('swi_foot_match_home_team_logo', array($this, 'match_home_team_logo_shortcode'));
|
||||
@ -44,6 +46,26 @@ class Swi_Foot_Shortcodes
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render error message, distinguishing between "data not available" (406) and other errors
|
||||
* @param WP_Error $error The error object
|
||||
* @return string HTML-formatted error message
|
||||
*/
|
||||
private function render_error_message($error)
|
||||
{
|
||||
if (!is_wp_error($error)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Check if this is a "data not yet available" error (HTTP 406)
|
||||
if ($error->get_error_code() === 'data_not_available') {
|
||||
return '<div class="swi-foot-data-warning">⏳ ' . __('Data not yet available', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
// For other errors, show grey "no data available" message
|
||||
return '<div class="swi-foot-data-unavailable">ℹ️ ' . __('No data available', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle internal requests for team logo images
|
||||
* URL format: /?swi_foot_team_logo=team_id&position=home|away|event
|
||||
@ -198,7 +220,7 @@ class Swi_Foot_Shortcodes
|
||||
$match_data = $this->get_match_data($atts);
|
||||
|
||||
if (!$match_data) {
|
||||
return '<span class="swi-foot-error">' . __('Match data not available', 'swi_foot_matchdata') . '</span>';
|
||||
return '<div class="swi-foot-error">' . __('Match data not available', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
@ -388,6 +410,139 @@ class Swi_Foot_Shortcodes
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render match bench (team staff) list
|
||||
* Usage: [swi_foot_bench match_id="12345"]
|
||||
* Lists staff members (name and role) for both home and away teams
|
||||
*/
|
||||
public function bench_shortcode($atts)
|
||||
{
|
||||
$atts = shortcode_atts(array(
|
||||
'match_id' => '',
|
||||
'side' => '' // 'home' or 'away', shows both if not specified
|
||||
), $atts);
|
||||
|
||||
$match_id = $atts['match_id'];
|
||||
if (empty($match_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Match ID required', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
$bench = $this->api->get_match_bench($match_id);
|
||||
|
||||
if (is_wp_error($bench)) {
|
||||
return $this->render_error_message($bench);
|
||||
}
|
||||
|
||||
if (empty($bench)) {
|
||||
return '<div class="swi-foot-notice">' . __('No bench data available', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
// Normalize side parameter
|
||||
$side = strtolower(trim($atts['side']));
|
||||
$show_side_filter = !empty($side) && in_array($side, array('home', 'away'), true);
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="swi-foot-bench-container" data-match-id="<?php echo esc_attr($match_id); ?>">
|
||||
<?php
|
||||
// Group bench players by team
|
||||
$home_staff = array_filter($bench, function($person) {
|
||||
return isset($person['isHomeTeam']) && $person['isHomeTeam'] === true;
|
||||
});
|
||||
$away_staff = array_filter($bench, function($person) {
|
||||
return isset($person['isHomeTeam']) && $person['isHomeTeam'] === false;
|
||||
});
|
||||
|
||||
// If side is specified, filter to only that side
|
||||
if ($show_side_filter) {
|
||||
if ($side === 'home') {
|
||||
$away_staff = array();
|
||||
} else {
|
||||
$home_staff = array();
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php if (!empty($home_staff)): ?>
|
||||
<div class="bench-team bench-home">
|
||||
<?php if (!$show_side_filter): ?>
|
||||
<h3><?php echo esc_html($home_staff[array_key_first($home_staff)]['teamName'] ?? __('Home Team', 'swi_foot_matchdata')); ?></h3>
|
||||
<?php endif; ?>
|
||||
<ul class="bench-list">
|
||||
<?php foreach ($home_staff as $person): ?>
|
||||
<li class="bench-person">
|
||||
<?php echo esc_html($person['personName'] ?? ''); ?>
|
||||
<?php if (!empty($person['roleCategoryName'])): ?>
|
||||
<span class="swi-foot-role">(<?php echo esc_html($person['roleCategoryName']); ?>)</span>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($away_staff)): ?>
|
||||
<div class="bench-team bench-away">
|
||||
<?php if (!$show_side_filter): ?>
|
||||
<h3><?php echo esc_html($away_staff[array_key_first($away_staff)]['teamName'] ?? __('Away Team', 'swi_foot_matchdata')); ?></h3>
|
||||
<?php endif; ?>
|
||||
<ul class="bench-list">
|
||||
<?php foreach ($away_staff as $person): ?>
|
||||
<li class="bench-person">
|
||||
<?php echo esc_html($person['personName'] ?? ''); ?>
|
||||
<?php if (!empty($person['roleCategoryName'])): ?>
|
||||
<span class="swi-foot-role">(<?php echo esc_html($person['roleCategoryName']); ?>)</span>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render referees list for a match
|
||||
* Usage: [swi_foot_referees match_id="12345"]
|
||||
* Lists referees with their role and name formatted as: "Role: Firstname Name" (name in bold)
|
||||
*/
|
||||
public function referees_shortcode($atts)
|
||||
{
|
||||
$atts = shortcode_atts(array(
|
||||
'match_id' => ''
|
||||
), $atts);
|
||||
|
||||
$match_id = $atts['match_id'];
|
||||
if (empty($match_id)) {
|
||||
return '<div class="swi-foot-error">✗ ' . __('Match ID required', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
$referees = $this->api->get_match_referees($match_id);
|
||||
|
||||
if (is_wp_error($referees)) {
|
||||
return $this->render_error_message($referees);
|
||||
}
|
||||
|
||||
if (empty($referees)) {
|
||||
return '<div class="swi-foot-data-unavailable">ℹ️ ' . __('No data available', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="swi-foot-referees-container" data-match-id="<?php echo esc_attr($match_id); ?>">
|
||||
<ul class="referees-list">
|
||||
<?php foreach ($referees as $referee): ?>
|
||||
<li class="referee-item">
|
||||
<span class="referee-role"><?php echo esc_html($referee['refereeRoleName'] ?? ''); ?>:</span>
|
||||
<strong><?php echo esc_html(($referee['firstname'] ?? '') . ' ' . ($referee['name'] ?? '')); ?></strong>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic inline data point shortcode
|
||||
* Extracts a specific field from match data and displays it inline
|
||||
@ -512,15 +667,16 @@ class Swi_Foot_Shortcodes
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse the matchDate string - handle multiple formats
|
||||
$timestamp = strtotime($match['matchDate']);
|
||||
if ($timestamp === false) {
|
||||
// Extract date part from ISO datetime string (e.g., "2026-03-27T19:30:00" -> "2026-03-27")
|
||||
// API provides date in Swiss time, use as-is without any timezone conversion
|
||||
$date_part = substr($match['matchDate'], 0, 10);
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_part)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use WordPress date format setting
|
||||
// Convert date string to WordPress format
|
||||
$date_format = get_option('date_format');
|
||||
return wp_date($date_format, $timestamp);
|
||||
return date_i18n($date_format, strtotime($date_part));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -535,15 +691,20 @@ class Swi_Foot_Shortcodes
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse the matchDate string - handle multiple formats
|
||||
$timestamp = strtotime($match['matchDate']);
|
||||
if ($timestamp === false) {
|
||||
// Extract time part from ISO datetime string (e.g., "2026-03-27T19:30:00" -> "19:30:00")
|
||||
// API provides time in Swiss time, use as-is without any timezone conversion
|
||||
if (strpos($match['matchDate'], 'T') === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$time_part = substr($match['matchDate'], 11, 8);
|
||||
if (!preg_match('/^\d{2}:\d{2}:\d{2}$/', $time_part)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use WordPress time format setting
|
||||
// Format time string with WordPress time format setting
|
||||
$time_format = get_option('time_format');
|
||||
return wp_date($time_format, $timestamp);
|
||||
return date_i18n($time_format, strtotime('2000-01-01 ' . $time_part));
|
||||
}
|
||||
|
||||
private function get_match_data($atts)
|
||||
@ -616,13 +777,13 @@ class Swi_Foot_Shortcodes
|
||||
), $atts);
|
||||
|
||||
if (empty($atts['team_id'])) {
|
||||
return '<span class="swi-foot-error">' . __('Team ID required', 'swi_foot_matchdata') . '</span>';
|
||||
return '<div class="swi-foot-error">' . __('Team ID required', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
$standings = $this->api->get_standings($atts['team_id']);
|
||||
|
||||
if (is_wp_error($standings) || empty($standings)) {
|
||||
return '<span class="swi-foot-error">' . __('Standings data not available', 'swi_foot_matchdata') . '</span>';
|
||||
return '<div class="swi-foot-error">' . __('Standings data not available', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
@ -667,16 +828,18 @@ class Swi_Foot_Shortcodes
|
||||
public function roster_shortcode($atts)
|
||||
{
|
||||
$atts = shortcode_atts(array(
|
||||
'match_id' => '',
|
||||
'team_id' => '',
|
||||
'show_current' => 'false',
|
||||
'side' => '',
|
||||
'with_bench' => 'false'
|
||||
'match_id' => '',
|
||||
'team_id' => '',
|
||||
'show_current' => 'false',
|
||||
'side' => '',
|
||||
'show_starting' => 'true',
|
||||
'show_bench' => 'false',
|
||||
'with_bench' => 'false' // Legacy support
|
||||
), $atts);
|
||||
|
||||
$side = strtolower(trim($atts['side']));
|
||||
if (!in_array($side, array('home', 'away'), true)) {
|
||||
return '<span class="swi-foot-error">' . __('Parameter "side" must be "home" or "away"', 'swi_foot_matchdata') . '</span>';
|
||||
return '<div class="swi-foot-error">' . __('Parameter "side" must be "home" or "away"', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
$match_id = $atts['match_id'];
|
||||
@ -688,27 +851,27 @@ class Swi_Foot_Shortcodes
|
||||
}
|
||||
|
||||
if (empty($match_id)) {
|
||||
return '<span class="swi-foot-error">' . __('Match ID required', 'swi_foot_matchdata') . '</span>';
|
||||
return '<div class="swi-foot-error">' . __('Match ID required', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
// Parse show_starting and show_bench attributes (handle legacy with_bench)
|
||||
$show_starting = strtolower($atts['show_starting']) === 'true';
|
||||
$show_bench = strtolower($atts['show_bench']) === 'true';
|
||||
|
||||
// Legacy support: if with_bench is true and show_starting is default, show both
|
||||
if (strtolower($atts['with_bench']) === 'true' && !$show_bench) {
|
||||
$show_bench = true;
|
||||
}
|
||||
|
||||
// First check if we have saved final data
|
||||
$saved = $this->api->get_finished_match_data($match_id);
|
||||
if ($saved) {
|
||||
$players = $saved['roster']['players'] ?? array();
|
||||
$bench_players = $saved['roster']['bench'] ?? array();
|
||||
$players = $saved['roster']['players'] ?? array();
|
||||
} else {
|
||||
// Live fetch
|
||||
$players = $this->api->get_match_players($match_id);
|
||||
if (is_wp_error($players)) {
|
||||
return '<span class="swi-foot-error">' . __('Roster data not available', 'swi_foot_matchdata') . ': ' . esc_html($players->get_error_message()) . '</span>';
|
||||
}
|
||||
$bench_players = array();
|
||||
|
||||
if (strtolower($atts['with_bench']) === 'true') {
|
||||
$bench = $this->api->get_match_bench($match_id);
|
||||
if (!is_wp_error($bench)) {
|
||||
$bench_players = $bench;
|
||||
}
|
||||
return $this->render_error_message($players);
|
||||
}
|
||||
|
||||
// Check match ended & possibly save events + roster together
|
||||
@ -722,68 +885,100 @@ class Swi_Foot_Shortcodes
|
||||
if (is_array($players)) {
|
||||
$this->api->save_finished_match_data(
|
||||
$match_id,
|
||||
array('players' => $players, 'bench' => $bench_players),
|
||||
array('players' => $players),
|
||||
$events
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter roster for side
|
||||
$filtered_players = array_filter($players, function ($p) use ($side) {
|
||||
// Filter players by team side
|
||||
$team_players = array_filter($players, function ($p) use ($side) {
|
||||
return $side === 'home' ? !empty($p['isHomeTeam']) : empty($p['isHomeTeam']);
|
||||
});
|
||||
$filtered_bench = array_filter($bench_players, function ($p) use ($side) {
|
||||
return $side === 'home' ? !empty($p['isHomeTeam']) : empty($p['isHomeTeam']);
|
||||
|
||||
// Split players into starting (assignmentRoleId = 0 or 1 for captain) and bench (assignmentRoleId > 1)
|
||||
$starting_players = array_filter($team_players, function ($p) {
|
||||
return isset($p['assignmentRoleId']) && ((int)$p['assignmentRoleId'] === 0 || (int)$p['assignmentRoleId'] === 1);
|
||||
});
|
||||
$bench_players = array_filter($team_players, function ($p) {
|
||||
return !isset($p['assignmentRoleId']) || ((int)$p['assignmentRoleId'] > 1);
|
||||
});
|
||||
|
||||
// If neither option is checked, return empty div
|
||||
if (!$show_starting && !$show_bench) {
|
||||
return '<div class="swi-foot-error">' . __('No roster display options selected', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="swi-foot-roster" data-match-id="<?php echo esc_attr($match_id); ?>">
|
||||
<h3><?php echo esc_html(ucfirst($side) . ' ' . __('Team Roster', 'swi_foot_matchdata')); ?></h3>
|
||||
<?php if (!empty($filtered_players)): ?>
|
||||
<ul class="roster-list">
|
||||
<?php foreach ($filtered_players as $pl): ?>
|
||||
<li>
|
||||
<?php
|
||||
$jersey = $pl['jerseyNumber'] ?? '';
|
||||
$firstname = $pl['firstname'] ?? '';
|
||||
$name = $pl['name'] ?? '';
|
||||
$secondName = $pl['secondName'] ?? '';
|
||||
$positionName = $pl['positionName'] ?? $pl['position'] ?? '';
|
||||
$displayName = trim($firstname . ' ' . $name . ' ' . $secondName);
|
||||
echo esc_html($jersey . ' – ' . $displayName);
|
||||
if (!empty($positionName)) {
|
||||
echo ' (' . esc_html($positionName) . ')';
|
||||
}
|
||||
?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<p><?php _e('No roster data available.', 'swi_foot_matchdata'); ?></p>
|
||||
|
||||
<?php if ($show_starting && !empty($starting_players)): ?>
|
||||
<div class="swi-foot-roster-starting">
|
||||
<h4><?php _e('Starting Squad', 'swi_foot_matchdata'); ?></h4>
|
||||
<ul class="roster-list">
|
||||
<?php foreach ($starting_players as $pl): ?>
|
||||
<li class="roster-player<?php echo isset($pl['assignmentRoleId']) && (int)$pl['assignmentRoleId'] === 1 ? ' roster-player-captain' : ''; ?>">
|
||||
<?php
|
||||
$jersey = $pl['jerseyNumber'] ?? '';
|
||||
$firstname = $pl['firstname'] ?? '';
|
||||
$name = $pl['name'] ?? '';
|
||||
$secondName = $pl['secondName'] ?? '';
|
||||
$positionName = $pl['positionName'] ?? $pl['position'] ?? '';
|
||||
$isCaptain = isset($pl['assignmentRoleId']) && (int)$pl['assignmentRoleId'] === 1;
|
||||
$displayName = trim($firstname . ' ' . $name . ' ' . $secondName);
|
||||
|
||||
// Captain badge
|
||||
if ($isCaptain) {
|
||||
echo '<span class="swi-foot-captain-badge" title="' . esc_attr(__('Captain', 'swi_foot_matchdata')) . '">C</span> ';
|
||||
}
|
||||
|
||||
echo esc_html($jersey . ' – ' . $displayName);
|
||||
|
||||
if (!empty($positionName)) {
|
||||
echo ' <span class="swi-foot-position">(' . esc_html($positionName) . ')</span>';
|
||||
}
|
||||
?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($filtered_bench)): ?>
|
||||
<h4><?php _e('Bench', 'swi_foot_matchdata'); ?></h4>
|
||||
<ul class="roster-bench-list">
|
||||
<?php foreach ($filtered_bench as $pl): ?>
|
||||
<li>
|
||||
<?php
|
||||
$jersey = $pl['jerseyNumber'] ?? '';
|
||||
$firstname = $pl['firstname'] ?? '';
|
||||
$name = $pl['name'] ?? '';
|
||||
$secondName = $pl['secondName'] ?? '';
|
||||
$positionName = $pl['positionName'] ?? $pl['position'] ?? '';
|
||||
$displayName = trim($firstname . ' ' . $name . ' ' . $secondName);
|
||||
echo esc_html($jersey . ' – ' . $displayName);
|
||||
if (!empty($positionName)) {
|
||||
echo ' (' . esc_html($positionName) . ')';
|
||||
}
|
||||
?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php if ($show_starting && !empty($starting_players) && $show_bench && !empty($bench_players)): ?>
|
||||
<hr class="swi-foot-roster-divider" />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_bench && !empty($bench_players)): ?>
|
||||
<div class="swi-foot-roster-bench">
|
||||
<h4><?php _e('Bench', 'swi_foot_matchdata'); ?></h4>
|
||||
<ul class="roster-bench-list">
|
||||
<?php foreach ($bench_players as $pl): ?>
|
||||
<li class="roster-player">
|
||||
<?php
|
||||
$jersey = $pl['jerseyNumber'] ?? '';
|
||||
$firstname = $pl['firstname'] ?? '';
|
||||
$name = $pl['name'] ?? '';
|
||||
$secondName = $pl['secondName'] ?? '';
|
||||
$positionName = $pl['positionName'] ?? $pl['position'] ?? '';
|
||||
$displayName = trim($firstname . ' ' . $name . ' ' . $secondName);
|
||||
echo esc_html($jersey . ' – ' . $displayName);
|
||||
if (!empty($positionName)) {
|
||||
echo ' <span class="swi-foot-position">(' . esc_html($positionName) . ')</span>';
|
||||
}
|
||||
?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$show_starting && !empty($bench_players) && !$show_bench): ?>
|
||||
<p><?php _e('No roster data to display.', 'swi_foot_matchdata'); ?></p>
|
||||
<?php elseif ($show_starting && empty($starting_players) && empty($bench_players)): ?>
|
||||
<p><?php _e('No roster data available.', 'swi_foot_matchdata'); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
@ -814,7 +1009,7 @@ class Swi_Foot_Shortcodes
|
||||
}
|
||||
|
||||
if (empty($match_id)) {
|
||||
return '<span class="swi-foot-error">' . __('Match ID required', 'swi_foot_matchdata') . '</span>';
|
||||
return '<div class="swi-foot-error">' . __('Match ID required', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
// Try saved first
|
||||
@ -824,7 +1019,7 @@ class Swi_Foot_Shortcodes
|
||||
} else {
|
||||
$events = $this->api->get_match_events($match_id);
|
||||
if (is_wp_error($events)) {
|
||||
return '<span class="swi-foot-error">' . __('Events data not available', 'swi_foot_matchdata') . '</span>';
|
||||
return $this->render_error_message($events);
|
||||
}
|
||||
|
||||
// Check if match ended and store both data sets
|
||||
|
||||
Binary file not shown.
@ -1,705 +0,0 @@
|
||||
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-de_DE.mo
Normal file
BIN
languages/swi_foot_matchdata-de_DE.mo
Normal file
Binary file not shown.
1315
languages/swi_foot_matchdata-de_DE.po
Normal file
1315
languages/swi_foot_matchdata-de_DE.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
languages/swi_foot_matchdata-it_IT.mo
Normal file
BIN
languages/swi_foot_matchdata-it_IT.mo
Normal file
Binary file not shown.
1292
languages/swi_foot_matchdata-it_IT.po
Normal file
1292
languages/swi_foot_matchdata-it_IT.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
182
readme.txt
Normal file
182
readme.txt
Normal file
@ -0,0 +1,182 @@
|
||||
=== Swiss Football Matchdata ===
|
||||
Contributors: David Reindl
|
||||
Tags: football, soccer, swiss football, sports, matches, standings, schedules, shortcodes, gutenberg
|
||||
Requires at least: 5.0
|
||||
Tested up to: 6.4
|
||||
Stable tag: 1.0.0
|
||||
Requires PHP: 7.4
|
||||
License: GPL v2 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Connect to Swiss Football Association API to display match data, standings, and schedules with flexible shortcodes and Gutenberg blocks.
|
||||
|
||||
== Description ==
|
||||
|
||||
Swiss Football Matchdata plugin allows you to integrate with the Swiss Football Association API to display:
|
||||
|
||||
* Current team standings/rankings
|
||||
* Upcoming match schedules
|
||||
* Detailed match information
|
||||
* Individual match elements via shortcodes
|
||||
* Easy Gutenberg block integration
|
||||
|
||||
The plugin features smart caching (configurable duration), automatic token management, and provides both Gutenberg blocks and shortcodes for maximum flexibility.
|
||||
|
||||
= Key Features =
|
||||
|
||||
* **Flexible Shortcodes**: Insert individual match elements anywhere in your content
|
||||
* **Smart Caching**: Configurable cache duration for optimal performance (10-300 seconds)
|
||||
* **Gutenberg Integration**: Easy-to-use blocks and shortcode inserter for the block editor
|
||||
* **API Management**: Built-in token refresh and error handling
|
||||
* **Responsive Design**: Mobile-friendly display of all data
|
||||
* **Configurable Base URL**: Set your own API endpoint
|
||||
* **Team Management**: Automatic team list fetching and caching
|
||||
|
||||
= Available Shortcodes =
|
||||
|
||||
**Full Match Display:**
|
||||
* `[swi_foot_match match_id="12345"]` - Complete match information
|
||||
* `[swi_foot_match team_id="67" show_next="true"]` - Next match for team
|
||||
|
||||
**Individual Match Elements:**
|
||||
* `[swi_foot_match_home_team match_id="12345"]` - Home team name
|
||||
* `[swi_foot_match_away_team match_id="12345"]` - Away team name
|
||||
* `[swi_foot_match_date match_id="12345" format="d.m.Y"]` - Match date
|
||||
* `[swi_foot_match_time match_id="12345" format="H:i"]` - Match time
|
||||
* `[swi_foot_match_venue match_id="12345"]` - Match venue
|
||||
* `[swi_foot_match_score match_id="12345" separator=":"]` - Current score
|
||||
* `[swi_foot_match_status match_id="12345"]` - Match status
|
||||
* `[swi_foot_match_league match_id="12345"]` - League name
|
||||
* `[swi_foot_match_round match_id="12345"]` - Round number
|
||||
|
||||
= Gutenberg Blocks =
|
||||
|
||||
* **Swiss Football Context** - Set shared context (team, season) for child blocks
|
||||
* **Swiss Football Standings** - Display team rankings and standings
|
||||
* **Swiss Football Schedule** - Show upcoming matches for a team
|
||||
* **Swiss Football Match Roster** - Display player rosters for a match
|
||||
* **Swiss Football Match Events** - Show match events (goals, cards, substitutions) with live updates
|
||||
* **Swiss Football Match Referees** - Display referee information
|
||||
* **Swiss Football Shortcode** - Insert custom shortcodes with visual interface (deprecated, use paragraph toolbar instead)
|
||||
|
||||
= Usage Examples =
|
||||
|
||||
**Simple Match Display:**
|
||||
`[swi_foot_match match_id="12345"]`
|
||||
|
||||
**Next Match for Team:**
|
||||
`[swi_foot_match team_id="67" show_next="true"]`
|
||||
|
||||
**Individual Elements in Text:**
|
||||
`The match between [swi_foot_match_home_team match_id="12345"] and [swi_foot_match_away_team match_id="12345"] will be played on [swi_foot_match_date match_id="12345" format="F j, Y"] at [swi_foot_match_time match_id="12345" format="g:i A"] in [swi_foot_match_venue match_id="12345"].
|
||||
Current score: [swi_foot_match_score match_id="12345"]`
|
||||
|
||||
|
||||
== Installation ==
|
||||
|
||||
1. Upload the plugin files to `/wp-content/plugins/swiss-football-matchdata/`
|
||||
2. Activate the plugin through the 'Plugins' screen in WordPress
|
||||
3. Go to Settings → Swiss Football to configure your API credentials
|
||||
4. Enter your API base URL, username (application key), password (application pass), Verein ID, and Season ID
|
||||
5. Test the connection and refresh your teams list
|
||||
6. Start using the shortcodes and blocks in your content
|
||||
|
||||
== Configuration ==
|
||||
|
||||
After activation, configure these settings in Settings → Swiss Football:
|
||||
|
||||
**API Configuration:**
|
||||
* **API Base URL**: The Swiss Football API endpoint (default: https://stg-club-api-services.football.ch)
|
||||
* **API Username**: Your application key from Swiss Football
|
||||
* **API Password**: Your application password from Swiss Football
|
||||
* **Verein ID**: Your club's unique identifier
|
||||
* **Season ID**: Current season (usually the year)
|
||||
|
||||
**Cache Settings:**
|
||||
* **Match Data Cache Duration**: How long to cache match data (10-300 seconds, default: 30)
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= How do I get API credentials? =
|
||||
|
||||
Contact the Swiss Football Association to obtain your application key, application password, and Verein ID.
|
||||
|
||||
= How often is data updated? =
|
||||
|
||||
Match data is cached according to your configured cache duration (default 30 seconds). The cache is automatically refreshed when pages with shortcodes are loaded.
|
||||
|
||||
= Can I customize the display styling? =
|
||||
|
||||
Yes, the plugin includes CSS classes you can target:
|
||||
* `.swi-foot-match-container` - Full match displays
|
||||
* `.swi-foot-standings` - Standings tables
|
||||
* `.swi-foot-schedule` - Schedule lists
|
||||
* `.swi-foot-inline` - Individual shortcode elements
|
||||
* `.swi-foot-error` - Error messages
|
||||
|
||||
= What happens if the API is unavailable? =
|
||||
|
||||
The plugin will display cached data if available, or show appropriate error messages. It includes robust error handling and logging.
|
||||
|
||||
= How do I use shortcodes in the Gutenberg editor? =
|
||||
|
||||
Click the shortcode icon in any paragraph's toolbar to quickly insert shortcodes. You can also use the "Swiss Football Shortcode" block (now deprecated) for backwards compatibility.
|
||||
|
||||
= Can I display data for multiple teams? =
|
||||
|
||||
Yes, you can use different team IDs in different shortcodes and blocks throughout your site.
|
||||
|
||||
= How do I find my team's ID? =
|
||||
|
||||
After configuring your API credentials, go to Settings → Swiss Football and click "Refresh Teams List". This will display all available teams with their IDs.
|
||||
|
||||
= Can I use custom date/time formats? =
|
||||
|
||||
Yes, use the `format` parameter in date and time shortcodes with PHP date format strings (e.g., `format="d.m.Y"` for European date format).
|
||||
|
||||
= Does the plugin work with caching plugins? =
|
||||
|
||||
Yes, the plugin's internal caching system works alongside WordPress caching plugins. The 30-second cache ensures fresh data while minimizing API calls.
|
||||
|
||||
== Screenshots ==
|
||||
|
||||
1. Admin settings page with API configuration and team management
|
||||
2. Shortcode helper meta box in post editor with click-to-copy functionality
|
||||
3. Gutenberg shortcode inserter block with visual interface
|
||||
4. Example standings table display on frontend
|
||||
5. Example match schedule display with upcoming games
|
||||
6. Individual shortcode elements integrated in post content
|
||||
7. Cache management and connection testing interface
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 1.0.0 =
|
||||
* Initial release
|
||||
* Swiss Football API integration with automatic token management
|
||||
* Comprehensive shortcode system for individual match elements
|
||||
* Configurable caching system (10-300 seconds)
|
||||
* Gutenberg blocks for standings, schedules, rosters, and events
|
||||
* Context provider block for scoped data
|
||||
* Admin interface for configuration and team management
|
||||
* Responsive design and error handling
|
||||
* Support for configurable API base URL
|
||||
* Click-to-copy shortcode functionality in admin
|
||||
* Smart cache updates on page load
|
||||
* Paragraph toolbar shortcode insertion
|
||||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 1.0.0 =
|
||||
Initial release of Swiss Football Matchdata plugin with comprehensive API integration and flexible shortcode system.
|
||||
|
||||
== Support ==
|
||||
|
||||
For support, feature requests, or bug reports, please contact the plugin author or visit the plugin's support forum.
|
||||
|
||||
== Privacy ==
|
||||
|
||||
This plugin connects to the Swiss Football Association API to retrieve match data. No personal data from your website visitors is sent to external services. The plugin only sends:
|
||||
* API credentials (application key/password) for authentication
|
||||
* Club/team identifiers to retrieve relevant match data
|
||||
|
||||
All data is cached locally on your WordPress installation according to your configured cache duration.
|
||||
@ -49,11 +49,24 @@ function makeAjaxCall(action, data = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
function checkBlockStatus(matchId, endpoint) {
|
||||
if (!matchId) return Promise.resolve(null);
|
||||
|
||||
const root = swiFootEditorData.rest_url.replace(/\/$/, '');
|
||||
const url = `${root}/block-status/${matchId}/${endpoint}`;
|
||||
const opts = { credentials: 'same-origin', headers: { 'X-WP-Nonce': swiFootEditorData.rest_nonce } };
|
||||
|
||||
return fetch(url, opts)
|
||||
.then(r => r.json())
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
// Standings block (editor UI only, server renders)
|
||||
// Gets team from container context, no user selection needed
|
||||
registerBlockType('swi-foot/standings', {
|
||||
apiVersion: 3,
|
||||
title: __('Swiss Football Standings', 'swi_foot_matchdata'),
|
||||
category: 'embed',
|
||||
category: 'swi-football',
|
||||
supports: { align: true, anchor: true },
|
||||
edit: ({ attributes, setAttributes, isSelected }) => {
|
||||
const blockProps = useBlockProps({ style: isSelected ? {outline: '2px solid #0073aa'} : {} });
|
||||
@ -64,7 +77,10 @@ registerBlockType('swi-foot/standings', {
|
||||
<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 className="swi-foot-editor-placeholder" style={{padding: '24px', cursor: 'pointer', background: '#f5f5f5', border: '2px solid #ddd', borderRadius: '4px'}}>
|
||||
<p><strong>{__('Standings', 'swi_foot_matchdata')}</strong></p>
|
||||
<p style={{fontSize: '13px', color: '#666', marginBottom: '0'}}>{__('League standings inherited from team context. ✓ Data will render on the front-end.', 'swi_foot_matchdata')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@ -74,15 +90,17 @@ registerBlockType('swi-foot/standings', {
|
||||
// Schedule block (editor UI only, server renders)
|
||||
// Gets team from container context, only allows limiting results
|
||||
registerBlockType('swi-foot/schedule', {
|
||||
apiVersion: 3,
|
||||
title: __('Swiss Football Schedule', 'swi_foot_matchdata'),
|
||||
category: 'embed',
|
||||
category: 'swi-football',
|
||||
supports: { align: true, anchor: true },
|
||||
attributes: {
|
||||
limit: { type: 'number', default: 10 }
|
||||
limit: { type: 'number', default: 10 },
|
||||
matchFilter: { type: 'string', default: 'all' }
|
||||
},
|
||||
edit: ({ attributes, setAttributes, isSelected }) => {
|
||||
const blockProps = useBlockProps({ style: isSelected ? {outline: '2px solid #0073aa'} : {} });
|
||||
const { limit } = attributes;
|
||||
const { limit, matchFilter } = attributes;
|
||||
|
||||
return (
|
||||
<div {...blockProps}>
|
||||
@ -90,106 +108,43 @@ registerBlockType('swi-foot/schedule', {
|
||||
<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} />
|
||||
<SelectControl
|
||||
label={__('Match Filter', 'swi_foot_matchdata')}
|
||||
value={matchFilter || 'all'}
|
||||
options={[
|
||||
{ label: __('All Matches', 'swi_foot_matchdata'), value: 'all' },
|
||||
{ label: __('Home Matches Only', 'swi_foot_matchdata'), value: 'home' },
|
||||
{ label: __('Away Matches Only', 'swi_foot_matchdata'), value: 'away' }
|
||||
]}
|
||||
onChange={val => setAttributes({ matchFilter: val })}
|
||||
/>
|
||||
</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 className="swi-foot-editor-placeholder" style={{padding: '24px', cursor: 'pointer', background: '#f5f5f5', border: '2px solid #ddd', borderRadius: '4px'}}>
|
||||
<p><strong>{__('Schedule', 'swi_foot_matchdata')}</strong></p>
|
||||
<p style={{fontSize: '13px', color: '#666', marginBottom: '0'}}>{__('Team and match schedule inherited from context. ✓ Data will render on the front-end.', 'swi_foot_matchdata')}</p>
|
||||
</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', {
|
||||
apiVersion: 3,
|
||||
title: __('Swiss Football Match Roster', 'swi_foot_matchdata'),
|
||||
category: 'embed',
|
||||
category: 'swi-football',
|
||||
supports: { align: true, anchor: true },
|
||||
attributes: {
|
||||
side: { type: 'string', default: 'home' },
|
||||
withBench: { type: 'boolean', default: false }
|
||||
showStartingSquad: { type: 'boolean', default: true },
|
||||
showBench: { type: 'boolean', default: false }
|
||||
},
|
||||
edit: ({ attributes, setAttributes, isSelected }) => {
|
||||
const blockProps = useBlockProps({ style: isSelected ? {outline: '2px solid #0073aa'} : {} });
|
||||
const { side, withBench } = attributes;
|
||||
const { side, showStartingSquad, showBench } = attributes;
|
||||
|
||||
return (
|
||||
<div {...blockProps}>
|
||||
@ -201,22 +156,35 @@ registerBlockType('swi-foot/match-roster', {
|
||||
value={side}
|
||||
options={[{value:'home',label:__('Home Team', 'swi_foot_matchdata')},{value:'away',label:__('Away Team', 'swi_foot_matchdata')}]}
|
||||
onChange={v=>setAttributes({side:v})}
|
||||
__next40pxDefaultSize={true}
|
||||
__nextHasNoMarginBottom={true}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={__('Include Bench Players', 'swi_foot_matchdata')}
|
||||
checked={withBench}
|
||||
onChange={v=>setAttributes({withBench:v})}
|
||||
label={__('Show Starting Squad', 'swi_foot_matchdata')}
|
||||
checked={showStartingSquad}
|
||||
onChange={v=>setAttributes({showStartingSquad:v})}
|
||||
__nextHasNoMarginBottom={true}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={__('Show Bench', 'swi_foot_matchdata')}
|
||||
checked={showBench}
|
||||
onChange={v=>setAttributes({showBench:v})}
|
||||
__nextHasNoMarginBottom={true}
|
||||
/>
|
||||
</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 className="swi-foot-editor-placeholder" style={{padding: '24px', cursor: 'pointer', background: '#f5f5f5', border: '2px solid #ddd', borderRadius: '4px'}}>
|
||||
<p><strong>{__('Match Roster', 'swi_foot_matchdata')}</strong></p>
|
||||
<p style={{fontSize: '13px', color: '#666', marginBottom: '0'}}>{__('Team and match are inherited from the container context. ✓ Data will render on the front-end.', 'swi_foot_matchdata')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
save: ({ attributes }) => {
|
||||
const { side, withBench } = attributes;
|
||||
const { side, showStartingSquad, showBench } = attributes;
|
||||
let shortcode = '[swi_foot_roster side="' + side + '"';
|
||||
if (withBench) shortcode += ' with_bench="true"';
|
||||
if (showStartingSquad) shortcode += ' starting_squad="true"';
|
||||
if (showBench) shortcode += ' bench="true"';
|
||||
shortcode += ']';
|
||||
return createElement(RawHTML, null, shortcode);
|
||||
}
|
||||
@ -225,8 +193,9 @@ registerBlockType('swi-foot/match-roster', {
|
||||
// Match Events
|
||||
// Gets match and team from container context, only allows setting refresh interval
|
||||
registerBlockType('swi-foot/match-events', {
|
||||
apiVersion: 3,
|
||||
title: __('Swiss Football Match Events', 'swi_foot_matchdata'),
|
||||
category: 'embed',
|
||||
category: 'swi-football',
|
||||
supports: { align: true, anchor: true },
|
||||
attributes: {
|
||||
refreshInterval: { type: 'number', default: 30 },
|
||||
@ -259,10 +228,15 @@ registerBlockType('swi-foot/match-events', {
|
||||
{ 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')}
|
||||
__next40pxDefaultSize={true}
|
||||
__nextHasNoMarginBottom={true}
|
||||
/>
|
||||
</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 className="swi-foot-editor-placeholder" style={{padding: '24px', cursor: 'pointer', background: '#f5f5f5', border: '2px solid #ddd', borderRadius: '4px'}}>
|
||||
<p><strong>{__('Match Events', 'swi_foot_matchdata')}</strong></p>
|
||||
<p style={{fontSize: '13px', color: '#666', marginBottom: '0'}}>{__('Live match events inherited from context. ✓ Will update during match.', 'swi_foot_matchdata')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@ -277,3 +251,74 @@ registerBlockType('swi-foot/match-events', {
|
||||
}
|
||||
});
|
||||
|
||||
// Match Bench
|
||||
// Gets match from container context, displays team staff and bench players
|
||||
registerBlockType('swi-foot/match-bench', {
|
||||
apiVersion: 3,
|
||||
title: __('Swiss Football Match Bench', 'swi_foot_matchdata'),
|
||||
icon: 'clipboard-user',
|
||||
category: 'swi-football',
|
||||
supports: { align: true, anchor: true },
|
||||
attributes: {
|
||||
side: { type: 'string', default: 'home' }
|
||||
},
|
||||
edit: ({ attributes, setAttributes, isSelected }) => {
|
||||
const blockProps = useBlockProps({ style: isSelected ? {outline: '2px solid #0073aa'} : {} });
|
||||
const { side } = attributes;
|
||||
|
||||
return (
|
||||
<div {...blockProps}>
|
||||
<InspectorControls>
|
||||
<PanelBody title={__('Display Options', 'swi_foot_matchdata')}>
|
||||
<p style={{color: '#666', fontSize: '13px', marginBottom: '12px'}}>{__('Match is 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})}
|
||||
__next40pxDefaultSize={true}
|
||||
__nextHasNoMarginBottom={true}
|
||||
/>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
<div className="swi-foot-editor-placeholder" style={{padding: '24px', cursor: 'pointer', background: '#f5f5f5', border: '2px solid #ddd', borderRadius: '4px'}}>
|
||||
<p><strong>{__('Match Bench', 'swi_foot_matchdata')}</strong></p>
|
||||
<p style={{fontSize: '13px', color: '#666', marginBottom: '0'}}>{__('Team bench and substitutes inherited from context. ✓ Data will render on the front-end.', 'swi_foot_matchdata')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
save: ({ attributes }) => {
|
||||
const { side } = attributes;
|
||||
let shortcode = '[swi_foot_bench side="' + side + '"]';
|
||||
return createElement(RawHTML, null, shortcode);
|
||||
}
|
||||
});
|
||||
|
||||
// Match Referees
|
||||
// Gets match from container context, displays match officials and referees
|
||||
registerBlockType('swi-foot/match-referees', {
|
||||
apiVersion: 3,
|
||||
title: __('Swiss Football Match Referees', 'swi_foot_matchdata'),
|
||||
icon: 'whistle',
|
||||
category: 'swi-football',
|
||||
supports: { align: true, anchor: true },
|
||||
edit: ({ attributes, 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'}}>{__('Match 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'}}>
|
||||
<p><strong>{__('Match Referees', 'swi_foot_matchdata')}</strong></p>
|
||||
<p style={{fontSize: '13px', color: '#666', marginBottom: '0'}}>{__('Match officials and referees inherited from context. ✓ Data will render on the front-end.', 'swi_foot_matchdata')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
save: () => null
|
||||
});
|
||||
|
||||
|
||||
@ -280,6 +280,8 @@ function InlineDataModal( { onInsert, onClose, matchId } ) {
|
||||
options={ INLINE_DATA_POINTS }
|
||||
onChange={ setSelectedDataPoint }
|
||||
help={ __( 'Select which match data to display', 'swi_foot_matchdata' ) }
|
||||
__next40pxDefaultSize={true}
|
||||
__nextHasNoMarginBottom={true}
|
||||
/>
|
||||
</PanelRow>
|
||||
|
||||
@ -322,6 +324,8 @@ function InlineDataModal( { onInsert, onClose, matchId } ) {
|
||||
options={ SHORTCODE_OPTIONS }
|
||||
onChange={ setSelectedShortcode }
|
||||
help={ __( 'Select a shortcode to insert (logos, etc.)', 'swi_foot_matchdata' ) }
|
||||
__next40pxDefaultSize={true}
|
||||
__nextHasNoMarginBottom={true}
|
||||
/>
|
||||
</PanelRow>
|
||||
|
||||
|
||||
@ -35,6 +35,15 @@ class Swiss_Football_Matchdata {
|
||||
}
|
||||
|
||||
public function init() {
|
||||
// Detect version updates and flush REST API routes cache if needed
|
||||
$stored_version = get_option('swi_foot_plugin_version');
|
||||
if ($stored_version !== SWI_FOOT_PLUGIN_VERSION) {
|
||||
// Plugin was updated, clear REST endpoints cache
|
||||
delete_transient('rest_endpoints_data');
|
||||
wp_cache_delete('rest_endpoints', 'swi-foot/v1');
|
||||
update_option('swi_foot_plugin_version', SWI_FOOT_PLUGIN_VERSION);
|
||||
}
|
||||
|
||||
// Register post meta to store per-post context (season/team/match) for posts and pages
|
||||
$meta_args = array(
|
||||
'type' => 'object',
|
||||
@ -80,9 +89,14 @@ class Swiss_Football_Matchdata {
|
||||
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);
|
||||
add_option('swi_foot_plugin_version', SWI_FOOT_PLUGIN_VERSION);
|
||||
|
||||
// Flush rewrite rules
|
||||
flush_rewrite_rules();
|
||||
|
||||
// Clear REST API route caches
|
||||
delete_transient('rest_endpoints_data');
|
||||
wp_cache_delete('rest_endpoints', 'swi-foot/v1');
|
||||
}
|
||||
|
||||
public function deactivate() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user