Files
wrapartamenty.pl/wp-content/plugins/mphb-ical-sync/includes/AdminUI.php
Jacek Pyziak c49d3b39d4 Add MPHB iCal Sync plugin for automatic iCal synchronization and booking management
- Implement SyncCron class for scheduling iCal sync every 15 minutes.
- Create main plugin file with initialization and activation hooks.
- Add admin booking modal for managing reservations, including AJAX functionality for searching available rooms and creating bookings.
- Include necessary CSS and JS for modal functionality and user interaction.
- Ensure compatibility with MotoPress Hotel Booking plugin.
2026-03-02 18:14:50 +01:00

387 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace MphbIcalSync;
/**
* Panel administracyjny iCal:
* metabox na stronie edycji pokoju (mphb_room)
* pełna strona pod admin.php?page=mphb_ical (przejmuje hook MPHB)
*/
class AdminUI {
const NONCE_FIELD = 'mphb_ical_sync_nonce';
const NONCE_ACTION = 'mphb_ical_sync_save';
const NONCE_AJAX = 'mphb_ical_sync_ajax';
public function __construct() {
// Metabox na stronie pokoju
add_action( 'add_meta_boxes', [ $this, 'addMetaBox' ] );
add_action( 'save_post_mphb_room', [ $this, 'saveMetaBox' ], 10, 1 );
// AJAX sync
add_action( 'wp_ajax_mphb_ical_sync_room', [ $this, 'ajaxSyncRoom' ] );
// Przejęcie strony mphb_ical — bardzo późny priorytet (po MPHB)
add_action( 'admin_menu', [ $this, 'overrideIcalPage' ], 9999 );
}
// -------------------------------------------------------------------------
// Przejęcie strony admin.php?page=mphb_ical
// -------------------------------------------------------------------------
public function overrideIcalPage(): void {
// WordPress rejestruje callback strony jako akcję o nazwie zwróconej
// przez add_submenu_page(). Dla rodzica 'mphb_booking_menu' i sluga 'mphb_ical'
// ta nazwa to wynik get_plugin_page_hookname().
$hookname = get_plugin_page_hookname( 'mphb_ical', 'mphb_booking_menu' );
if ( ! $hookname ) {
return;
}
// Usuń MPHB-owy render (który jest pusty), dodaj nasz
remove_all_actions( $hookname );
add_action( $hookname, [ $this, 'renderIcalPage' ] );
}
public function renderIcalPage(): void {
$rooms = get_posts( [
'post_type' => 'mphb_room',
'post_status' => 'publish',
'numberposts' => -1,
'no_found_rows' => true,
'orderby' => 'title',
'order' => 'ASC',
] );
$syncRepo = MPHB()->getSyncUrlsRepository();
?>
<div class="wrap">
<h1 class="wp-heading-inline">Synchronizacja kalendarzy iCal</h1>
<hr class="wp-header-end" />
<?php if ( empty( $rooms ) ) : ?>
<p>Brak opublikowanych pokoi.</p>
<?php else : ?>
<style>
#mphb-ical-rooms-table { border-collapse: collapse; width: 100%; margin-top: 16px; background: #fff; }
#mphb-ical-rooms-table th,
#mphb-ical-rooms-table td { padding: 9px 13px; border: 1px solid #dcdcde; vertical-align: middle; font-size: 13px; }
#mphb-ical-rooms-table th { background: #f6f7f7; font-weight: 600; text-align: left; }
#mphb-ical-rooms-table tr:hover td { background: #f9f9f9; }
#mphb-ical-rooms-table .url-mono { font-family: monospace; font-size: 11px; word-break: break-all; }
#mphb-ical-rooms-table .no-val { color: #999; font-style: italic; }
.mphb-ical-status-ok { color: #1a7a1a; font-weight: 600; }
.mphb-ical-status-err { color: #c33; font-weight: 600; }
</style>
<p>
<strong>Eksport:</strong> skopiuj link i wklej w Booking.com → Ekstranet → Nieruchomość → Synchronizacja kalendarza → Eksportuj.<br>
<strong>Import:</strong> URL iCal z Booking.com wklej na stronie edycji pokoju (metabox „iCal Synchronizacja").
</p>
<table id="mphb-ical-rooms-table">
<thead>
<tr>
<th style="width:200px;">Pokój</th>
<th>Link eksportu (→ Booking.com)</th>
<th>URL importu (← Booking.com)</th>
<th style="width:170px;">Ostatnia synchronizacja</th>
<th style="width:120px;">Akcje</th>
</tr>
</thead>
<tbody>
<?php foreach ( $rooms as $room ) :
$roomId = $room->ID;
$exportUrl = FeedEndpoint::getExportUrl( $roomId );
$importUrls = array_values( $syncRepo->getUrls( $roomId ) );
$editUrl = get_edit_post_link( $roomId );
$lastSync = get_post_meta( $roomId, '_mphb_ical_last_sync', true );
$lastStatus = get_post_meta( $roomId, '_mphb_ical_last_status', true );
$lastResult = get_post_meta( $roomId, '_mphb_ical_last_result', true );
$inputId = 'mphb-ical-exp-' . $roomId;
$nonce = wp_create_nonce( self::NONCE_AJAX );
?>
<tr>
<td>
<a href="<?php echo esc_url( $editUrl ); ?>" style="font-weight:600;">
<?php echo esc_html( $room->post_title ); ?>
</a>
<br><small style="color:#999;">ID: <?php echo $roomId; ?></small>
</td>
<td>
<div style="display:flex;gap:6px;align-items:flex-start;">
<input type="text"
id="<?php echo esc_attr( $inputId ); ?>"
value="<?php echo esc_attr( $exportUrl ); ?>"
readonly onclick="this.select()"
class="url-mono"
style="flex:1;padding:3px 6px;" />
<button type="button" class="button button-small"
onclick="(function(id){var el=document.getElementById(id);el.select();navigator.clipboard.writeText(el.value).then(function(){});})('<?php echo esc_js( $inputId ); ?>')">
Kopiuj
</button>
</div>
</td>
<td>
<?php if ( empty( $importUrls ) ) : ?>
<span class="no-val">— brak —
<a href="<?php echo esc_url( $editUrl ); ?>" style="font-size:11px;">dodaj w edycji pokoju</a>
</span>
<?php else : ?>
<?php foreach ( $importUrls as $url ) : ?>
<div class="url-mono"><?php echo esc_html( $url ); ?></div>
<?php endforeach; ?>
<?php endif; ?>
</td>
<td>
<?php if ( $lastSync ) :
$timeStr = wp_date( 'd.m.Y H:i', (int) $lastSync );
$cls = $lastStatus === 'ok' ? 'mphb-ical-status-ok' : 'mphb-ical-status-err';
$icon = $lastStatus === 'ok' ? '✓' : '✗';
?>
<span class="<?php echo esc_attr( $cls ); ?>">
<?php echo esc_html( $icon . ' ' . $timeStr ); ?>
</span>
<?php if ( is_array( $lastResult ) ) : ?>
<br><small style="color:#888;">
+<?php echo (int) ( $lastResult['created'] ?? 0 ); ?> nowych,
<?php echo (int) ( $lastResult['deleted'] ?? 0 ); ?> usuniętych
</small>
<?php endif; ?>
<?php else : ?>
<span class="no-val">nie uruchamiano</span>
<?php endif; ?>
</td>
<td>
<?php if ( ! empty( $importUrls ) ) : ?>
<button type="button" class="button mphb-ical-sync-btn"
data-room-id="<?php echo esc_attr( $roomId ); ?>"
data-nonce="<?php echo esc_attr( $nonce ); ?>">
&#x1F504; Sync
</button>
<span class="mphb-ical-row-status" style="display:block;font-size:11px;margin-top:4px;"></span>
<?php else : ?>
<span class="no-val">—</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p style="margin-top:12px;color:#666;font-size:12px;">
&#x23F0; Automatyczna synchronizacja uruchamia się co 15 minut przez WP-Cron.
</p>
<?php endif; ?>
</div>
<script>
(function() {
document.querySelectorAll('.mphb-ical-sync-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var status = btn.nextElementSibling;
status.style.color = '#888';
status.textContent = 'Synchronizuję\u2026';
btn.disabled = true;
var fd = new FormData();
fd.append('action', 'mphb_ical_sync_room');
fd.append('room_id', btn.dataset.roomId);
fd.append('nonce', btn.dataset.nonce);
fetch(ajaxurl, { method: 'POST', body: fd })
.then(function(r) { return r.json(); })
.then(function(r) {
if (r.success) {
status.style.color = '#1a7a1a';
status.textContent = '\u2713 +' + r.data.created + ' / \u2212' + r.data.deleted;
} else {
status.style.color = '#c33';
status.textContent = '\u2717 ' + (r.data || 'b\u0142\u0105d');
}
btn.disabled = false;
})
.catch(function() {
status.style.color = '#c33';
status.textContent = '\u2717 b\u0142\u0105d po\u0142\u0105czenia';
btn.disabled = false;
});
});
});
})();
</script>
<?php
}
// -------------------------------------------------------------------------
// Metabox na stronie edycji pokoju
// -------------------------------------------------------------------------
public function addMetaBox(): void {
add_meta_box(
'mphb_ical_sync',
'iCal Synchronizacja (Booking.com)',
[ $this, 'renderMetaBox' ],
'mphb_room',
'normal',
'default'
);
}
public function renderMetaBox( \WP_Post $post ): void {
$roomId = $post->ID;
$exportUrl = FeedEndpoint::getExportUrl( $roomId );
$syncRepo = MPHB()->getSyncUrlsRepository();
$urls = $syncRepo->getUrls( $roomId );
$importUrls = implode( "\n", array_values( $urls ) );
$lastSync = get_post_meta( $roomId, '_mphb_ical_last_sync', true );
$lastStatus = get_post_meta( $roomId, '_mphb_ical_last_status', true );
$lastResult = get_post_meta( $roomId, '_mphb_ical_last_result', true );
wp_nonce_field( self::NONCE_ACTION, self::NONCE_FIELD );
echo '<style>
#mphb_ical_sync .mphb-section { margin-bottom: 14px; }
#mphb_ical_sync .mphb-section h4 { margin: 0 0 5px; font-size: 13px; font-weight: 600; }
#mphb_ical_sync .mphb-export-row { display: flex; gap: 8px; align-items: center; }
#mphb_ical_sync .mphb-export-row input { flex: 1; font-family: monospace; font-size: 11px; }
#mphb_ical_sync .description { color: #666; font-size: 12px; margin-top: 4px; }
</style>';
// Eksport
echo '<div class="mphb-section">';
echo '<h4>&#x1F4E4; Link eksportu (dla Booking.com)</h4>';
echo '<div class="mphb-export-row">';
$inputId = 'mphb-ical-exp-meta-' . $roomId;
echo '<input type="text" readonly id="' . esc_attr( $inputId ) . '" value="' . esc_attr( $exportUrl ) . '" onclick="this.select()" />';
echo '<button type="button" class="button" onclick="(function(id){var el=document.getElementById(id);el.select();navigator.clipboard.writeText(el.value).then(function(){});})(' . wp_json_encode( $inputId ) . ')">Kopiuj</button>';
echo '</div>';
echo '<p class="description">Wklej w Booking.com: Ekstranet → Nieruchomość → Synchronizacja kalendarza → Eksportuj.</p>';
echo '</div>';
// Import
echo '<div class="mphb-section">';
echo '<h4>&#x1F4E5; URL importu z Booking.com</h4>';
echo '<textarea name="mphb_ical_import_urls" rows="3" class="large-text" style="font-family:monospace;font-size:11px;">' . esc_textarea( $importUrls ) . '</textarea>';
echo '<p class="description">URL iCal z Booking.com (jeden na linię). Znajdziesz go w Booking.com: Ekstranet → Synchronizacja kalendarza → Importuj.</p>';
echo '</div>';
// Status
echo '<div class="mphb-section">';
if ( $lastSync ) {
$timeStr = wp_date( 'd.m.Y H:i', (int) $lastSync );
$icon = $lastStatus === 'ok' ? '✓ OK' : '✗ Błąd';
echo '<p style="margin:0 0 6px;">Ostatnia sync: <strong>' . esc_html( $timeStr ) . '</strong> — ' . esc_html( $icon );
if ( is_array( $lastResult ) ) {
echo ' (+' . (int) ( $lastResult['created'] ?? 0 ) . ' / ' . (int) ( $lastResult['deleted'] ?? 0 ) . ')';
}
echo '</p>';
} else {
echo '<p style="margin:0 0 6px;color:#666;">Synchronizacja jeszcze nie była uruchamiana.</p>';
}
$nonce = wp_create_nonce( self::NONCE_AJAX );
echo '<button type="button" class="button" id="mphb-ical-meta-sync" data-room-id="' . esc_attr( $roomId ) . '" data-nonce="' . esc_attr( $nonce ) . '">&#x1F504; Synchronizuj teraz</button>';
echo '&nbsp;<span id="mphb-ical-meta-status" style="font-size:12px;margin-left:8px;"></span>';
echo '</div>';
?>
<script>
(function() {
var btn = document.getElementById('mphb-ical-meta-sync');
if (!btn) return;
btn.addEventListener('click', function() {
var status = document.getElementById('mphb-ical-meta-status');
status.textContent = 'Synchronizuję\u2026';
btn.disabled = true;
var fd = new FormData();
fd.append('action', 'mphb_ical_sync_room');
fd.append('room_id', btn.dataset.roomId);
fd.append('nonce', btn.dataset.nonce);
fetch(ajaxurl, {method:'POST',body:fd})
.then(function(r){return r.json();})
.then(function(r){
if(r.success){
status.style.color='#1a7a1a';
status.textContent='\u2713 +'+r.data.created+' / \u2212'+r.data.deleted;
} else {
status.style.color='#c33';
status.textContent='\u2717 '+(r.data||'b\u0142\u0105d');
}
btn.disabled=false;
})
.catch(function(){
status.style.color='#c33';
status.textContent='\u2717 b\u0142\u0105d po\u0142\u0105czenia';
btn.disabled=false;
});
});
})();
</script>
<?php
}
public function saveMetaBox( int $postId ): void {
if ( ! isset( $_POST[ self::NONCE_FIELD ] ) ) {
return;
}
if ( ! wp_verify_nonce(
sanitize_text_field( wp_unslash( $_POST[ self::NONCE_FIELD ] ) ),
self::NONCE_ACTION
) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $postId ) ) {
return;
}
$raw = isset( $_POST['mphb_ical_import_urls'] )
? sanitize_textarea_field( wp_unslash( $_POST['mphb_ical_import_urls'] ) )
: '';
$validUrls = [];
foreach ( array_filter( array_map( 'trim', explode( "\n", $raw ) ) ) as $url ) {
$url = esc_url_raw( $url );
if ( filter_var( $url, FILTER_VALIDATE_URL ) ) {
$validUrls[] = $url;
}
}
MPHB()->getSyncUrlsRepository()->updateUrls( $postId, $validUrls );
}
// -------------------------------------------------------------------------
// AJAX
// -------------------------------------------------------------------------
public function ajaxSyncRoom(): void {
check_ajax_referer( self::NONCE_AJAX, 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( 'Brak uprawnień' );
}
$roomId = isset( $_POST['room_id'] ) ? absint( $_POST['room_id'] ) : 0;
if ( ! $roomId ) {
wp_send_json_error( 'Nieprawidłowy ID pokoju' );
}
$result = SyncCron::syncRoom( $roomId );
if ( isset( $result['error'] ) ) {
wp_send_json_error( $result['error'] );
}
wp_send_json_success( $result );
}
}