Initial commit
This commit is contained in:
9
.claude/settings.local.json
Normal file
9
.claude/settings.local.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"WebFetch(domain:github.com)",
|
||||
"WebFetch(domain:pwr-solaar.github.io)",
|
||||
"WebFetch(domain:lekensteyn.nl)"
|
||||
]
|
||||
}
|
||||
}
|
||||
184
assets/css/admin.css
Normal file
184
assets/css/admin.css
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Door Status Voting - Admin Styles
|
||||
*/
|
||||
|
||||
.dsv-admin-wrap {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.dsv-admin-wrap h1 {
|
||||
font-size: 28px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
/* Grid Layout */
|
||||
.dsv-admin-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.dsv-admin-card {
|
||||
background: white;
|
||||
border: 1px solid #ccd0d4;
|
||||
border-radius: 8px;
|
||||
padding: 20px 25px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
.dsv-admin-card h2 {
|
||||
margin: 0 0 15px 0;
|
||||
padding: 0 0 10px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.dsv-admin-card-full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* Shortcode Display */
|
||||
.dsv-shortcode {
|
||||
display: block;
|
||||
background: #f0f0f1;
|
||||
padding: 12px 15px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
margin: 10px 0;
|
||||
user-select: all;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dsv-shortcode:hover {
|
||||
background: #e5e5e5;
|
||||
}
|
||||
|
||||
/* Form Elements */
|
||||
#dsv-settings-form textarea {
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#dsv-settings-form .button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.dsv-save-status {
|
||||
margin-left: 10px;
|
||||
color: #00a32a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Table Styles */
|
||||
.dsv-admin-card table {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.dsv-admin-card table th,
|
||||
.dsv-admin-card table td {
|
||||
padding: 12px 10px;
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.dsv-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dsv-badge-open {
|
||||
background: #4CAF50;
|
||||
}
|
||||
|
||||
.dsv-badge-closed {
|
||||
background: #f44336;
|
||||
}
|
||||
|
||||
.dsv-badge-tied {
|
||||
background: #FF9800;
|
||||
}
|
||||
|
||||
.dsv-meta-empty {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* Meta-Status Buttons */
|
||||
.dsv-meta-buttons {
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.dsv-meta-set-btn {
|
||||
min-width: 28px;
|
||||
padding: 2px 5px !important;
|
||||
font-size: 12px !important;
|
||||
line-height: 1.4 !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.dsv-meta-set-btn.active {
|
||||
box-shadow: inset 0 0 0 2px #2271b1;
|
||||
background: #f0f6fc;
|
||||
}
|
||||
|
||||
/* Abweichung hervorheben */
|
||||
.dsv-mismatch {
|
||||
background: #fff3cd !important;
|
||||
}
|
||||
|
||||
.dsv-mismatch td {
|
||||
border-left-color: #FF9800;
|
||||
}
|
||||
|
||||
.dsv-mismatch td:first-child {
|
||||
border-left: 3px solid #FF9800;
|
||||
}
|
||||
|
||||
/* Disabled Label */
|
||||
.dsv-disabled-label {
|
||||
color: #d63638;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.dsv-actions-cell {
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
.dsv-action-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.dsv-action-buttons .button {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
height: auto;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.dsv-reset-type-btn {
|
||||
min-width: 85px;
|
||||
}
|
||||
|
||||
/* No Data */
|
||||
.dsv-no-data {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 782px) {
|
||||
.dsv-admin-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
550
assets/css/frontend.css
Normal file
550
assets/css/frontend.css
Normal file
@@ -0,0 +1,550 @@
|
||||
/* ============================================
|
||||
DOOR STATUS VOTING - FRONTEND STYLES
|
||||
Angepasst an Website-Design mit Elementor-Variablen
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
--dsv-primary: #2F4858;
|
||||
--dsv-text: #141F27;
|
||||
--dsv-accent: #C5422A;
|
||||
--dsv-bg: #F6F9FC;
|
||||
--dsv-white: #FFFFFF;
|
||||
--dsv-hover: #B33822;
|
||||
--dsv-border: #E0E5E9;
|
||||
--dsv-success: #4CAF50;
|
||||
--dsv-error: #C5422A;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
HAUPT-WIDGET
|
||||
============================================ */
|
||||
|
||||
.dsv-widget {
|
||||
background: var(--dsv-bg);
|
||||
border-radius: 12px;
|
||||
padding: 24px 28px;
|
||||
width: 100%;
|
||||
border: 1px solid var(--dsv-border);
|
||||
box-shadow: 0 4px 12px rgba(47, 72, 88, 0.06);
|
||||
font-family: var(--e-global-typography-text-font-family), Sans-serif;
|
||||
font-size: var(--e-global-typography-text-font-size);
|
||||
font-weight: var(--e-global-typography-text-font-weight);
|
||||
color: var(--e-global-color-text, var(--dsv-text));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Titel */
|
||||
.dsv-title {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--dsv-primary);
|
||||
margin: 0 0 18px 0;
|
||||
}
|
||||
|
||||
/* Hauptzeile: Tür links, Voting rechts */
|
||||
.dsv-main-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TÜR CONTAINER
|
||||
============================================ */
|
||||
|
||||
.dsv-door-container {
|
||||
position: relative;
|
||||
width: 110px;
|
||||
height: 150px;
|
||||
flex-shrink: 0;
|
||||
perspective: 500px;
|
||||
}
|
||||
|
||||
/* Status Badge */
|
||||
.dsv-status-badge {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
z-index: 10;
|
||||
white-space: nowrap;
|
||||
transition: all 0.5s ease;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.dsv-status-open {
|
||||
background: linear-gradient(135deg, var(--dsv-success), #43A047);
|
||||
box-shadow: 0 3px 10px rgba(76, 175, 80, 0.35);
|
||||
}
|
||||
|
||||
.dsv-status-closed {
|
||||
background: linear-gradient(135deg, var(--dsv-accent), var(--dsv-hover));
|
||||
box-shadow: 0 3px 10px rgba(197, 66, 42, 0.35);
|
||||
}
|
||||
|
||||
.dsv-status-tied {
|
||||
background: linear-gradient(135deg, #FF9800, #F57C00);
|
||||
box-shadow: 0 3px 10px rgba(255, 152, 0, 0.35);
|
||||
}
|
||||
|
||||
/* Türrahmen */
|
||||
.dsv-door-frame {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 85px;
|
||||
height: 120px;
|
||||
background: linear-gradient(135deg, #5D4037 0%, #4E342E 100%);
|
||||
border-radius: 4px 4px 0 0;
|
||||
border: 4px solid #3E2723;
|
||||
border-bottom: 6px solid #3E2723;
|
||||
box-shadow: inset 0 0 12px rgba(0, 0, 0, 0.5);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Licht hinter der Tür */
|
||||
.dsv-light {
|
||||
position: absolute;
|
||||
inset: 3px;
|
||||
border-radius: 2px;
|
||||
background: linear-gradient(180deg, #FFF9C4 0%, #FFEE58 50%, #FFC107 100%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.7s ease;
|
||||
}
|
||||
|
||||
.dsv-widget[data-status="open"] .dsv-light {
|
||||
opacity: 1;
|
||||
box-shadow: 0 0 35px 12px rgba(255, 235, 59, 0.3);
|
||||
}
|
||||
|
||||
.dsv-widget[data-status="tied"] .dsv-light {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Lichtstrahlen */
|
||||
.dsv-light-rays {
|
||||
position: absolute;
|
||||
inset: 3px;
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.7s ease;
|
||||
}
|
||||
|
||||
.dsv-widget[data-status="open"] .dsv-light-rays,
|
||||
.dsv-widget[data-status="tied"] .dsv-light-rays {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dsv-ray {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 200, 0.4);
|
||||
filter: blur(1px);
|
||||
transform: rotate(5deg);
|
||||
}
|
||||
|
||||
.dsv-ray:nth-child(1) { left: 15%; }
|
||||
.dsv-ray:nth-child(2) { left: 30%; }
|
||||
.dsv-ray:nth-child(3) { left: 45%; }
|
||||
.dsv-ray:nth-child(4) { left: 60%; }
|
||||
.dsv-ray:nth-child(5) { left: 75%; }
|
||||
|
||||
/* ============================================
|
||||
DIE TÜR
|
||||
============================================ */
|
||||
|
||||
.dsv-door {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 71px;
|
||||
height: 106px;
|
||||
border-radius: 2px 2px 0 0;
|
||||
transform-origin: left center;
|
||||
transform-style: preserve-3d;
|
||||
transition: transform 0.7s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
background:
|
||||
linear-gradient(90deg,
|
||||
rgba(0,0,0,0.1) 0%,
|
||||
transparent 3%,
|
||||
transparent 97%,
|
||||
rgba(0,0,0,0.1) 100%
|
||||
),
|
||||
linear-gradient(180deg,
|
||||
#8D6E63 0%,
|
||||
#795548 15%,
|
||||
#8D6E63 30%,
|
||||
#6D4C41 50%,
|
||||
#795548 70%,
|
||||
#8D6E63 85%,
|
||||
#795548 100%
|
||||
);
|
||||
|
||||
box-shadow: 2px 0 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dsv-widget[data-status="open"] .dsv-door {
|
||||
transform: rotateY(-70deg);
|
||||
box-shadow: 4px 0 12px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.dsv-widget[data-status="tied"] .dsv-door {
|
||||
transform: rotateY(-35deg);
|
||||
box-shadow: 3px 0 8px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.dsv-widget[data-status="closed"] .dsv-door {
|
||||
transform: rotateY(0deg);
|
||||
}
|
||||
|
||||
/* Holzmaserung */
|
||||
.dsv-door-grain {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 2px 2px 0 0;
|
||||
opacity: 0.15;
|
||||
background-image:
|
||||
repeating-linear-gradient(
|
||||
92deg,
|
||||
transparent,
|
||||
transparent 5px,
|
||||
rgba(0,0,0,0.1) 5px,
|
||||
rgba(0,0,0,0.1) 6px
|
||||
);
|
||||
}
|
||||
|
||||
/* Türpaneele */
|
||||
.dsv-door-panel {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
right: 6px;
|
||||
border: 2px solid rgba(62, 39, 35, 0.4);
|
||||
border-radius: 2px;
|
||||
background: rgba(93, 64, 55, 0.2);
|
||||
}
|
||||
|
||||
.dsv-door-panel-top {
|
||||
top: 10px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.dsv-door-panel-bottom {
|
||||
bottom: 10px;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
/* Türklinke */
|
||||
.dsv-door-handle {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.dsv-handle-plate {
|
||||
width: 9px;
|
||||
height: 26px;
|
||||
border-radius: 4px;
|
||||
background: linear-gradient(135deg, #FFD54F 0%, #FFA000 50%, #FF8F00 100%);
|
||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.dsv-handle-lever {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -1px;
|
||||
transform: translateY(-50%);
|
||||
width: 11px;
|
||||
height: 5px;
|
||||
border-radius: 0 2px 2px 0;
|
||||
background: linear-gradient(180deg, #FFD54F 0%, #FFA000 100%);
|
||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Schatten */
|
||||
.dsv-shadow {
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 75px;
|
||||
height: 10px;
|
||||
background: radial-gradient(ellipse, rgba(0,0,0,0.2) 0%, transparent 70%);
|
||||
transition: all 0.7s ease;
|
||||
}
|
||||
|
||||
.dsv-widget[data-status="open"] .dsv-shadow {
|
||||
width: 90px;
|
||||
transform: translateX(-50%) scaleX(1.2);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
VOTING SECTION (rechte Seite)
|
||||
============================================ */
|
||||
|
||||
.dsv-voting-section {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Fortschrittsbalken */
|
||||
.dsv-progress-container {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.dsv-progress-bar {
|
||||
position: relative;
|
||||
height: 28px;
|
||||
background: var(--dsv-bg);
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--dsv-border);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dsv-progress-open {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--dsv-success) 0%, #66BB6A 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding-left: 12px;
|
||||
transition: width 0.7s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.dsv-progress-closed {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #EF5350 0%, var(--dsv-accent) 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-right: 12px;
|
||||
transition: width 0.7s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.dsv-progress-count {
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.dsv-progress-divider {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background: rgba(255,255,255,0.5);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.dsv-progress-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: var(--e-global-typography-text-font-weight);
|
||||
}
|
||||
|
||||
.dsv-label-open {
|
||||
color: #388E3C;
|
||||
}
|
||||
|
||||
.dsv-label-closed {
|
||||
color: var(--dsv-accent);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
VOTING BUTTONS
|
||||
============================================ */
|
||||
|
||||
.dsv-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dsv-buttons.dsv-voted {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.dsv-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px 18px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-family: var(--e-global-typography-text-font-family), Sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.dsv-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.dsv-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.dsv-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.dsv-btn-open {
|
||||
background: linear-gradient(135deg, var(--dsv-success) 0%, #388E3C 100%);
|
||||
}
|
||||
|
||||
.dsv-btn-open:hover {
|
||||
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.dsv-btn-closed {
|
||||
background: linear-gradient(135deg, var(--dsv-accent) 0%, var(--dsv-hover) 100%);
|
||||
}
|
||||
|
||||
.dsv-btn-closed:hover {
|
||||
box-shadow: 0 5px 15px rgba(197, 66, 42, 0.4);
|
||||
}
|
||||
|
||||
.dsv-btn-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.dsv-btn-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Voted Message mit Remove Button */
|
||||
.dsv-voted-message {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.dsv-voted-open {
|
||||
background: #E8F5E9;
|
||||
color: #2E7D32;
|
||||
border-color: #A5D6A7;
|
||||
}
|
||||
|
||||
.dsv-voted-closed {
|
||||
background: #FFEBEE;
|
||||
color: var(--dsv-hover);
|
||||
border-color: #FFCDD2;
|
||||
}
|
||||
|
||||
.dsv-voted-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Remove Vote Button */
|
||||
.dsv-remove-vote {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
margin-left: 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.dsv-remove-vote:hover {
|
||||
opacity: 1;
|
||||
background: rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
INFO TEXT
|
||||
============================================ */
|
||||
|
||||
.dsv-info {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
margin: 12px 0 0 0;
|
||||
font-weight: var(--e-global-typography-text-font-weight);
|
||||
}
|
||||
|
||||
.dsv-total {
|
||||
font-weight: 600;
|
||||
color: var(--dsv-primary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
ANIMATIONEN
|
||||
============================================ */
|
||||
|
||||
.dsv-widget.dsv-animating .dsv-door {
|
||||
transition: transform 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
|
||||
@keyframes dsv-pulse {
|
||||
0% { transform: translateX(-50%) scale(1); }
|
||||
50% { transform: translateX(-50%) scale(1.08); }
|
||||
100% { transform: translateX(-50%) scale(1); }
|
||||
}
|
||||
|
||||
.dsv-widget.dsv-just-voted .dsv-status-badge {
|
||||
animation: dsv-pulse 0.5s ease;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RESPONSIVE
|
||||
============================================ */
|
||||
|
||||
@media (max-width: 550px) {
|
||||
.dsv-widget {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.dsv-main-row {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.dsv-door-container {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.dsv-voting-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dsv-btn {
|
||||
padding: 11px 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.dsv-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
259
assets/js/admin.js
Normal file
259
assets/js/admin.js
Normal file
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* Door Status Voting - Admin JavaScript
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
$(document).ready(function() {
|
||||
initSettingsForm();
|
||||
initResetButtons();
|
||||
initResetByTypeButtons();
|
||||
initMismatchFilter();
|
||||
initMetaStatusButtons();
|
||||
});
|
||||
|
||||
/**
|
||||
* Settings Form Handler
|
||||
*/
|
||||
function initSettingsForm() {
|
||||
$('#dsv-settings-form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $form = $(this);
|
||||
var $btn = $form.find('.button-primary');
|
||||
var $status = $form.find('.dsv-save-status');
|
||||
|
||||
$btn.prop('disabled', true).text('Speichern...');
|
||||
$status.text('');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: dsvAdmin.ajaxUrl,
|
||||
data: {
|
||||
action: 'dsv_save_settings',
|
||||
nonce: dsvAdmin.nonce,
|
||||
disabled_posts: $('#dsv-disabled-posts').val()
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
$status.text('✓ Gespeichert').css('color', '#00a32a');
|
||||
} else {
|
||||
$status.text('✗ Fehler: ' + (response.data?.message || 'Unbekannt')).css('color', '#d63638');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$status.text('✗ Verbindungsfehler').css('color', '#d63638');
|
||||
},
|
||||
complete: function() {
|
||||
$btn.prop('disabled', false).text('Einstellungen speichern');
|
||||
|
||||
setTimeout(function() {
|
||||
$status.fadeOut(300, function() {
|
||||
$(this).text('').show();
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset All Buttons Handler
|
||||
*/
|
||||
function initResetButtons() {
|
||||
$(document).on('click', '.dsv-reset-btn', function() {
|
||||
var $btn = $(this);
|
||||
var $row = $btn.closest('tr');
|
||||
var postId = $btn.data('post-id');
|
||||
|
||||
if (!confirm('Alle Votes für diesen Beitrag wirklich zurücksetzen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$btn.prop('disabled', true).text('Löschen...');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: dsvAdmin.ajaxUrl,
|
||||
data: {
|
||||
action: 'dsv_reset_votes',
|
||||
nonce: dsvAdmin.nonce,
|
||||
post_id: postId
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
$row.fadeOut(300, function() {
|
||||
$(this).remove();
|
||||
checkEmptyTable();
|
||||
});
|
||||
} else {
|
||||
alert('Fehler: ' + (response.data?.message || 'Unbekannt'));
|
||||
$btn.prop('disabled', false).text('Alle löschen');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('Verbindungsfehler');
|
||||
$btn.prop('disabled', false).text('Alle löschen');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset By Type Buttons Handler
|
||||
*/
|
||||
function initResetByTypeButtons() {
|
||||
$(document).on('click', '.dsv-reset-type-btn', function() {
|
||||
var $btn = $(this);
|
||||
var $row = $btn.closest('tr');
|
||||
var postId = $btn.data('post-id');
|
||||
var type = $btn.data('type');
|
||||
var typeLabel = type === 'open' ? 'Geöffnet' : 'Geschlossen';
|
||||
|
||||
if (!confirm('Alle "' + typeLabel + '" Stimmen für diesen Beitrag löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var originalText = $btn.text();
|
||||
$btn.prop('disabled', true).text('...');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: dsvAdmin.ajaxUrl,
|
||||
data: {
|
||||
action: 'dsv_reset_votes_by_type',
|
||||
nonce: dsvAdmin.nonce,
|
||||
post_id: postId,
|
||||
type: type
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Zähler aktualisieren
|
||||
$row.find('.dsv-count-open').text(response.data.votes.open);
|
||||
$row.find('.dsv-count-closed').text(response.data.votes.closed);
|
||||
$row.find('.dsv-count-total').text(response.data.votes.open + response.data.votes.closed);
|
||||
|
||||
// Status Badge aktualisieren
|
||||
var $badge = $row.find('.dsv-badge');
|
||||
$badge.removeClass('dsv-badge-open dsv-badge-closed dsv-badge-tied');
|
||||
|
||||
if (response.data.votes.open > response.data.votes.closed) {
|
||||
$badge.addClass('dsv-badge-open').text('Geöffnet');
|
||||
} else if (response.data.votes.closed > response.data.votes.open) {
|
||||
$badge.addClass('dsv-badge-closed').text('Geschlossen');
|
||||
} else {
|
||||
$badge.addClass('dsv-badge-tied').text('Unklar');
|
||||
}
|
||||
|
||||
// Wenn keine Votes mehr, Zeile entfernen
|
||||
if (response.data.votes.open === 0 && response.data.votes.closed === 0) {
|
||||
$row.fadeOut(300, function() {
|
||||
$(this).remove();
|
||||
checkEmptyTable();
|
||||
});
|
||||
} else {
|
||||
// Kurzes Feedback
|
||||
$btn.text('✓').css('color', '#00a32a');
|
||||
setTimeout(function() {
|
||||
$btn.prop('disabled', false).text(originalText).css('color', '');
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
alert('Fehler: ' + (response.data?.message || 'Unbekannt'));
|
||||
$btn.prop('disabled', false).text(originalText);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('Verbindungsfehler');
|
||||
$btn.prop('disabled', false).text(originalText);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: Nur Abweichungen anzeigen
|
||||
*/
|
||||
function initMismatchFilter() {
|
||||
var $btn = $('#dsv-filter-mismatch');
|
||||
var active = false;
|
||||
|
||||
$btn.on('click', function() {
|
||||
active = !active;
|
||||
var $table = $('#dsv-votes-table');
|
||||
|
||||
if (active) {
|
||||
$table.find('tbody tr').not('.dsv-mismatch').hide();
|
||||
$btn.addClass('button-primary').text('Alle anzeigen');
|
||||
} else {
|
||||
$table.find('tbody tr').show();
|
||||
$btn.removeClass('button-primary').text('Nur Abweichungen anzeigen');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Meta-Status per Knopfdruck ändern
|
||||
*/
|
||||
function initMetaStatusButtons() {
|
||||
$(document).on('click', '.dsv-meta-set-btn', function() {
|
||||
var $btn = $(this);
|
||||
var $cell = $btn.closest('.dsv-meta-status-cell');
|
||||
var postId = $btn.data('post-id');
|
||||
var status = $btn.data('status');
|
||||
|
||||
$cell.find('.dsv-meta-set-btn').prop('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: dsvAdmin.ajaxUrl,
|
||||
data: {
|
||||
action: 'dsv_update_meta_status',
|
||||
nonce: dsvAdmin.nonce,
|
||||
post_id: postId,
|
||||
status: status
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Badge aktualisieren
|
||||
var badgeClass = 'dsv-badge-tied';
|
||||
var lower = status.toLowerCase();
|
||||
if (lower === 'geöffnet') badgeClass = 'dsv-badge-open';
|
||||
else if (lower === 'geschlossen') badgeClass = 'dsv-badge-closed';
|
||||
|
||||
var $badge = $cell.find('.dsv-badge, .dsv-meta-empty');
|
||||
if ($badge.hasClass('dsv-meta-empty')) {
|
||||
$badge.removeClass('dsv-meta-empty').addClass('dsv-badge');
|
||||
}
|
||||
$badge.removeClass('dsv-badge-open dsv-badge-closed dsv-badge-tied')
|
||||
.addClass(badgeClass)
|
||||
.text(status);
|
||||
|
||||
// Active-State der Buttons
|
||||
$cell.find('.dsv-meta-set-btn').removeClass('active');
|
||||
$btn.addClass('active');
|
||||
} else {
|
||||
alert('Fehler: ' + (response.data?.message || 'Unbekannt'));
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('Verbindungsfehler');
|
||||
},
|
||||
complete: function() {
|
||||
$cell.find('.dsv-meta-set-btn').prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüfen ob Tabelle leer ist
|
||||
*/
|
||||
function checkEmptyTable() {
|
||||
if ($('tbody tr').length === 0) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
257
assets/js/frontend.js
Normal file
257
assets/js/frontend.js
Normal file
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Door Status Voting - Frontend JavaScript
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initDoorVoting();
|
||||
});
|
||||
|
||||
function initDoorVoting() {
|
||||
const widgets = document.querySelectorAll('.dsv-widget');
|
||||
|
||||
widgets.forEach(function(widget) {
|
||||
// Vote Buttons
|
||||
const buttons = widget.querySelectorAll('.dsv-btn');
|
||||
buttons.forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
handleVote(widget, btn.dataset.vote);
|
||||
});
|
||||
});
|
||||
|
||||
// Remove Vote Button
|
||||
const removeBtn = widget.querySelector('.dsv-remove-vote');
|
||||
if (removeBtn) {
|
||||
removeBtn.addEventListener('click', function() {
|
||||
handleRemoveVote(widget);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleVote(widget, vote) {
|
||||
const postId = widget.dataset.postId;
|
||||
const hasVoted = widget.dataset.hasVoted === 'true';
|
||||
|
||||
if (hasVoted) return;
|
||||
|
||||
// Buttons deaktivieren
|
||||
const buttons = widget.querySelectorAll('.dsv-btn');
|
||||
buttons.forEach(function(btn) {
|
||||
btn.disabled = true;
|
||||
});
|
||||
|
||||
// Animation starten
|
||||
widget.classList.add('dsv-animating');
|
||||
|
||||
// AJAX Request
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'dsv_vote');
|
||||
formData.append('post_id', postId);
|
||||
formData.append('vote', vote);
|
||||
formData.append('nonce', dsvConfig.nonce);
|
||||
|
||||
fetch(dsvConfig.ajaxUrl, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
updateWidget(widget, data.data, vote);
|
||||
} else {
|
||||
console.error('Vote error:', data.data?.message);
|
||||
buttons.forEach(function(btn) {
|
||||
btn.disabled = false;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Network error:', error);
|
||||
buttons.forEach(function(btn) {
|
||||
btn.disabled = false;
|
||||
});
|
||||
})
|
||||
.finally(function() {
|
||||
widget.classList.remove('dsv-animating');
|
||||
});
|
||||
}
|
||||
|
||||
function handleRemoveVote(widget) {
|
||||
const postId = widget.dataset.postId;
|
||||
|
||||
// Button deaktivieren
|
||||
const removeBtn = widget.querySelector('.dsv-remove-vote');
|
||||
if (removeBtn) {
|
||||
removeBtn.disabled = true;
|
||||
removeBtn.textContent = '...';
|
||||
}
|
||||
|
||||
widget.classList.add('dsv-animating');
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'dsv_remove_vote');
|
||||
formData.append('post_id', postId);
|
||||
formData.append('nonce', dsvConfig.nonce);
|
||||
|
||||
fetch(dsvConfig.ajaxUrl, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
resetWidgetToVoting(widget, data.data);
|
||||
} else {
|
||||
console.error('Remove vote error:', data.data?.message);
|
||||
if (removeBtn) {
|
||||
removeBtn.disabled = false;
|
||||
removeBtn.textContent = '✕';
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Network error:', error);
|
||||
if (removeBtn) {
|
||||
removeBtn.disabled = false;
|
||||
removeBtn.textContent = '✕';
|
||||
}
|
||||
})
|
||||
.finally(function() {
|
||||
widget.classList.remove('dsv-animating');
|
||||
});
|
||||
}
|
||||
|
||||
function updateWidget(widget, data, userVote) {
|
||||
// Status aktualisieren
|
||||
widget.dataset.status = data.status;
|
||||
widget.dataset.hasVoted = 'true';
|
||||
widget.dataset.userVote = userVote;
|
||||
|
||||
// Badge aktualisieren
|
||||
const badge = widget.querySelector('.dsv-status-badge');
|
||||
badge.className = 'dsv-status-badge dsv-status-' + data.status;
|
||||
|
||||
if (data.status === 'open') {
|
||||
badge.innerHTML = '🟢 GEÖFFNET';
|
||||
} else if (data.status === 'closed') {
|
||||
badge.innerHTML = '🔴 GESCHLOSSEN';
|
||||
} else {
|
||||
badge.innerHTML = '🟡 UNKLAR';
|
||||
}
|
||||
|
||||
// Fortschrittsbalken aktualisieren
|
||||
updateProgressBar(widget, data);
|
||||
|
||||
// Buttons durch Bestätigung ersetzen
|
||||
const buttonsContainer = widget.querySelector('.dsv-buttons');
|
||||
buttonsContainer.classList.add('dsv-voted');
|
||||
|
||||
const voteClass = userVote === 'open' ? 'dsv-voted-open' : 'dsv-voted-closed';
|
||||
const voteIcon = userVote === 'open' ? '✓' : '✗';
|
||||
const voteText = userVote === 'open' ? 'Geöffnet' : 'Geschlossen';
|
||||
|
||||
buttonsContainer.innerHTML =
|
||||
'<div class="dsv-voted-message ' + voteClass + '">' +
|
||||
'<span class="dsv-voted-icon">' + voteIcon + '</span>' +
|
||||
'<span class="dsv-voted-text">Du hast für "' + voteText + '" gestimmt</span>' +
|
||||
'<button type="button" class="dsv-remove-vote" title="Stimme zurücknehmen">✕</button>' +
|
||||
'</div>';
|
||||
|
||||
// Event Listener für neuen Remove Button
|
||||
const newRemoveBtn = buttonsContainer.querySelector('.dsv-remove-vote');
|
||||
newRemoveBtn.addEventListener('click', function() {
|
||||
handleRemoveVote(widget);
|
||||
});
|
||||
|
||||
// Total aktualisieren
|
||||
widget.querySelector('.dsv-total').textContent = data.total;
|
||||
|
||||
// Pulse Animation
|
||||
widget.classList.add('dsv-just-voted');
|
||||
setTimeout(function() {
|
||||
widget.classList.remove('dsv-just-voted');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function resetWidgetToVoting(widget, data) {
|
||||
// Status aktualisieren
|
||||
widget.dataset.status = data.status;
|
||||
widget.dataset.hasVoted = 'false';
|
||||
widget.dataset.userVote = '';
|
||||
|
||||
// Badge aktualisieren
|
||||
const badge = widget.querySelector('.dsv-status-badge');
|
||||
badge.className = 'dsv-status-badge dsv-status-' + data.status;
|
||||
|
||||
if (data.status === 'open') {
|
||||
badge.innerHTML = '🟢 GEÖFFNET';
|
||||
} else if (data.status === 'closed') {
|
||||
badge.innerHTML = '🔴 GESCHLOSSEN';
|
||||
} else {
|
||||
badge.innerHTML = '🟡 UNKLAR';
|
||||
}
|
||||
|
||||
// Fortschrittsbalken aktualisieren
|
||||
updateProgressBar(widget, data);
|
||||
|
||||
// Voting Buttons wieder anzeigen
|
||||
const buttonsContainer = widget.querySelector('.dsv-buttons');
|
||||
buttonsContainer.classList.remove('dsv-voted');
|
||||
|
||||
buttonsContainer.innerHTML =
|
||||
'<button type="button" class="dsv-btn dsv-btn-open" data-vote="open">' +
|
||||
'<span class="dsv-btn-icon">✓</span>' +
|
||||
'<span class="dsv-btn-text">Geöffnet</span>' +
|
||||
'</button>' +
|
||||
'<button type="button" class="dsv-btn dsv-btn-closed" data-vote="closed">' +
|
||||
'<span class="dsv-btn-icon">✗</span>' +
|
||||
'<span class="dsv-btn-text">Geschlossen</span>' +
|
||||
'</button>';
|
||||
|
||||
// Event Listener für neue Buttons
|
||||
const newButtons = buttonsContainer.querySelectorAll('.dsv-btn');
|
||||
newButtons.forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
handleVote(widget, btn.dataset.vote);
|
||||
});
|
||||
});
|
||||
|
||||
// Total aktualisieren
|
||||
widget.querySelector('.dsv-total').textContent = data.total;
|
||||
}
|
||||
|
||||
function updateProgressBar(widget, data) {
|
||||
const progressOpen = widget.querySelector('.dsv-progress-open');
|
||||
const progressClosed = widget.querySelector('.dsv-progress-closed');
|
||||
|
||||
progressOpen.style.width = data.openPercent + '%';
|
||||
progressClosed.style.width = (100 - data.openPercent) + '%';
|
||||
|
||||
// Zähler
|
||||
if (data.openPercent > 15) {
|
||||
progressOpen.innerHTML = '<span class="dsv-progress-count">' + data.votes.open + '</span>';
|
||||
} else {
|
||||
progressOpen.innerHTML = '';
|
||||
}
|
||||
|
||||
if ((100 - data.openPercent) > 15) {
|
||||
progressClosed.innerHTML = '<span class="dsv-progress-count">' + data.votes.closed + '</span>';
|
||||
} else {
|
||||
progressClosed.innerHTML = '';
|
||||
}
|
||||
|
||||
// Labels
|
||||
widget.querySelector('.dsv-label-open').textContent = 'Geöffnet (' + data.openPercent + '%)';
|
||||
widget.querySelector('.dsv-label-closed').textContent = 'Geschlossen (' + (100 - data.openPercent) + '%)';
|
||||
}
|
||||
|
||||
})();
|
||||
735
door-status-voting.php
Normal file
735
door-status-voting.php
Normal file
@@ -0,0 +1,735 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Restaurant Tür-Status
|
||||
* Plugin URI: https://lucas-orth.de
|
||||
* Description: Interaktives Voting-Widget mit animierter Tür - Besucher können abstimmen ob ein Restaurant geöffnet oder geschlossen ist
|
||||
* Version: 1.1.0
|
||||
* Author: Lucas Orth
|
||||
* Author URI: https://lucas-orth.de
|
||||
* License: GPL v2 or later
|
||||
* Text Domain: door-status
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
define('DSV_VERSION', '1.0.0');
|
||||
define('DSV_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||
define('DSV_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||
|
||||
class DoorStatusVoting {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance() {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function __construct() {
|
||||
// Frontend
|
||||
add_shortcode('door_status', [$this, 'render_shortcode']);
|
||||
add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_assets']);
|
||||
|
||||
// AJAX Handlers
|
||||
add_action('wp_ajax_dsv_vote', [$this, 'handle_vote']);
|
||||
add_action('wp_ajax_nopriv_dsv_vote', [$this, 'handle_vote']);
|
||||
add_action('wp_ajax_dsv_remove_vote', [$this, 'handle_remove_vote']);
|
||||
add_action('wp_ajax_nopriv_dsv_remove_vote', [$this, 'handle_remove_vote']);
|
||||
add_action('wp_ajax_dsv_get_status', [$this, 'get_status']);
|
||||
add_action('wp_ajax_nopriv_dsv_get_status', [$this, 'get_status']);
|
||||
|
||||
// Admin
|
||||
add_action('admin_menu', [$this, 'add_admin_menu']);
|
||||
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
|
||||
add_action('wp_ajax_dsv_save_settings', [$this, 'save_settings']);
|
||||
add_action('wp_ajax_dsv_reset_votes', [$this, 'reset_votes']);
|
||||
add_action('wp_ajax_dsv_reset_votes_by_type', [$this, 'reset_votes_by_type']);
|
||||
add_action('wp_ajax_dsv_update_meta_status', [$this, 'update_meta_status']);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// FRONTEND
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Assets für Frontend laden
|
||||
*/
|
||||
public function enqueue_frontend_assets() {
|
||||
wp_enqueue_style(
|
||||
'dsv-frontend',
|
||||
DSV_PLUGIN_URL . 'assets/css/frontend.css',
|
||||
[],
|
||||
DSV_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'dsv-frontend',
|
||||
DSV_PLUGIN_URL . 'assets/js/frontend.js',
|
||||
[],
|
||||
DSV_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script('dsv-frontend', 'dsvConfig', [
|
||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('dsv_nonce')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcode rendern
|
||||
*/
|
||||
public function render_shortcode($atts) {
|
||||
$atts = shortcode_atts([
|
||||
'post_id' => get_the_ID()
|
||||
], $atts);
|
||||
|
||||
$post_id = intval($atts['post_id']);
|
||||
|
||||
// Prüfen ob für diesen Post deaktiviert
|
||||
$disabled_posts = get_option('dsv_disabled_posts', []);
|
||||
if (in_array($post_id, $disabled_posts)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Votes laden
|
||||
$votes = $this->get_votes($post_id);
|
||||
$total = $votes['open'] + $votes['closed'];
|
||||
$open_percent = $total > 0 ? round(($votes['open'] / $total) * 100) : 50;
|
||||
|
||||
// Status ermitteln
|
||||
if ($votes['open'] > $votes['closed']) {
|
||||
$status = 'open';
|
||||
$status_text = 'GEÖFFNET';
|
||||
$status_icon = '🟢';
|
||||
} elseif ($votes['closed'] > $votes['open']) {
|
||||
$status = 'closed';
|
||||
$status_text = 'GESCHLOSSEN';
|
||||
$status_icon = '🔴';
|
||||
} else {
|
||||
$status = 'tied';
|
||||
$status_text = 'UNKLAR';
|
||||
$status_icon = '🟡';
|
||||
}
|
||||
|
||||
// User Vote aus Cookie prüfen
|
||||
$cookie_name = 'dsv_vote_' . $post_id;
|
||||
$user_vote = isset($_COOKIE[$cookie_name]) ? sanitize_text_field($_COOKIE[$cookie_name]) : null;
|
||||
$has_voted = !empty($user_vote);
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="dsv-widget"
|
||||
data-post-id="<?php echo esc_attr($post_id); ?>"
|
||||
data-status="<?php echo esc_attr($status); ?>"
|
||||
data-has-voted="<?php echo $has_voted ? 'true' : 'false'; ?>"
|
||||
data-user-vote="<?php echo esc_attr($user_vote); ?>">
|
||||
|
||||
<h3 class="dsv-title">Ist das Restaurant noch geöffnet?</h3>
|
||||
|
||||
<div class="dsv-main-row">
|
||||
<!-- Tür Container -->
|
||||
<div class="dsv-door-container">
|
||||
|
||||
<!-- Status Badge -->
|
||||
<div class="dsv-status-badge dsv-status-<?php echo esc_attr($status); ?>">
|
||||
<?php echo $status_icon; ?> <?php echo esc_html($status_text); ?>
|
||||
</div>
|
||||
|
||||
<!-- Türrahmen -->
|
||||
<div class="dsv-door-frame">
|
||||
|
||||
<!-- Licht hinter der Tür -->
|
||||
<div class="dsv-light"></div>
|
||||
|
||||
<!-- Lichtstrahlen -->
|
||||
<div class="dsv-light-rays">
|
||||
<div class="dsv-ray"></div>
|
||||
<div class="dsv-ray"></div>
|
||||
<div class="dsv-ray"></div>
|
||||
<div class="dsv-ray"></div>
|
||||
<div class="dsv-ray"></div>
|
||||
</div>
|
||||
|
||||
<!-- Die Tür -->
|
||||
<div class="dsv-door">
|
||||
<div class="dsv-door-grain"></div>
|
||||
<div class="dsv-door-panel dsv-door-panel-top"></div>
|
||||
<div class="dsv-door-panel dsv-door-panel-bottom"></div>
|
||||
<div class="dsv-door-handle">
|
||||
<div class="dsv-handle-plate"></div>
|
||||
<div class="dsv-handle-lever"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schatten -->
|
||||
<div class="dsv-shadow"></div>
|
||||
</div>
|
||||
|
||||
<!-- Rechte Seite: Voting -->
|
||||
<div class="dsv-voting-section">
|
||||
<!-- Fortschrittsbalken -->
|
||||
<div class="dsv-progress-container">
|
||||
<div class="dsv-progress-bar">
|
||||
<div class="dsv-progress-open" style="width: <?php echo $open_percent; ?>%">
|
||||
<?php if ($open_percent > 15): ?>
|
||||
<span class="dsv-progress-count"><?php echo $votes['open']; ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="dsv-progress-closed" style="width: <?php echo 100 - $open_percent; ?>%">
|
||||
<?php if ((100 - $open_percent) > 15): ?>
|
||||
<span class="dsv-progress-count"><?php echo $votes['closed']; ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="dsv-progress-divider"></div>
|
||||
</div>
|
||||
<div class="dsv-progress-labels">
|
||||
<span class="dsv-label-open">Geöffnet (<?php echo $open_percent; ?>%)</span>
|
||||
<span class="dsv-label-closed">Geschlossen (<?php echo 100 - $open_percent; ?>%)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Voting Buttons -->
|
||||
<div class="dsv-buttons <?php echo $has_voted ? 'dsv-voted' : ''; ?>">
|
||||
<?php if (!$has_voted): ?>
|
||||
<button type="button" class="dsv-btn dsv-btn-open" data-vote="open">
|
||||
<span class="dsv-btn-icon">✓</span>
|
||||
<span class="dsv-btn-text">Geöffnet</span>
|
||||
</button>
|
||||
<button type="button" class="dsv-btn dsv-btn-closed" data-vote="closed">
|
||||
<span class="dsv-btn-icon">✗</span>
|
||||
<span class="dsv-btn-text">Geschlossen</span>
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<div class="dsv-voted-message dsv-voted-<?php echo esc_attr($user_vote); ?>">
|
||||
<span class="dsv-voted-icon"><?php echo $user_vote === 'open' ? '✓' : '✗'; ?></span>
|
||||
<span class="dsv-voted-text">
|
||||
Du hast für "<?php echo $user_vote === 'open' ? 'Geöffnet' : 'Geschlossen'; ?>" gestimmt
|
||||
</span>
|
||||
<button type="button" class="dsv-remove-vote" title="Stimme zurücknehmen">✕</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Teilnehmer Info -->
|
||||
<p class="dsv-info">
|
||||
<span class="dsv-total"><?php echo $total; ?></span> Besucher haben abgestimmt
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Votes aus Post Meta laden
|
||||
*/
|
||||
private function get_votes($post_id) {
|
||||
$data = get_post_meta($post_id, '_dsv_votes', true);
|
||||
if (!is_array($data)) {
|
||||
return ['open' => 0, 'closed' => 0];
|
||||
}
|
||||
|
||||
$open = $closed = 0;
|
||||
foreach ($data as $vote) {
|
||||
if ($vote === 'open') $open++;
|
||||
if ($vote === 'closed') $closed++;
|
||||
}
|
||||
return ['open' => $open, 'closed' => $closed];
|
||||
}
|
||||
|
||||
/**
|
||||
* User-Key generieren (IP Hash)
|
||||
*/
|
||||
private function get_user_key() {
|
||||
$ip = '';
|
||||
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
|
||||
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
|
||||
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];
|
||||
} else {
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
}
|
||||
return md5($ip . wp_salt());
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Vote speichern
|
||||
*/
|
||||
public function handle_vote() {
|
||||
check_ajax_referer('dsv_nonce', 'nonce');
|
||||
|
||||
$post_id = intval($_POST['post_id'] ?? 0);
|
||||
$vote = sanitize_text_field($_POST['vote'] ?? '');
|
||||
|
||||
if (!$post_id || !in_array($vote, ['open', 'closed'])) {
|
||||
wp_send_json_error(['message' => 'Ungültige Anfrage']);
|
||||
}
|
||||
|
||||
// Prüfen ob deaktiviert
|
||||
$disabled_posts = get_option('dsv_disabled_posts', []);
|
||||
if (in_array($post_id, $disabled_posts)) {
|
||||
wp_send_json_error(['message' => 'Voting deaktiviert']);
|
||||
}
|
||||
|
||||
$user_key = $this->get_user_key();
|
||||
$data = get_post_meta($post_id, '_dsv_votes', true);
|
||||
if (!is_array($data)) $data = [];
|
||||
|
||||
// Bereits gevotet? (Server-side Check)
|
||||
if (isset($data[$user_key])) {
|
||||
wp_send_json_error(['message' => 'Bereits abgestimmt']);
|
||||
}
|
||||
|
||||
// Vote speichern
|
||||
$data[$user_key] = $vote;
|
||||
update_post_meta($post_id, '_dsv_votes', $data);
|
||||
|
||||
// Cookie setzen (30 Tage)
|
||||
$cookie_name = 'dsv_vote_' . $post_id;
|
||||
setcookie($cookie_name, $vote, time() + (30 * DAY_IN_SECONDS), COOKIEPATH, COOKIE_DOMAIN);
|
||||
|
||||
// Neue Votes zurückgeben
|
||||
$votes = $this->get_votes($post_id);
|
||||
$total = $votes['open'] + $votes['closed'];
|
||||
$open_percent = $total > 0 ? round(($votes['open'] / $total) * 100) : 50;
|
||||
|
||||
wp_send_json_success([
|
||||
'votes' => $votes,
|
||||
'total' => $total,
|
||||
'openPercent' => $open_percent,
|
||||
'status' => $votes['open'] > $votes['closed'] ? 'open' :
|
||||
($votes['closed'] > $votes['open'] ? 'closed' : 'tied')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Aktuellen Status abrufen
|
||||
*/
|
||||
public function get_status() {
|
||||
$post_id = intval($_GET['post_id'] ?? 0);
|
||||
|
||||
if (!$post_id) {
|
||||
wp_send_json_error(['message' => 'Ungültige Post ID']);
|
||||
}
|
||||
|
||||
$votes = $this->get_votes($post_id);
|
||||
$total = $votes['open'] + $votes['closed'];
|
||||
$open_percent = $total > 0 ? round(($votes['open'] / $total) * 100) : 50;
|
||||
|
||||
wp_send_json_success([
|
||||
'votes' => $votes,
|
||||
'total' => $total,
|
||||
'openPercent' => $open_percent,
|
||||
'status' => $votes['open'] > $votes['closed'] ? 'open' :
|
||||
($votes['closed'] > $votes['open'] ? 'closed' : 'tied')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Vote entfernen
|
||||
*/
|
||||
public function handle_remove_vote() {
|
||||
check_ajax_referer('dsv_nonce', 'nonce');
|
||||
|
||||
$post_id = intval($_POST['post_id'] ?? 0);
|
||||
|
||||
if (!$post_id) {
|
||||
wp_send_json_error(['message' => 'Ungültige Anfrage']);
|
||||
}
|
||||
|
||||
$user_key = $this->get_user_key();
|
||||
$data = get_post_meta($post_id, '_dsv_votes', true);
|
||||
|
||||
if (!is_array($data) || !isset($data[$user_key])) {
|
||||
wp_send_json_error(['message' => 'Kein Vote gefunden']);
|
||||
}
|
||||
|
||||
// Vote entfernen
|
||||
unset($data[$user_key]);
|
||||
update_post_meta($post_id, '_dsv_votes', $data);
|
||||
|
||||
// Cookie löschen
|
||||
$cookie_name = 'dsv_vote_' . $post_id;
|
||||
setcookie($cookie_name, '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN);
|
||||
|
||||
// Neue Votes zurückgeben
|
||||
$votes = $this->get_votes($post_id);
|
||||
$total = $votes['open'] + $votes['closed'];
|
||||
$open_percent = $total > 0 ? round(($votes['open'] / $total) * 100) : 50;
|
||||
|
||||
wp_send_json_success([
|
||||
'votes' => $votes,
|
||||
'total' => $total,
|
||||
'openPercent' => $open_percent,
|
||||
'status' => $votes['open'] > $votes['closed'] ? 'open' :
|
||||
($votes['closed'] > $votes['open'] ? 'closed' : 'tied')
|
||||
]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// ADMIN
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Admin Menü unter Werkzeuge
|
||||
*/
|
||||
public function add_admin_menu() {
|
||||
add_management_page(
|
||||
'Tür-Status Voting',
|
||||
'Tür-Status',
|
||||
'manage_options',
|
||||
'door-status-settings',
|
||||
[$this, 'render_admin_page']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Assets laden
|
||||
*/
|
||||
public function enqueue_admin_assets($hook) {
|
||||
if ($hook !== 'tools_page_door-status-settings') {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_style(
|
||||
'dsv-admin',
|
||||
DSV_PLUGIN_URL . 'assets/css/admin.css',
|
||||
[],
|
||||
DSV_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'dsv-admin',
|
||||
DSV_PLUGIN_URL . 'assets/js/admin.js',
|
||||
['jquery'],
|
||||
DSV_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script('dsv-admin', 'dsvAdmin', [
|
||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('dsv_admin_nonce')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Seite rendern
|
||||
*/
|
||||
public function render_admin_page() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_die('Keine Berechtigung');
|
||||
}
|
||||
|
||||
$disabled_posts = get_option('dsv_disabled_posts', []);
|
||||
|
||||
// Alle Posts mit Votes holen
|
||||
global $wpdb;
|
||||
$posts_with_votes = $wpdb->get_col(
|
||||
"SELECT DISTINCT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_dsv_votes'"
|
||||
);
|
||||
|
||||
?>
|
||||
<div class="wrap dsv-admin-wrap">
|
||||
<h1>🚪 Tür-Status Voting</h1>
|
||||
|
||||
<div class="dsv-admin-grid">
|
||||
|
||||
<!-- Shortcode Info -->
|
||||
<div class="dsv-admin-card">
|
||||
<h2>Shortcode</h2>
|
||||
<p>Füge das Voting-Widget mit folgendem Shortcode ein:</p>
|
||||
<code class="dsv-shortcode">[door_status]</code>
|
||||
<p class="description">
|
||||
Der Shortcode verwendet automatisch die aktuelle Post-ID.
|
||||
Du kannst ihn in Posts, Seiten oder Page-Builder einfügen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Deaktivierte Posts -->
|
||||
<div class="dsv-admin-card">
|
||||
<h2>Voting deaktivieren</h2>
|
||||
<p>Gib Post-IDs ein, für die das Voting deaktiviert werden soll (kommagetrennt):</p>
|
||||
<form id="dsv-settings-form">
|
||||
<textarea
|
||||
name="disabled_posts"
|
||||
id="dsv-disabled-posts"
|
||||
class="large-text"
|
||||
rows="3"
|
||||
placeholder="z.B. 123, 456, 789"
|
||||
><?php echo esc_textarea(implode(', ', $disabled_posts)); ?></textarea>
|
||||
<p class="description">
|
||||
Der Shortcode wird auf diesen Seiten nichts ausgeben.
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit" class="button button-primary">
|
||||
Einstellungen speichern
|
||||
</button>
|
||||
<span class="dsv-save-status"></span>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Statistiken -->
|
||||
<div class="dsv-admin-card dsv-admin-card-full">
|
||||
<h2>Voting Übersicht</h2>
|
||||
|
||||
<?php if (empty($posts_with_votes)): ?>
|
||||
<p class="dsv-no-data">Noch keine Votes vorhanden.</p>
|
||||
<?php else: ?>
|
||||
<p>
|
||||
<button type="button" id="dsv-filter-mismatch" class="button">
|
||||
Nur Abweichungen anzeigen
|
||||
</button>
|
||||
</p>
|
||||
<table class="wp-list-table widefat fixed striped" id="dsv-votes-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Post</th>
|
||||
<th>Meta-Status</th>
|
||||
<th>Voting-Status</th>
|
||||
<th>Geöffnet</th>
|
||||
<th>Geschlossen</th>
|
||||
<th>Gesamt</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($posts_with_votes as $pid):
|
||||
$post = get_post($pid);
|
||||
if (!$post) continue;
|
||||
|
||||
$votes = $this->get_votes($pid);
|
||||
$total = $votes['open'] + $votes['closed'];
|
||||
$is_disabled = in_array($pid, $disabled_posts);
|
||||
|
||||
if ($votes['open'] > $votes['closed']) {
|
||||
$status_badge = '<span class="dsv-badge dsv-badge-open">Geöffnet</span>';
|
||||
} elseif ($votes['closed'] > $votes['open']) {
|
||||
$status_badge = '<span class="dsv-badge dsv-badge-closed">Geschlossen</span>';
|
||||
} else {
|
||||
$status_badge = '<span class="dsv-badge dsv-badge-tied">Unklar</span>';
|
||||
}
|
||||
|
||||
// JetEngine Meta-Feld "status" auslesen
|
||||
$meta_status = get_post_meta($pid, 'status', true);
|
||||
$meta_status_lower = mb_strtolower(trim($meta_status));
|
||||
if (in_array($meta_status_lower, ['geöffnet', 'geoeffnet', 'open', 'offen'])) {
|
||||
$meta_badge_class = 'dsv-badge-open';
|
||||
$meta_voting_match = ($votes['open'] > $votes['closed']);
|
||||
} elseif (in_array($meta_status_lower, ['geschlossen', 'closed', 'zu'])) {
|
||||
$meta_badge_class = 'dsv-badge-closed';
|
||||
$meta_voting_match = ($votes['closed'] > $votes['open']);
|
||||
} elseif (in_array($meta_status_lower, ['neuer betreiber'])) {
|
||||
$meta_badge_class = 'dsv-badge-tied';
|
||||
$meta_voting_match = false;
|
||||
} elseif (!empty($meta_status)) {
|
||||
$meta_badge_class = 'dsv-badge-tied';
|
||||
$meta_voting_match = false;
|
||||
} else {
|
||||
$meta_badge_class = '';
|
||||
$meta_voting_match = true; // kein Meta = kein Abweichung
|
||||
}
|
||||
|
||||
if (!empty($meta_status)) {
|
||||
$meta_status_display = '<span class="dsv-badge ' . $meta_badge_class . '">' . esc_html($meta_status) . '</span>';
|
||||
} else {
|
||||
$meta_status_display = '<span class="dsv-meta-empty">—</span>';
|
||||
}
|
||||
|
||||
$row_mismatch = !$meta_voting_match;
|
||||
?>
|
||||
<tr data-post-id="<?php echo esc_attr($pid); ?>"<?php if ($row_mismatch): ?> class="dsv-mismatch"<?php endif; ?>>
|
||||
<td>
|
||||
<strong><a href="<?php echo get_edit_post_link($pid); ?>" target="_blank"><?php echo esc_html($post->post_title); ?></a></strong>
|
||||
<br>
|
||||
<small>ID: <?php echo $pid; ?></small>
|
||||
<?php if ($is_disabled): ?>
|
||||
<br><em class="dsv-disabled-label">Voting deaktiviert</em>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="dsv-meta-status-cell">
|
||||
<?php echo $meta_status_display; ?>
|
||||
<div class="dsv-meta-buttons">
|
||||
<button type="button" class="button button-small dsv-meta-set-btn<?php echo ($meta_status_lower === 'geöffnet') ? ' active' : ''; ?>" data-post-id="<?php echo esc_attr($pid); ?>" data-status="geöffnet" title="Geöffnet">🟢</button>
|
||||
<button type="button" class="button button-small dsv-meta-set-btn<?php echo ($meta_status_lower === 'geschlossen') ? ' active' : ''; ?>" data-post-id="<?php echo esc_attr($pid); ?>" data-status="geschlossen" title="Geschlossen">🔴</button>
|
||||
</div>
|
||||
</td>
|
||||
<td><?php echo $status_badge; ?></td>
|
||||
<td class="dsv-count-open"><?php echo $votes['open']; ?></td>
|
||||
<td class="dsv-count-closed"><?php echo $votes['closed']; ?></td>
|
||||
<td class="dsv-count-total"><?php echo $total; ?></td>
|
||||
<td class="dsv-actions-cell">
|
||||
<div class="dsv-action-buttons">
|
||||
<button type="button"
|
||||
class="button button-small dsv-reset-type-btn"
|
||||
data-post-id="<?php echo esc_attr($pid); ?>"
|
||||
data-type="open"
|
||||
title="Alle 'Geöffnet' Stimmen löschen">
|
||||
🟢 Löschen
|
||||
</button>
|
||||
<button type="button"
|
||||
class="button button-small dsv-reset-type-btn"
|
||||
data-post-id="<?php echo esc_attr($pid); ?>"
|
||||
data-type="closed"
|
||||
title="Alle 'Geschlossen' Stimmen löschen">
|
||||
🔴 Löschen
|
||||
</button>
|
||||
<button type="button"
|
||||
class="button button-small dsv-reset-btn"
|
||||
data-post-id="<?php echo esc_attr($pid); ?>"
|
||||
title="Alle Stimmen löschen">
|
||||
Alle löschen
|
||||
</button>
|
||||
<a href="<?php echo get_permalink($pid); ?>"
|
||||
target="_blank"
|
||||
class="button button-small">
|
||||
Ansehen
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Einstellungen speichern
|
||||
*/
|
||||
public function save_settings() {
|
||||
check_ajax_referer('dsv_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error(['message' => 'Keine Berechtigung']);
|
||||
}
|
||||
|
||||
$disabled_posts_raw = sanitize_text_field($_POST['disabled_posts'] ?? '');
|
||||
$disabled_posts = [];
|
||||
|
||||
if (!empty($disabled_posts_raw)) {
|
||||
$ids = explode(',', $disabled_posts_raw);
|
||||
foreach ($ids as $id) {
|
||||
$id = intval(trim($id));
|
||||
if ($id > 0) {
|
||||
$disabled_posts[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_option('dsv_disabled_posts', $disabled_posts);
|
||||
|
||||
wp_send_json_success(['message' => 'Gespeichert']);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Votes zurücksetzen
|
||||
*/
|
||||
public function reset_votes() {
|
||||
check_ajax_referer('dsv_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error(['message' => 'Keine Berechtigung']);
|
||||
}
|
||||
|
||||
$post_id = intval($_POST['post_id'] ?? 0);
|
||||
|
||||
if (!$post_id) {
|
||||
wp_send_json_error(['message' => 'Ungültige Post ID']);
|
||||
}
|
||||
|
||||
delete_post_meta($post_id, '_dsv_votes');
|
||||
|
||||
wp_send_json_success(['message' => 'Votes zurückgesetzt']);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Votes nach Typ zurücksetzen (nur open oder nur closed)
|
||||
*/
|
||||
public function reset_votes_by_type() {
|
||||
check_ajax_referer('dsv_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error(['message' => 'Keine Berechtigung']);
|
||||
}
|
||||
|
||||
$post_id = intval($_POST['post_id'] ?? 0);
|
||||
$type = sanitize_text_field($_POST['type'] ?? '');
|
||||
|
||||
if (!$post_id || !in_array($type, ['open', 'closed'])) {
|
||||
wp_send_json_error(['message' => 'Ungültige Parameter']);
|
||||
}
|
||||
|
||||
$data = get_post_meta($post_id, '_dsv_votes', true);
|
||||
if (!is_array($data)) {
|
||||
wp_send_json_error(['message' => 'Keine Votes vorhanden']);
|
||||
}
|
||||
|
||||
// Nur Votes des angegebenen Typs entfernen
|
||||
$removed = 0;
|
||||
foreach ($data as $key => $vote) {
|
||||
if ($vote === $type) {
|
||||
unset($data[$key]);
|
||||
$removed++;
|
||||
}
|
||||
}
|
||||
|
||||
update_post_meta($post_id, '_dsv_votes', $data);
|
||||
|
||||
// Neue Zahlen berechnen
|
||||
$votes = $this->get_votes($post_id);
|
||||
|
||||
wp_send_json_success([
|
||||
'message' => $removed . ' Stimmen gelöscht',
|
||||
'votes' => $votes,
|
||||
'removed' => $removed
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Meta-Status ändern
|
||||
*/
|
||||
public function update_meta_status() {
|
||||
check_ajax_referer('dsv_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error(['message' => 'Keine Berechtigung']);
|
||||
}
|
||||
|
||||
$post_id = intval($_POST['post_id'] ?? 0);
|
||||
$status = sanitize_text_field($_POST['status'] ?? '');
|
||||
|
||||
if (!$post_id || !in_array($status, ['geöffnet', 'geschlossen'])) {
|
||||
wp_send_json_error(['message' => 'Ungültige Parameter']);
|
||||
}
|
||||
|
||||
update_post_meta($post_id, 'status', $status);
|
||||
|
||||
wp_send_json_success(['message' => 'Status aktualisiert', 'status' => $status]);
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin initialisieren
|
||||
DoorStatusVoting::get_instance();
|
||||
|
||||
// Aktivierungs-Hook
|
||||
register_activation_hook(__FILE__, function() {
|
||||
add_option('dsv_disabled_posts', []);
|
||||
});
|
||||
|
||||
// Deaktivierungs-Hook
|
||||
register_deactivation_hook(__FILE__, function() {
|
||||
// Optional: Daten behalten oder löschen
|
||||
});
|
||||
49
readme.txt
Normal file
49
readme.txt
Normal file
@@ -0,0 +1,49 @@
|
||||
=== Restaurant Tür-Status ===
|
||||
Contributors: developer
|
||||
Tags: voting, restaurant, status, door, animation
|
||||
Requires at least: 5.0
|
||||
Tested up to: 6.4
|
||||
Stable tag: 1.0.0
|
||||
License: GPLv2 or later
|
||||
|
||||
Interaktives Voting-Widget mit animierter Tür - Besucher können abstimmen ob ein Restaurant geöffnet oder geschlossen ist.
|
||||
|
||||
== Beschreibung ==
|
||||
|
||||
Das Plugin zeigt eine animierte Holztür, die sich je nach Abstimmungsergebnis öffnet oder schließt. Besucher können mit einem Klick angeben, ob ein Restaurant noch geöffnet oder bereits geschlossen ist.
|
||||
|
||||
**Features:**
|
||||
|
||||
* Animierte 3D-Holztür mit Lichteffekten
|
||||
* Einfaches Voting mit einem Klick
|
||||
* Fortschrittsbalken zeigt Abstimmungsverhältnis
|
||||
* Cookie-basierte Duplikat-Verhinderung
|
||||
* Admin-Bereich unter Werkzeuge
|
||||
* Voting für einzelne Posts deaktivierbar
|
||||
* Responsive Design
|
||||
|
||||
== Installation ==
|
||||
|
||||
1. Plugin-Ordner nach `/wp-content/plugins/` hochladen
|
||||
2. Plugin im WordPress Admin aktivieren
|
||||
3. Shortcode `[door_status]` in Posts/Seiten einfügen
|
||||
|
||||
== Verwendung ==
|
||||
|
||||
**Shortcode:**
|
||||
```
|
||||
[door_status]
|
||||
```
|
||||
|
||||
Der Shortcode verwendet automatisch die aktuelle Post-ID.
|
||||
|
||||
**Admin-Bereich:**
|
||||
Unter *Werkzeuge > Tür-Status* findest du:
|
||||
* Übersicht aller Posts mit Votes
|
||||
* Möglichkeit, Voting für bestimmte Posts zu deaktivieren
|
||||
* Button zum Zurücksetzen von Votes
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 1.0.0 =
|
||||
* Erste Version
|
||||
Reference in New Issue
Block a user