feat: Implement the Instagram Gallery Sync Pro WordPress plugin.
This commit is contained in:
279
README.md
Normal file
279
README.md
Normal file
@@ -0,0 +1,279 @@
|
||||
# Instagram Gallery Sync Pro
|
||||
|
||||
[](https://wordpress.org/)
|
||||
[](https://php.net/)
|
||||
[](https://www.gnu.org/licenses/gpl-2.0.html)
|
||||
|
||||
Ein leistungsstarkes WordPress-Plugin zur Synchronisierung und Anzeige von Instagram-Fotos ohne offizielle API.
|
||||
|
||||
**Entwickelt von [Keil & Schick](https://keil-schick.de)**
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- **Automatische Synchronisierung** – Holt regelmäßig neue Bilder von öffentlichen Instagram-Profilen
|
||||
- **Lokale Bildspeicherung** – Bilder werden auf deinem Server gespeichert (bessere Performance)
|
||||
- **5 Layout-Optionen** – Grid, Masonry, Justified, Slider, List
|
||||
- **Vollständig responsive** – Separate Spalteneinstellungen für Desktop, Tablet & Mobile
|
||||
- **Lightbox-Integration** – GLightbox für elegante Bildanzeige
|
||||
- **Gutenberg-Block** – Nativer Block-Editor mit Live-Vorschau
|
||||
- **Shortcode-Unterstützung** – Flexibel mit zahlreichen Parametern
|
||||
- **6 Hover-Effekte** – Zoom, Fade, Overlay, Grayscale, Lift, None
|
||||
- **Custom CSS** – Eigene Styles direkt im Admin-Bereich
|
||||
- **Activity Logs** – Detaillierte Protokollierung aller Sync-Aktivitäten
|
||||
- **i18n-ready** – Vorbereitet für Übersetzungen (DE/EN)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Anforderungen
|
||||
|
||||
- WordPress 5.0 oder höher
|
||||
- PHP 7.4 oder höher
|
||||
- Öffentliches Instagram-Profil
|
||||
|
||||
---
|
||||
|
||||
## 📥 Installation
|
||||
|
||||
### Manuelle Installation
|
||||
|
||||
1. Lade den `instagram-gallery-sync-pro` Ordner herunter
|
||||
2. Kopiere ihn nach `/wp-content/plugins/`
|
||||
3. Aktiviere das Plugin unter **Plugins** im WordPress-Admin
|
||||
4. Gehe zu **Instagram Gallery** in der Sidebar
|
||||
|
||||
### Via WordPress Admin
|
||||
|
||||
1. Gehe zu **Plugins → Installieren**
|
||||
2. Klicke auf **Plugin hochladen**
|
||||
3. Wähle die ZIP-Datei aus
|
||||
4. Klicke auf **Jetzt installieren** und dann **Aktivieren**
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Konfiguration
|
||||
|
||||
### Grundeinrichtung
|
||||
|
||||
1. Navigiere zu **Instagram Gallery** im Admin-Menü
|
||||
2. Gib deinen Instagram-Benutzernamen ein (ohne @)
|
||||
3. Wähle die gewünschte Bildanzahl und Qualität
|
||||
4. Klicke auf **Sync Now**
|
||||
|
||||
### Einstellungs-Tabs
|
||||
|
||||
| Tab | Beschreibung |
|
||||
|-----|--------------|
|
||||
| **Instagram** | Username, Sync-Intervall, Bildqualität |
|
||||
| **Layout** | Grid-Typ, Spalten, Abstände, Hover-Effekte |
|
||||
| **Anzeige** | Limit, Sortierung, Lightbox, Lazy Loading |
|
||||
| **Styling** | Farben, Schriftgrößen, Custom CSS |
|
||||
| **Erweitert** | Debug-Modus, Cache, Proxy, Logs |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Verwendung
|
||||
|
||||
### Shortcode
|
||||
|
||||
Einfachste Verwendung:
|
||||
|
||||
```
|
||||
[instagram_gallery]
|
||||
```
|
||||
|
||||
Mit Parametern:
|
||||
|
||||
```
|
||||
[instagram_gallery layout="masonry" columns="4" limit="12" spacing="15"]
|
||||
```
|
||||
|
||||
#### Alle Parameter
|
||||
|
||||
| Parameter | Werte | Standard |
|
||||
|-----------|-------|----------|
|
||||
| `layout` | `grid`, `masonry`, `slider`, `justified`, `list` | `grid` |
|
||||
| `columns` | `1` - `6` | `3` |
|
||||
| `spacing` | `0` - `50` (px) | `10` |
|
||||
| `limit` | `1` - `50` | `12` |
|
||||
| `order` | `newest`, `oldest`, `random` | `newest` |
|
||||
| `lightbox` | `yes`, `no` | `yes` |
|
||||
| `captions` | `yes`, `no` | `no` |
|
||||
| `autoplay` | `true`, `false` (nur Slider) | `false` |
|
||||
| `class` | CSS-Klassenname | – |
|
||||
|
||||
### Gutenberg Block
|
||||
|
||||
1. Öffne den Block-Editor
|
||||
2. Suche nach "Instagram Gallery"
|
||||
3. Füge den Block hinzu
|
||||
4. Konfiguriere über die Sidebar-Einstellungen
|
||||
|
||||
### PHP Template
|
||||
|
||||
```php
|
||||
<?php
|
||||
if (function_exists('display_instagram_gallery')) {
|
||||
display_instagram_gallery(array(
|
||||
'layout' => 'grid',
|
||||
'columns' => 3,
|
||||
'limit' => 9
|
||||
));
|
||||
}
|
||||
?>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Styling
|
||||
|
||||
### CSS-Variablen
|
||||
|
||||
Das Plugin verwendet CSS Custom Properties für einfache Anpassung:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--igsp-primary: #e1306c;
|
||||
--igsp-hover: #c13584;
|
||||
--igsp-text: #ffffff;
|
||||
--igsp-font-size: 14px;
|
||||
--igsp-radius: 0px;
|
||||
}
|
||||
```
|
||||
|
||||
### CSS-Klassen
|
||||
|
||||
| Klasse | Beschreibung |
|
||||
|--------|--------------|
|
||||
| `.igsp-gallery` | Container |
|
||||
| `.igsp-item` | Einzelnes Bild |
|
||||
| `.igsp-image` | Bild-Element |
|
||||
| `.igsp-overlay` | Caption-Overlay |
|
||||
| `.igsp-follow-btn` | Instagram-Button |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Synchronisierung
|
||||
|
||||
### Automatisch
|
||||
|
||||
Das Plugin synchronisiert automatisch basierend auf dem konfigurierten Intervall:
|
||||
- Alle 30 Minuten
|
||||
- Stündlich
|
||||
- Alle 6 Stunden
|
||||
- Täglich
|
||||
- Wöchentlich
|
||||
|
||||
### Manuell
|
||||
|
||||
Klicke auf **Sync Now** im Instagram-Tab der Einstellungen.
|
||||
|
||||
### Scraping-Methoden
|
||||
|
||||
Das Plugin verwendet mehrere Fallback-Methoden:
|
||||
|
||||
1. **Web Profile Info API** – Instagrams interne Web-API
|
||||
2. **Embed Page Parsing** – Parst die Embed-Seiten
|
||||
3. **Profile Page Parsing** – HTML-Analyse mit mehreren Strategien
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Wichtige Hinweise
|
||||
|
||||
> **Hinweis:** Instagram blockiert Scraping aktiv. Das Plugin enthält Anti-Blocking-Maßnahmen, aber 100% Zuverlässigkeit kann nicht garantiert werden.
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Verwende **lange Sync-Intervalle** (6h+)
|
||||
- Aktiviere **lokale Bildspeicherung**
|
||||
- Nutze **Caching** für bessere Performance
|
||||
- Halte die **Bildanzahl** moderat (12-24)
|
||||
|
||||
### Problembehandlung
|
||||
|
||||
| Problem | Lösung |
|
||||
|---------|--------|
|
||||
| Keine Bilder | Prüfe ob Profil öffentlich ist |
|
||||
| Rate Limiting | Erhöhe Sync-Intervall |
|
||||
| Timeout | Erhöhe Request-Timeout in Erweitert |
|
||||
| Parsing-Fehler | Prüfe Logs, warte und versuche erneut |
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Datenbank
|
||||
|
||||
Das Plugin erstellt zwei Tabellen:
|
||||
|
||||
- `wp_instagram_gallery_posts` – Gespeicherte Bilder
|
||||
- `wp_instagram_gallery_log` – Aktivitätsprotokolle
|
||||
|
||||
Bei Deinstallation werden alle Daten vollständig entfernt.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Dateistruktur
|
||||
|
||||
```
|
||||
instagram-gallery-sync-pro/
|
||||
├── instagram-gallery-sync-pro.php # Hauptdatei
|
||||
├── uninstall.php # Deinstallations-Routine
|
||||
├── includes/
|
||||
│ ├── class-admin.php # Admin-Handler
|
||||
│ ├── class-cron.php # Cron-Jobs
|
||||
│ ├── class-database.php # Datenbank-Operationen
|
||||
│ ├── class-gutenberg-block.php # Block-Editor
|
||||
│ ├── class-image-handler.php # Bild-Download & -Verarbeitung
|
||||
│ ├── class-logger.php # Logging-System
|
||||
│ ├── class-scraper.php # Instagram-Scraper
|
||||
│ └── class-shortcode.php # Shortcode-Handler
|
||||
├── admin/
|
||||
│ ├── css/admin-style.css
|
||||
│ ├── js/admin-script.js
|
||||
│ ├── js/ajax-sync.js
|
||||
│ └── views/ # Admin-Templates
|
||||
├── public/
|
||||
│ ├── css/ # Frontend-Styles
|
||||
│ └── js/ # Frontend-Scripts + Libs
|
||||
└── languages/ # Übersetzungsdateien
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Sicherheit
|
||||
|
||||
- Prepared Statements für alle DB-Queries
|
||||
- Nonce-Verifizierung für AJAX-Requests
|
||||
- Capability-Checks (`manage_options`)
|
||||
- Input-Sanitization für alle Benutzereingaben
|
||||
- Output-Escaping mit `esc_html()`, `esc_attr()`, etc.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Changelog
|
||||
|
||||
### 1.0.0 (2026-01-22)
|
||||
- Erste Veröffentlichung
|
||||
- 5 Layout-Typen (Grid, Masonry, Justified, Slider, List)
|
||||
- Gutenberg Block mit Live-Vorschau
|
||||
- Shortcode mit allen Parametern
|
||||
- Admin-Bereich mit 5 Tabs
|
||||
- Multi-Methoden Scraping Engine
|
||||
- Lokale Bildspeicherung mit Thumbnails
|
||||
- Activity Logging System
|
||||
|
||||
---
|
||||
|
||||
## 📄 Lizenz
|
||||
|
||||
GPL-2.0-or-later © [Keil & Schick](https://keil-schick.de)
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
Für Fragen und Support besuche: [https://keil-schick.de](https://keil-schick.de)
|
||||
|
||||
---
|
||||
|
||||
*Entwickelt mit ❤️ in Deutschland*
|
||||
BIN
admin/.DS_Store
vendored
Normal file
BIN
admin/.DS_Store
vendored
Normal file
Binary file not shown.
649
admin/css/admin-style.css
Normal file
649
admin/css/admin-style.css
Normal file
@@ -0,0 +1,649 @@
|
||||
/**
|
||||
* Instagram Gallery Sync Pro - Admin Styles
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
/* ============================================
|
||||
Variables
|
||||
============================================ */
|
||||
:root {
|
||||
--igsp-primary: #e1306c;
|
||||
--igsp-primary-hover: #c13584;
|
||||
--igsp-success: #28a745;
|
||||
--igsp-warning: #ffc107;
|
||||
--igsp-error: #dc3545;
|
||||
--igsp-info: #17a2b8;
|
||||
--igsp-dark: #1e1e1e;
|
||||
--igsp-gray: #6c757d;
|
||||
--igsp-light: #f8f9fa;
|
||||
--igsp-border: #e2e4e7;
|
||||
--igsp-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
--igsp-radius: 8px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Layout
|
||||
============================================ */
|
||||
.igsp-admin-wrap {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 300px;
|
||||
gap: 20px;
|
||||
margin: 20px 20px 20px 0;
|
||||
}
|
||||
|
||||
.igsp-admin-header {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, var(--igsp-primary), var(--igsp-primary-hover));
|
||||
color: #fff;
|
||||
border-radius: var(--igsp-radius);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.igsp-admin-header h1 {
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.igsp-admin-header .dashicons {
|
||||
font-size: 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.igsp-version {
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.igsp-admin-content {
|
||||
background: #fff;
|
||||
border-radius: var(--igsp-radius);
|
||||
box-shadow: var(--igsp-shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Tabs Navigation
|
||||
============================================ */
|
||||
.igsp-tabs-nav {
|
||||
display: flex;
|
||||
background: var(--igsp-light);
|
||||
border-bottom: 1px solid var(--igsp-border);
|
||||
padding: 0 20px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.igsp-tab-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 15px 20px;
|
||||
text-decoration: none;
|
||||
color: var(--igsp-gray);
|
||||
font-weight: 500;
|
||||
border-bottom: 3px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.igsp-tab-link:hover {
|
||||
color: var(--igsp-primary);
|
||||
}
|
||||
|
||||
.igsp-tab-link.active {
|
||||
color: var(--igsp-primary);
|
||||
border-bottom-color: var(--igsp-primary);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.igsp-tab-link .dashicons {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Tab Content
|
||||
============================================ */
|
||||
.igsp-tab-content {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.igsp-section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.igsp-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.igsp-section h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--igsp-dark);
|
||||
margin: 0 0 20px 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--igsp-border);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Form Elements
|
||||
============================================ */
|
||||
.igsp-settings-form .form-table th {
|
||||
padding: 20px 10px 20px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.igsp-settings-form .form-table td {
|
||||
padding: 15px 10px;
|
||||
}
|
||||
|
||||
.igsp-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.igsp-input-prefix {
|
||||
background: var(--igsp-light);
|
||||
border: 1px solid var(--igsp-border);
|
||||
border-right: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: var(--igsp-radius) 0 0 var(--igsp-radius);
|
||||
color: var(--igsp-gray);
|
||||
}
|
||||
|
||||
.igsp-input-wrapper input {
|
||||
border-radius: 0 var(--igsp-radius) var(--igsp-radius) 0 !important;
|
||||
}
|
||||
|
||||
/* Slider Wrapper */
|
||||
.igsp-slider-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.igsp-slider-wrapper input[type="range"] {
|
||||
flex: 1;
|
||||
-webkit-appearance: none;
|
||||
height: 6px;
|
||||
background: var(--igsp-border);
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.igsp-slider-wrapper input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: var(--igsp-primary);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.igsp-slider-wrapper input[type="range"]::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.igsp-slider-value {
|
||||
min-width: 50px;
|
||||
padding: 5px 10px;
|
||||
background: var(--igsp-light);
|
||||
border-radius: var(--igsp-radius);
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Inline Fields */
|
||||
.igsp-inline-fields {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Layout Selector
|
||||
============================================ */
|
||||
.igsp-layout-selector {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 15px;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.igsp-layout-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border: 2px solid var(--igsp-border);
|
||||
border-radius: var(--igsp-radius);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.igsp-layout-option:hover {
|
||||
border-color: var(--igsp-primary);
|
||||
}
|
||||
|
||||
.igsp-layout-option.selected {
|
||||
border-color: var(--igsp-primary);
|
||||
background: rgba(225, 48, 108, 0.05);
|
||||
}
|
||||
|
||||
.igsp-layout-option input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.igsp-layout-preview {
|
||||
width: 100px;
|
||||
height: 70px;
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.igsp-layout-preview span {
|
||||
background: var(--igsp-border);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.igsp-preview-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.igsp-preview-masonry {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.igsp-preview-justified {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.igsp-preview-justified span {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.igsp-preview-slider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.igsp-preview-slider .slide {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.igsp-preview-slider .arrow-left,
|
||||
.igsp-preview-slider .arrow-right {
|
||||
background: none;
|
||||
color: var(--igsp-gray);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.igsp-preview-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.igsp-layout-name {
|
||||
font-weight: 500;
|
||||
color: var(--igsp-dark);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Sidebar
|
||||
============================================ */
|
||||
.igsp-admin-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.igsp-sidebar-box {
|
||||
background: #fff;
|
||||
border-radius: var(--igsp-radius);
|
||||
box-shadow: var(--igsp-shadow);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.igsp-sidebar-box h3 {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--igsp-dark);
|
||||
}
|
||||
|
||||
/* Status Box */
|
||||
.igsp-status-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--igsp-border);
|
||||
}
|
||||
|
||||
.igsp-status-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.igsp-status-label {
|
||||
color: var(--igsp-gray);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.igsp-status-value {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Shortcode Box */
|
||||
.igsp-shortcode-display {
|
||||
display: block;
|
||||
padding: 12px;
|
||||
background: var(--igsp-dark);
|
||||
color: #fff;
|
||||
border-radius: var(--igsp-radius);
|
||||
font-size: 13px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Help Box */
|
||||
.igsp-help-box ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.igsp-help-box li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.igsp-help-box li .dashicons {
|
||||
color: var(--igsp-primary);
|
||||
}
|
||||
|
||||
.igsp-help-box a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Progress Bar
|
||||
============================================ */
|
||||
.igsp-progress-wrapper {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.igsp-progress-bar {
|
||||
height: 8px;
|
||||
background: var(--igsp-light);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.igsp-progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--igsp-primary), var(--igsp-primary-hover));
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
animation: igsp-progress-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes igsp-progress-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
|
||||
.igsp-progress-text {
|
||||
font-size: 13px;
|
||||
color: var(--igsp-gray);
|
||||
}
|
||||
|
||||
/* Sync Result */
|
||||
.igsp-sync-result {
|
||||
margin-top: 15px;
|
||||
padding: 12px;
|
||||
border-radius: var(--igsp-radius);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.igsp-sync-result.success {
|
||||
background: rgba(40, 167, 69, 0.1);
|
||||
color: var(--igsp-success);
|
||||
}
|
||||
|
||||
.igsp-sync-result.error {
|
||||
background: rgba(220, 53, 69, 0.1);
|
||||
color: var(--igsp-error);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Tools Grid
|
||||
============================================ */
|
||||
.igsp-tools-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.igsp-tool-card {
|
||||
padding: 20px;
|
||||
background: var(--igsp-light);
|
||||
border-radius: var(--igsp-radius);
|
||||
}
|
||||
|
||||
.igsp-tool-card h3 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.igsp-tool-card p {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 13px;
|
||||
color: var(--igsp-gray);
|
||||
}
|
||||
|
||||
.igsp-tool-danger {
|
||||
background: rgba(220, 53, 69, 0.05);
|
||||
}
|
||||
|
||||
.button-danger {
|
||||
background: var(--igsp-error) !important;
|
||||
border-color: var(--igsp-error) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.button-danger:hover {
|
||||
background: #c82333 !important;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Logs
|
||||
============================================ */
|
||||
.igsp-logs-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.igsp-logs-header h2 {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.igsp-logs-table-wrapper {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--igsp-border);
|
||||
border-radius: var(--igsp-radius);
|
||||
}
|
||||
|
||||
.igsp-logs-table {
|
||||
margin: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.igsp-logs-table th {
|
||||
background: var(--igsp-light);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.igsp-log-type { width: 100px; }
|
||||
.igsp-log-date { width: 120px; }
|
||||
|
||||
.igsp-log-badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.igsp-log-success { background: rgba(40, 167, 69, 0.1); color: var(--igsp-success); }
|
||||
.igsp-log-error { background: rgba(220, 53, 69, 0.1); color: var(--igsp-error); }
|
||||
.igsp-log-warning { background: rgba(255, 193, 7, 0.1); color: #856404; }
|
||||
.igsp-log-info { background: rgba(23, 162, 184, 0.1); color: var(--igsp-info); }
|
||||
|
||||
.igsp-no-logs {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: var(--igsp-gray);
|
||||
background: var(--igsp-light);
|
||||
border-radius: var(--igsp-radius);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Style Preview
|
||||
============================================ */
|
||||
.igsp-style-preview {
|
||||
padding: 30px;
|
||||
background: var(--igsp-light);
|
||||
border-radius: var(--igsp-radius);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.igsp-preview-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.igsp-preview-image {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: var(--igsp-radius);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.igsp-preview-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--hover);
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.igsp-preview-image:hover .igsp-preview-overlay {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.igsp-preview-caption {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.igsp-preview-button {
|
||||
background: var(--primary) !important;
|
||||
color: var(--text) !important;
|
||||
border: none !important;
|
||||
padding: 10px 25px !important;
|
||||
border-radius: var(--igsp-radius) !important;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.igsp-preview-button:hover {
|
||||
background: var(--hover) !important;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Color Picker
|
||||
============================================ */
|
||||
.wp-picker-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Responsive
|
||||
============================================ */
|
||||
@media (max-width: 1200px) {
|
||||
.igsp-admin-wrap {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.igsp-admin-sidebar {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.igsp-sidebar-box {
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 782px) {
|
||||
.igsp-tabs-nav {
|
||||
flex-wrap: wrap;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.igsp-tab-link {
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.igsp-tab-link .dashicons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.igsp-layout-selector {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
1
admin/index.php
Normal file
1
admin/index.php
Normal file
@@ -0,0 +1 @@
|
||||
<?php // Silence is golden
|
||||
286
admin/js/admin-script.js
Normal file
286
admin/js/admin-script.js
Normal file
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* Instagram Gallery Sync Pro - Admin Script
|
||||
*
|
||||
* Handles admin UI interactions
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Admin Controller
|
||||
*/
|
||||
const IGSPAdmin = {
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
init: function() {
|
||||
this.initColorPickers();
|
||||
this.initSliders();
|
||||
this.initLayoutSelector();
|
||||
this.initConditionalFields();
|
||||
this.initSyncButton();
|
||||
this.initToolButtons();
|
||||
this.initTabPersistence();
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize color pickers
|
||||
*/
|
||||
initColorPickers: function() {
|
||||
$('.igsp-color-picker').wpColorPicker({
|
||||
change: function(event, ui) {
|
||||
// Update live preview
|
||||
IGSPAdmin.updatePreview();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize range sliders
|
||||
*/
|
||||
initSliders: function() {
|
||||
$('input[type="range"]').on('input change', function() {
|
||||
const $slider = $(this);
|
||||
const $value = $slider.siblings('.igsp-slider-value');
|
||||
let value = $slider.val();
|
||||
|
||||
// Handle special cases
|
||||
const id = $slider.attr('id');
|
||||
|
||||
if (id === 'igsp_auto_delete_days') {
|
||||
value = value == 0 ? igspAdmin.strings.never || 'Never' : value + ' days';
|
||||
} else if (id === 'igsp_spacing' || id === 'igsp_padding' ||
|
||||
id === 'igsp_border_radius' || id === 'igsp_caption_font_size') {
|
||||
value += 'px';
|
||||
} else if (id === 'igsp_request_timeout') {
|
||||
value += 's';
|
||||
}
|
||||
|
||||
$value.text(value);
|
||||
|
||||
// Update live preview if applicable
|
||||
if (id === 'igsp_caption_font_size') {
|
||||
IGSPAdmin.updatePreview();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize layout selector
|
||||
*/
|
||||
initLayoutSelector: function() {
|
||||
$('.igsp-layout-option input').on('change', function() {
|
||||
$('.igsp-layout-option').removeClass('selected');
|
||||
$(this).closest('.igsp-layout-option').addClass('selected');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize conditional fields
|
||||
*/
|
||||
initConditionalFields: function() {
|
||||
$('[data-depends-on]').each(function() {
|
||||
const $field = $(this);
|
||||
const dependsOn = $field.data('depends-on');
|
||||
const dependsValue = $field.data('depends-value');
|
||||
|
||||
function checkVisibility() {
|
||||
const $input = $('[name="' + dependsOn + '"]:checked, [name="' + dependsOn + '"]').filter(':checked, select');
|
||||
const currentValue = $input.val();
|
||||
|
||||
if (currentValue === dependsValue) {
|
||||
$field.show();
|
||||
} else {
|
||||
$field.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Initial check
|
||||
checkVisibility();
|
||||
|
||||
// Listen for changes
|
||||
$('[name="' + dependsOn + '"]').on('change', checkVisibility);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize sync button
|
||||
*/
|
||||
initSyncButton: function() {
|
||||
$('#igsp-sync-now').on('click', function() {
|
||||
const $button = $(this);
|
||||
const $progress = $('#igsp-sync-progress');
|
||||
const $result = $('#igsp-sync-result');
|
||||
|
||||
// Disable button
|
||||
$button.prop('disabled', true);
|
||||
$progress.show();
|
||||
$result.hide();
|
||||
|
||||
// Animate progress bar
|
||||
const $fill = $progress.find('.igsp-progress-fill');
|
||||
$fill.css('width', '0%');
|
||||
|
||||
// Simulate progress
|
||||
let progress = 0;
|
||||
const progressInterval = setInterval(function() {
|
||||
progress += Math.random() * 15;
|
||||
if (progress > 90) progress = 90;
|
||||
$fill.css('width', progress + '%');
|
||||
}, 500);
|
||||
|
||||
// Make AJAX request
|
||||
$.ajax({
|
||||
url: igspAdmin.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'igsp_manual_sync',
|
||||
nonce: igspAdmin.nonce
|
||||
},
|
||||
success: function(response) {
|
||||
clearInterval(progressInterval);
|
||||
$fill.css('width', '100%');
|
||||
|
||||
setTimeout(function() {
|
||||
$progress.hide();
|
||||
$result.removeClass('success error');
|
||||
|
||||
if (response.success) {
|
||||
$result.addClass('success').html(response.data.message).show();
|
||||
IGSPAdmin.updateSyncStatus();
|
||||
} else {
|
||||
$result.addClass('error').html(response.data.message || igspAdmin.strings.syncError).show();
|
||||
}
|
||||
|
||||
$button.prop('disabled', false);
|
||||
}, 500);
|
||||
},
|
||||
error: function() {
|
||||
clearInterval(progressInterval);
|
||||
$progress.hide();
|
||||
$result.addClass('error').html(igspAdmin.strings.syncError).show();
|
||||
$button.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize tool buttons
|
||||
*/
|
||||
initToolButtons: function() {
|
||||
// Clear cache
|
||||
$('#igsp-clear-cache').on('click', function() {
|
||||
const $button = $(this);
|
||||
$button.prop('disabled', true).text(igspAdmin.strings.processing);
|
||||
|
||||
$.post(igspAdmin.ajaxUrl, {
|
||||
action: 'igsp_clear_cache',
|
||||
nonce: igspAdmin.nonce
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
$button.text(igspAdmin.strings.done);
|
||||
setTimeout(function() {
|
||||
$button.prop('disabled', false).html('<span class="dashicons dashicons-trash"></span> Clear Cache');
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Reset data
|
||||
$('#igsp-reset-data').on('click', function() {
|
||||
if (!confirm(igspAdmin.strings.confirmReset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $button = $(this);
|
||||
$button.prop('disabled', true).text(igspAdmin.strings.processing);
|
||||
|
||||
$.post(igspAdmin.ajaxUrl, {
|
||||
action: 'igsp_reset_data',
|
||||
nonce: igspAdmin.nonce
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Clear logs
|
||||
$('#igsp-clear-logs').on('click', function() {
|
||||
if (!confirm(igspAdmin.strings.confirmClearLogs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $button = $(this);
|
||||
$button.prop('disabled', true);
|
||||
|
||||
$.post(igspAdmin.ajaxUrl, {
|
||||
action: 'igsp_clear_logs',
|
||||
nonce: igspAdmin.nonce
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update sync status
|
||||
*/
|
||||
updateSyncStatus: function() {
|
||||
$.post(igspAdmin.ajaxUrl, {
|
||||
action: 'igsp_get_sync_status',
|
||||
nonce: igspAdmin.nonce
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
$('#igsp-total-posts').text(response.data.total_posts);
|
||||
$('#igsp-last-sync').text(response.data.last_sync);
|
||||
$('#igsp-next-sync').text(response.data.next_sync);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update live preview
|
||||
*/
|
||||
updatePreview: function() {
|
||||
const primaryColor = $('#igsp_primary_color').val() || '#e1306c';
|
||||
const hoverColor = $('#igsp_hover_color').val() || '#c13584';
|
||||
const textColor = $('#igsp_text_color').val() || '#ffffff';
|
||||
const fontSize = $('#igsp_caption_font_size').val() || 14;
|
||||
|
||||
$('.igsp-preview-item').css({
|
||||
'--primary': primaryColor,
|
||||
'--hover': hoverColor,
|
||||
'--text': textColor
|
||||
});
|
||||
|
||||
$('.igsp-preview-caption').css('font-size', fontSize + 'px');
|
||||
},
|
||||
|
||||
/**
|
||||
* Tab persistence
|
||||
*/
|
||||
initTabPersistence: function() {
|
||||
// Store current tab in localStorage
|
||||
$('.igsp-tab-link').on('click', function() {
|
||||
const tab = $(this).attr('href').split('tab=')[1];
|
||||
if (tab) {
|
||||
localStorage.setItem('igsp_active_tab', tab);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize when document is ready
|
||||
$(document).ready(function() {
|
||||
IGSPAdmin.init();
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
182
admin/js/ajax-sync.js
Normal file
182
admin/js/ajax-sync.js
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Instagram Gallery Sync Pro - AJAX Sync Handler
|
||||
*
|
||||
* Handles manual sync with progress feedback
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Sync Controller
|
||||
*/
|
||||
const IGSPSync = {
|
||||
|
||||
isRunning: false,
|
||||
|
||||
/**
|
||||
* Start sync process
|
||||
*/
|
||||
start: function () {
|
||||
if (this.isRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isRunning = true;
|
||||
|
||||
const $button = $('#igsp-sync-now');
|
||||
const $progress = $('#igsp-sync-progress');
|
||||
const $progressFill = $progress.find('.igsp-progress-fill');
|
||||
const $progressText = $progress.find('.igsp-progress-text');
|
||||
const $result = $('#igsp-sync-result');
|
||||
|
||||
// Reset UI
|
||||
$button.prop('disabled', true);
|
||||
$progress.show();
|
||||
$result.hide().removeClass('success error');
|
||||
$progressFill.css('width', '0%');
|
||||
|
||||
// Start progress animation
|
||||
this.animateProgress($progressFill, $progressText);
|
||||
|
||||
// Execute sync
|
||||
this.executeSync($button, $progress, $progressFill, $result);
|
||||
},
|
||||
|
||||
/**
|
||||
* Animate progress bar
|
||||
*/
|
||||
animateProgress: function ($fill, $text) {
|
||||
const stages = [
|
||||
{ progress: 20, text: 'Connecting to Instagram...' },
|
||||
{ progress: 40, text: 'Fetching profile data...' },
|
||||
{ progress: 60, text: 'Downloading images...' },
|
||||
{ progress: 80, text: 'Saving to database...' },
|
||||
{ progress: 90, text: 'Finalizing...' }
|
||||
];
|
||||
|
||||
let stageIndex = 0;
|
||||
|
||||
this.progressInterval = setInterval(function () {
|
||||
if (stageIndex < stages.length && IGSPSync.isRunning) {
|
||||
$fill.css('width', stages[stageIndex].progress + '%');
|
||||
$text.text(stages[stageIndex].text);
|
||||
stageIndex++;
|
||||
}
|
||||
}, 1500);
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute sync via AJAX
|
||||
*/
|
||||
executeSync: function ($button, $progress, $fill, $result) {
|
||||
const self = this;
|
||||
|
||||
$.ajax({
|
||||
url: igspAdmin.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'igsp_manual_sync',
|
||||
nonce: igspAdmin.nonce
|
||||
},
|
||||
timeout: 120000, // 2 minute timeout
|
||||
success: function (response) {
|
||||
self.complete($button, $progress, $fill, $result, response);
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
let message = igspAdmin.strings.syncError;
|
||||
|
||||
if (status === 'timeout') {
|
||||
message = 'Sync timed out. The process may still be running in the background.';
|
||||
} else if (xhr.responseJSON && xhr.responseJSON.data) {
|
||||
message = xhr.responseJSON.data.message;
|
||||
}
|
||||
|
||||
self.complete($button, $progress, $fill, $result, {
|
||||
success: false,
|
||||
data: { message: message }
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Complete sync process
|
||||
*/
|
||||
complete: function ($button, $progress, $fill, $result, response) {
|
||||
clearInterval(this.progressInterval);
|
||||
this.isRunning = false;
|
||||
|
||||
// Complete progress bar
|
||||
$fill.css('width', '100%');
|
||||
|
||||
setTimeout(function () {
|
||||
$progress.hide();
|
||||
|
||||
if (response.success) {
|
||||
$result
|
||||
.addClass('success')
|
||||
.html(IGSPSync.formatSuccessMessage(response.data))
|
||||
.show();
|
||||
|
||||
// Update sidebar status
|
||||
IGSPSync.updateStatus();
|
||||
} else {
|
||||
$result
|
||||
.addClass('error')
|
||||
.html(response.data.message || igspAdmin.strings.syncError)
|
||||
.show();
|
||||
}
|
||||
|
||||
$button.prop('disabled', false);
|
||||
}, 500);
|
||||
},
|
||||
|
||||
/**
|
||||
* Format success message
|
||||
*/
|
||||
formatSuccessMessage: function (data) {
|
||||
let html = '<strong>✓ ' + igspAdmin.strings.syncComplete + '</strong><br>';
|
||||
|
||||
if (data.posts_added > 0) {
|
||||
html += '<span>Added: ' + data.posts_added + ' new posts</span><br>';
|
||||
}
|
||||
|
||||
if (data.posts_updated > 0) {
|
||||
html += '<span>Updated: ' + data.posts_updated + ' existing posts</span><br>';
|
||||
}
|
||||
|
||||
if (data.posts_skipped > 0) {
|
||||
html += '<span>Skipped: ' + data.posts_skipped + ' posts</span>';
|
||||
}
|
||||
|
||||
if (data.errors && data.errors.length > 0) {
|
||||
html += '<br><small style="color: #dc3545;">Warnings: ' + data.errors.length + '</small>';
|
||||
}
|
||||
|
||||
return html;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update sync status in sidebar
|
||||
*/
|
||||
updateStatus: function () {
|
||||
$.post(igspAdmin.ajaxUrl, {
|
||||
action: 'igsp_get_sync_status',
|
||||
nonce: igspAdmin.nonce
|
||||
}, function (response) {
|
||||
if (response.success) {
|
||||
$('#igsp-total-posts').text(response.data.total_posts);
|
||||
$('#igsp-last-sync').text(response.data.last_sync);
|
||||
$('#igsp-next-sync').text(response.data.next_sync);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Make it available globally
|
||||
window.IGSPSync = IGSPSync;
|
||||
|
||||
})(jQuery);
|
||||
122
admin/views/settings-page.php
Normal file
122
admin/views/settings-page.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin Settings Page Template
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$admin = new IGSP_Admin();
|
||||
$current_tab = $admin->get_current_tab();
|
||||
$tabs = $admin->get_tabs();
|
||||
?>
|
||||
<div class="wrap igsp-admin-wrap">
|
||||
<div class="igsp-admin-header">
|
||||
<h1>
|
||||
<span class="dashicons dashicons-instagram"></span>
|
||||
<?php esc_html_e('Instagram Gallery Sync Pro', 'instagram-gallery-sync-pro'); ?>
|
||||
</h1>
|
||||
<p class="igsp-version">
|
||||
<?php echo esc_html(sprintf(__('Version %s', 'instagram-gallery-sync-pro'), IGSP_VERSION)); ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="igsp-admin-content">
|
||||
<!-- Tabs Navigation -->
|
||||
<nav class="igsp-tabs-nav">
|
||||
<?php foreach ($tabs as $tab_id => $tab): ?>
|
||||
<a href="<?php echo esc_url(admin_url('admin.php?page=' . IGSP_Admin::PAGE_SLUG . '&tab=' . $tab_id)); ?>"
|
||||
class="igsp-tab-link <?php echo $current_tab === $tab_id ? 'active' : ''; ?>">
|
||||
<span class="dashicons <?php echo esc_attr($tab['icon']); ?>"></span>
|
||||
<?php echo esc_html($tab['title']); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="igsp-tab-content">
|
||||
<?php
|
||||
$tab_file = IGSP_PLUGIN_DIR . 'admin/views/tab-' . $current_tab . '.php';
|
||||
|
||||
if (file_exists($tab_file)) {
|
||||
include $tab_file;
|
||||
} else {
|
||||
echo '<p>' . esc_html__('Tab content not found.', 'instagram-gallery-sync-pro') . '</p>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="igsp-admin-sidebar">
|
||||
<div class="igsp-sidebar-box igsp-status-box">
|
||||
<h3>
|
||||
<?php esc_html_e('Sync Status', 'instagram-gallery-sync-pro'); ?>
|
||||
</h3>
|
||||
<div class="igsp-status-info" id="igsp-status-info">
|
||||
<?php
|
||||
$cron = new IGSP_Cron();
|
||||
$status = $cron->get_sync_status();
|
||||
?>
|
||||
<div class="igsp-status-row">
|
||||
<span class="igsp-status-label">
|
||||
<?php esc_html_e('Total Posts:', 'instagram-gallery-sync-pro'); ?>
|
||||
</span>
|
||||
<span class="igsp-status-value" id="igsp-total-posts">
|
||||
<?php echo esc_html($status['total_posts']); ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="igsp-status-row">
|
||||
<span class="igsp-status-label">
|
||||
<?php esc_html_e('Last Sync:', 'instagram-gallery-sync-pro'); ?>
|
||||
</span>
|
||||
<span class="igsp-status-value" id="igsp-last-sync">
|
||||
<?php echo esc_html($status['last_sync']); ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="igsp-status-row">
|
||||
<span class="igsp-status-label">
|
||||
<?php esc_html_e('Next Sync:', 'instagram-gallery-sync-pro'); ?>
|
||||
</span>
|
||||
<span class="igsp-status-value" id="igsp-next-sync">
|
||||
<?php echo esc_html($status['next_sync']); ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="igsp-sidebar-box igsp-shortcode-box">
|
||||
<h3>
|
||||
<?php esc_html_e('Shortcode', 'instagram-gallery-sync-pro'); ?>
|
||||
</h3>
|
||||
<code class="igsp-shortcode-display">[instagram_gallery]</code>
|
||||
<p class="description">
|
||||
<?php esc_html_e('Use this shortcode to display the gallery on any page or post.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="igsp-sidebar-box igsp-help-box">
|
||||
<h3>
|
||||
<?php esc_html_e('Need Help?', 'instagram-gallery-sync-pro'); ?>
|
||||
</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="dashicons dashicons-book"></span>
|
||||
<a href="#" target="_blank">
|
||||
<?php esc_html_e('Documentation', 'instagram-gallery-sync-pro'); ?>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<span class="dashicons dashicons-sos"></span>
|
||||
<a href="#" target="_blank">
|
||||
<?php esc_html_e('Support', 'instagram-gallery-sync-pro'); ?>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
264
admin/views/tab-advanced.php
Normal file
264
admin/views/tab-advanced.php
Normal file
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
/**
|
||||
* Advanced Settings Tab
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$logger = new IGSP_Logger();
|
||||
$logs = $logger->get_formatted_logs(50);
|
||||
$image_handler = new IGSP_Image_Handler();
|
||||
?>
|
||||
|
||||
<form method="post" action="options.php" class="igsp-settings-form" id="igsp-advanced-form">
|
||||
<?php settings_fields('igsp_advanced_settings'); ?>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Debug & Development', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Debug Mode', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<?php $debug = get_option('igsp_debug_mode', 'no'); ?>
|
||||
<fieldset>
|
||||
<label>
|
||||
<input type="radio" name="igsp_debug_mode" value="yes" <?php checked($debug, 'yes'); ?>>
|
||||
<?php esc_html_e('Enabled', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="igsp_debug_mode" value="no" <?php checked($debug, 'no'); ?>>
|
||||
<?php esc_html_e('Disabled', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</fieldset>
|
||||
<p class="description">
|
||||
<?php esc_html_e('Enable to log all sync activities. Disable in production for better performance.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Performance', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_cache_duration">
|
||||
<?php esc_html_e('Cache Duration', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php $cache = get_option('igsp_cache_duration', 3600); ?>
|
||||
<select id="igsp_cache_duration" name="igsp_cache_duration">
|
||||
<option value="3600" <?php selected($cache, 3600); ?>>
|
||||
<?php esc_html_e('1 Hour', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="21600" <?php selected($cache, 21600); ?>>
|
||||
<?php esc_html_e('6 Hours', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="43200" <?php selected($cache, 43200); ?>>
|
||||
<?php esc_html_e('12 Hours', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="86400" <?php selected($cache, 86400); ?>>
|
||||
<?php esc_html_e('24 Hours', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
</select>
|
||||
<p class="description">
|
||||
<?php esc_html_e('How long to cache the gallery HTML.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Request Timeout', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-slider-wrapper">
|
||||
<input type="range" id="igsp_request_timeout" name="igsp_request_timeout" min="10" max="60"
|
||||
value="<?php echo esc_attr(get_option('igsp_request_timeout', 30)); ?>">
|
||||
<span class="igsp-slider-value">
|
||||
<?php echo esc_html(get_option('igsp_request_timeout', 30)); ?>s
|
||||
</span>
|
||||
</div>
|
||||
<p class="description">
|
||||
<?php esc_html_e('Timeout for Instagram requests in seconds.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Auto-Delete Old Posts', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-slider-wrapper">
|
||||
<input type="range" id="igsp_auto_delete_days" name="igsp_auto_delete_days" min="0" max="365"
|
||||
step="30" value="<?php echo esc_attr(get_option('igsp_auto_delete_days', 0)); ?>">
|
||||
<span class="igsp-slider-value">
|
||||
<?php
|
||||
$days = get_option('igsp_auto_delete_days', 0);
|
||||
echo $days === 0 ? esc_html__('Never', 'instagram-gallery-sync-pro') : esc_html($days . ' ' . __('days', 'instagram-gallery-sync-pro'));
|
||||
?>
|
||||
</span>
|
||||
</div>
|
||||
<p class="description">
|
||||
<?php esc_html_e('Automatically delete posts older than X days. Set to 0 to disable.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Request Configuration', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_user_agent">
|
||||
<?php esc_html_e('Custom User-Agent', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" id="igsp_user_agent" name="igsp_user_agent"
|
||||
value="<?php echo esc_attr(get_option('igsp_user_agent', '')); ?>" class="large-text"
|
||||
placeholder="<?php esc_attr_e('Leave empty to use rotating User-Agents', 'instagram-gallery-sync-pro'); ?>">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Proxy Settings', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-inline-fields">
|
||||
<input type="text" id="igsp_proxy_host" name="igsp_proxy_host"
|
||||
value="<?php echo esc_attr(get_option('igsp_proxy_host', '')); ?>"
|
||||
placeholder="<?php esc_attr_e('Proxy Host', 'instagram-gallery-sync-pro'); ?>"
|
||||
class="regular-text">
|
||||
<input type="number" id="igsp_proxy_port" name="igsp_proxy_port"
|
||||
value="<?php echo esc_attr(get_option('igsp_proxy_port', '')); ?>"
|
||||
placeholder="<?php esc_attr_e('Port', 'instagram-gallery-sync-pro'); ?>"
|
||||
class="small-text" min="1" max="65535">
|
||||
</div>
|
||||
<p class="description">
|
||||
<?php esc_html_e('Optional: Use a proxy for Instagram requests.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php submit_button(); ?>
|
||||
</form>
|
||||
|
||||
<div class="igsp-section igsp-actions-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Tools', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<div class="igsp-tools-grid">
|
||||
<div class="igsp-tool-card">
|
||||
<h3>
|
||||
<?php esc_html_e('Clear Cache', 'instagram-gallery-sync-pro'); ?>
|
||||
</h3>
|
||||
<p>
|
||||
<?php esc_html_e('Clear all cached gallery data.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
<button type="button" id="igsp-clear-cache" class="button">
|
||||
<span class="dashicons dashicons-trash"></span>
|
||||
<?php esc_html_e('Clear Cache', 'instagram-gallery-sync-pro'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="igsp-tool-card">
|
||||
<h3>
|
||||
<?php esc_html_e('Storage Used', 'instagram-gallery-sync-pro'); ?>
|
||||
</h3>
|
||||
<p>
|
||||
<?php echo esc_html(sprintf(__('Total: %s', 'instagram-gallery-sync-pro'), $image_handler->get_storage_used_formatted())); ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="igsp-tool-card igsp-tool-danger">
|
||||
<h3>
|
||||
<?php esc_html_e('Reset All Data', 'instagram-gallery-sync-pro'); ?>
|
||||
</h3>
|
||||
<p>
|
||||
<?php esc_html_e('Delete ALL Instagram data, images, and logs.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
<button type="button" id="igsp-reset-data" class="button button-danger">
|
||||
<span class="dashicons dashicons-warning"></span>
|
||||
<?php esc_html_e('Reset Everything', 'instagram-gallery-sync-pro'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="igsp-section igsp-logs-section">
|
||||
<div class="igsp-logs-header">
|
||||
<h2>
|
||||
<?php esc_html_e('Activity Logs', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
<button type="button" id="igsp-clear-logs" class="button button-small">
|
||||
<?php esc_html_e('Clear Logs', 'instagram-gallery-sync-pro'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php if (empty($logs)): ?>
|
||||
<p class="igsp-no-logs">
|
||||
<?php esc_html_e('No log entries yet. Enable debug mode to see activity logs.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<div class="igsp-logs-table-wrapper">
|
||||
<table class="igsp-logs-table widefat">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="igsp-log-type">
|
||||
<?php esc_html_e('Type', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<th class="igsp-log-message">
|
||||
<?php esc_html_e('Message', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<th class="igsp-log-date">
|
||||
<?php esc_html_e('Date', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($logs as $log): ?>
|
||||
<tr class="<?php echo esc_attr($log['type_class']); ?>">
|
||||
<td>
|
||||
<span class="igsp-log-badge igsp-log-<?php echo esc_attr($log['type']); ?>">
|
||||
<?php echo esc_html($log['type_label']); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo esc_html($log['message']); ?>
|
||||
</td>
|
||||
<td title="<?php echo esc_attr($log['date']); ?>">
|
||||
<?php echo esc_html($log['relative']); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
233
admin/views/tab-display.php
Normal file
233
admin/views/tab-display.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
/**
|
||||
* Display Settings Tab
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<form method="post" action="options.php" class="igsp-settings-form">
|
||||
<?php settings_fields('igsp_display_settings'); ?>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Display Options', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Number of Images to Display', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-slider-wrapper">
|
||||
<input type="range" id="igsp_display_limit" name="igsp_display_limit" min="1" max="50"
|
||||
value="<?php echo esc_attr(get_option('igsp_display_limit', 12)); ?>">
|
||||
<span class="igsp-slider-value">
|
||||
<?php echo esc_html(get_option('igsp_display_limit', 12)); ?>
|
||||
</span>
|
||||
</div>
|
||||
<p class="description">
|
||||
<?php esc_html_e('Number of images to show in the gallery. Set to 50 for "All".', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_order">
|
||||
<?php esc_html_e('Display Order', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php $order = get_option('igsp_order', 'newest'); ?>
|
||||
<select id="igsp_order" name="igsp_order">
|
||||
<option value="newest" <?php selected($order, 'newest'); ?>>
|
||||
<?php esc_html_e('Newest First', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="oldest" <?php selected($order, 'oldest'); ?>>
|
||||
<?php esc_html_e('Oldest First', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="random" <?php selected($order, 'random'); ?>>
|
||||
<?php esc_html_e('Random', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Show Captions', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<?php $show_caption = get_option('igsp_show_caption', 'no'); ?>
|
||||
<fieldset>
|
||||
<label>
|
||||
<input type="radio" name="igsp_show_caption" value="yes" <?php checked($show_caption, 'yes'); ?>>
|
||||
<?php esc_html_e('Yes', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="igsp_show_caption" value="no" <?php checked($show_caption, 'no'); ?>>
|
||||
<?php esc_html_e('No', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="igsp-conditional" data-depends-on="igsp_show_caption" data-depends-value="yes">
|
||||
<th scope="row">
|
||||
<label for="igsp_caption_position">
|
||||
<?php esc_html_e('Caption Position', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php $position = get_option('igsp_caption_position', 'overlay'); ?>
|
||||
<select id="igsp_caption_position" name="igsp_caption_position">
|
||||
<option value="overlay" <?php selected($position, 'overlay'); ?>>
|
||||
<?php esc_html_e('Overlay on Image', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="below" <?php selected($position, 'below'); ?>>
|
||||
<?php esc_html_e('Below Image', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Show "More on Instagram" Button', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<?php $show_btn = get_option('igsp_show_instagram_btn', 'yes'); ?>
|
||||
<fieldset>
|
||||
<label>
|
||||
<input type="radio" name="igsp_show_instagram_btn" value="yes" <?php checked($show_btn, 'yes'); ?>>
|
||||
<?php esc_html_e('Yes', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="igsp_show_instagram_btn" value="no" <?php checked($show_btn, 'no'); ?>>
|
||||
<?php esc_html_e('No', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Interactivity', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_link_behavior">
|
||||
<?php esc_html_e('Link Behavior', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php $link = get_option('igsp_link_behavior', 'new_tab'); ?>
|
||||
<select id="igsp_link_behavior" name="igsp_link_behavior">
|
||||
<option value="new_tab" <?php selected($link, 'new_tab'); ?>>
|
||||
<?php esc_html_e('Open in New Tab', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="same_window" <?php selected($link, 'same_window'); ?>>
|
||||
<?php esc_html_e('Open in Same Window', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="lightbox" <?php selected($link, 'lightbox'); ?>>
|
||||
<?php esc_html_e('Open in Lightbox', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="none" <?php selected($link, 'none'); ?>>
|
||||
<?php esc_html_e('No Link', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Enable Lightbox', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<?php $lightbox = get_option('igsp_lightbox', 'yes'); ?>
|
||||
<fieldset>
|
||||
<label>
|
||||
<input type="radio" name="igsp_lightbox" value="yes" <?php checked($lightbox, 'yes'); ?>>
|
||||
<?php esc_html_e('Yes', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="igsp_lightbox" value="no" <?php checked($lightbox, 'no'); ?>>
|
||||
<?php esc_html_e('No', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</fieldset>
|
||||
<p class="description">
|
||||
<?php esc_html_e('Allow clicking images to view them in a lightbox.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Performance', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Lazy Loading', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<?php $lazy = get_option('igsp_lazy_loading', 'yes'); ?>
|
||||
<fieldset>
|
||||
<label>
|
||||
<input type="radio" name="igsp_lazy_loading" value="yes" <?php checked($lazy, 'yes'); ?>>
|
||||
<?php esc_html_e('Yes (Recommended)', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="igsp_lazy_loading" value="no" <?php checked($lazy, 'no'); ?>>
|
||||
<?php esc_html_e('No', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_loader_type">
|
||||
<?php esc_html_e('Loading Animation', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php $loader = get_option('igsp_loader_type', 'spinner'); ?>
|
||||
<select id="igsp_loader_type" name="igsp_loader_type">
|
||||
<option value="spinner" <?php selected($loader, 'spinner'); ?>>
|
||||
<?php esc_html_e('Spinner', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="dots" <?php selected($loader, 'dots'); ?>>
|
||||
<?php esc_html_e('Dots', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="pulse" <?php selected($loader, 'pulse'); ?>>
|
||||
<?php esc_html_e('Pulse', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="skeleton" <?php selected($loader, 'skeleton'); ?>>
|
||||
<?php esc_html_e('Skeleton', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="none" <?php selected($loader, 'none'); ?>>
|
||||
<?php esc_html_e('None', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php submit_button(); ?>
|
||||
</form>
|
||||
153
admin/views/tab-instagram.php
Normal file
153
admin/views/tab-instagram.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
/**
|
||||
* Instagram Settings Tab
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$cron = new IGSP_Cron();
|
||||
$intervals = $cron->get_intervals();
|
||||
?>
|
||||
|
||||
<form method="post" action="options.php" class="igsp-settings-form">
|
||||
<?php settings_fields( 'igsp_instagram_settings' ); ?>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2><?php esc_html_e( 'Account Settings', 'instagram-gallery-sync-pro' ); ?></h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_username"><?php esc_html_e( 'Instagram Username', 'instagram-gallery-sync-pro' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-input-wrapper">
|
||||
<span class="igsp-input-prefix">@</span>
|
||||
<input type="text"
|
||||
id="igsp_username"
|
||||
name="igsp_username"
|
||||
value="<?php echo esc_attr( get_option( 'igsp_username', '' ) ); ?>"
|
||||
class="regular-text"
|
||||
placeholder="username">
|
||||
</div>
|
||||
<p class="description"><?php esc_html_e( 'Enter the Instagram username without the @ symbol. The profile must be public.', 'instagram-gallery-sync-pro' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_max_images"><?php esc_html_e( 'Maximum Images to Fetch', 'instagram-gallery-sync-pro' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<select id="igsp_max_images" name="igsp_max_images">
|
||||
<?php
|
||||
$max_images = get_option( 'igsp_max_images', 12 );
|
||||
$options = array( 12, 24, 36, 48, 100 );
|
||||
foreach ( $options as $option ) :
|
||||
?>
|
||||
<option value="<?php echo esc_attr( $option ); ?>" <?php selected( $max_images, $option ); ?>>
|
||||
<?php echo esc_html( $option === 100 ? __( 'All available', 'instagram-gallery-sync-pro' ) : $option ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<p class="description"><?php esc_html_e( 'Number of images to fetch from Instagram.', 'instagram-gallery-sync-pro' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Save Images Locally', 'instagram-gallery-sync-pro' ); ?></th>
|
||||
<td>
|
||||
<fieldset>
|
||||
<label>
|
||||
<input type="radio"
|
||||
name="igsp_save_locally"
|
||||
value="yes"
|
||||
<?php checked( get_option( 'igsp_save_locally', 'yes' ), 'yes' ); ?>>
|
||||
<?php esc_html_e( 'Yes - Download and store images on your server (recommended)', 'instagram-gallery-sync-pro' ); ?>
|
||||
</label>
|
||||
<br>
|
||||
<label>
|
||||
<input type="radio"
|
||||
name="igsp_save_locally"
|
||||
value="no"
|
||||
<?php checked( get_option( 'igsp_save_locally', 'yes' ), 'no' ); ?>>
|
||||
<?php esc_html_e( 'No - Link directly to Instagram (may cause loading issues)', 'instagram-gallery-sync-pro' ); ?>
|
||||
</label>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_image_quality"><?php esc_html_e( 'Image Quality', 'instagram-gallery-sync-pro' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<select id="igsp_image_quality" name="igsp_image_quality">
|
||||
<?php
|
||||
$quality = get_option( 'igsp_image_quality', 'high' );
|
||||
$quality_options = array(
|
||||
'thumbnail' => __( 'Thumbnail (150px)', 'instagram-gallery-sync-pro' ),
|
||||
'medium' => __( 'Medium (320px)', 'instagram-gallery-sync-pro' ),
|
||||
'high' => __( 'High (640px+)', 'instagram-gallery-sync-pro' ),
|
||||
);
|
||||
foreach ( $quality_options as $value => $label ) :
|
||||
?>
|
||||
<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $quality, $value ); ?>>
|
||||
<?php echo esc_html( $label ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2><?php esc_html_e( 'Synchronization', 'instagram-gallery-sync-pro' ); ?></h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_sync_interval"><?php esc_html_e( 'Sync Interval', 'instagram-gallery-sync-pro' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<select id="igsp_sync_interval" name="igsp_sync_interval">
|
||||
<?php
|
||||
$current_interval = get_option( 'igsp_sync_interval', 'daily' );
|
||||
foreach ( $intervals as $value => $label ) :
|
||||
?>
|
||||
<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $current_interval, $value ); ?>>
|
||||
<?php echo esc_html( $label ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<p class="description"><?php esc_html_e( 'How often to check for new posts.', 'instagram-gallery-sync-pro' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Manual Sync', 'instagram-gallery-sync-pro' ); ?></th>
|
||||
<td>
|
||||
<button type="button" id="igsp-sync-now" class="button button-primary">
|
||||
<span class="dashicons dashicons-update"></span>
|
||||
<?php esc_html_e( 'Sync Now', 'instagram-gallery-sync-pro' ); ?>
|
||||
</button>
|
||||
<div id="igsp-sync-progress" class="igsp-progress-wrapper" style="display: none;">
|
||||
<div class="igsp-progress-bar">
|
||||
<div class="igsp-progress-fill"></div>
|
||||
</div>
|
||||
<span class="igsp-progress-text"><?php esc_html_e( 'Syncing...', 'instagram-gallery-sync-pro' ); ?></span>
|
||||
</div>
|
||||
<div id="igsp-sync-result" class="igsp-sync-result" style="display: none;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php submit_button(); ?>
|
||||
</form>
|
||||
279
admin/views/tab-layout.php
Normal file
279
admin/views/tab-layout.php
Normal file
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
/**
|
||||
* Layout Settings Tab
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<form method="post" action="options.php" class="igsp-settings-form">
|
||||
<?php settings_fields('igsp_layout_settings'); ?>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Layout Type', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<?php $layout_type = get_option('igsp_layout_type', 'grid'); ?>
|
||||
<div class="igsp-layout-selector">
|
||||
<label class="igsp-layout-option <?php echo $layout_type === 'grid' ? 'selected' : ''; ?>">
|
||||
<input type="radio" name="igsp_layout_type" value="grid" <?php checked($layout_type, 'grid'); ?>>
|
||||
<div class="igsp-layout-preview igsp-preview-grid">
|
||||
<span></span><span></span><span></span>
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<span class="igsp-layout-name">
|
||||
<?php esc_html_e('Grid', 'instagram-gallery-sync-pro'); ?>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="igsp-layout-option <?php echo $layout_type === 'masonry' ? 'selected' : ''; ?>">
|
||||
<input type="radio" name="igsp_layout_type" value="masonry" <?php checked($layout_type, 'masonry'); ?>>
|
||||
<div class="igsp-layout-preview igsp-preview-masonry">
|
||||
<span style="height: 40px;"></span><span style="height: 60px;"></span><span
|
||||
style="height: 30px;"></span>
|
||||
<span style="height: 50px;"></span><span style="height: 35px;"></span><span
|
||||
style="height: 55px;"></span>
|
||||
</div>
|
||||
<span class="igsp-layout-name">
|
||||
<?php esc_html_e('Masonry', 'instagram-gallery-sync-pro'); ?>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="igsp-layout-option <?php echo $layout_type === 'justified' ? 'selected' : ''; ?>">
|
||||
<input type="radio" name="igsp_layout_type" value="justified" <?php checked($layout_type, 'justified'); ?>>
|
||||
<div class="igsp-layout-preview igsp-preview-justified">
|
||||
<span style="width: 60px;"></span><span style="width: 40px;"></span>
|
||||
<span style="width: 35px;"></span><span style="width: 65px;"></span>
|
||||
</div>
|
||||
<span class="igsp-layout-name">
|
||||
<?php esc_html_e('Justified', 'instagram-gallery-sync-pro'); ?>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="igsp-layout-option <?php echo $layout_type === 'slider' ? 'selected' : ''; ?>">
|
||||
<input type="radio" name="igsp_layout_type" value="slider" <?php checked($layout_type, 'slider'); ?>>
|
||||
<div class="igsp-layout-preview igsp-preview-slider">
|
||||
<span class="arrow-left">‹</span>
|
||||
<span class="slide"></span>
|
||||
<span class="arrow-right">›</span>
|
||||
</div>
|
||||
<span class="igsp-layout-name">
|
||||
<?php esc_html_e('Slider', 'instagram-gallery-sync-pro'); ?>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="igsp-layout-option <?php echo $layout_type === 'list' ? 'selected' : ''; ?>">
|
||||
<input type="radio" name="igsp_layout_type" value="list" <?php checked($layout_type, 'list'); ?>>
|
||||
<div class="igsp-layout-preview igsp-preview-list">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
<span class="igsp-layout-name">
|
||||
<?php esc_html_e('List', 'instagram-gallery-sync-pro'); ?>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Grid Settings', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Columns (Desktop)', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-slider-wrapper">
|
||||
<input type="range" id="igsp_columns_desktop" name="igsp_columns_desktop" min="1" max="6"
|
||||
value="<?php echo esc_attr(get_option('igsp_columns_desktop', 3)); ?>">
|
||||
<span class="igsp-slider-value">
|
||||
<?php echo esc_html(get_option('igsp_columns_desktop', 3)); ?>
|
||||
</span>
|
||||
</div>
|
||||
<p class="description">
|
||||
<?php esc_html_e('Number of columns on desktop (> 1024px)', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Columns (Tablet)', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-slider-wrapper">
|
||||
<input type="range" id="igsp_columns_tablet" name="igsp_columns_tablet" min="1" max="4"
|
||||
value="<?php echo esc_attr(get_option('igsp_columns_tablet', 2)); ?>">
|
||||
<span class="igsp-slider-value">
|
||||
<?php echo esc_html(get_option('igsp_columns_tablet', 2)); ?>
|
||||
</span>
|
||||
</div>
|
||||
<p class="description">
|
||||
<?php esc_html_e('Number of columns on tablet (768px - 1023px)', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Columns (Mobile)', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-slider-wrapper">
|
||||
<input type="range" id="igsp_columns_mobile" name="igsp_columns_mobile" min="1" max="2"
|
||||
value="<?php echo esc_attr(get_option('igsp_columns_mobile', 1)); ?>">
|
||||
<span class="igsp-slider-value">
|
||||
<?php echo esc_html(get_option('igsp_columns_mobile', 1)); ?>
|
||||
</span>
|
||||
</div>
|
||||
<p class="description">
|
||||
<?php esc_html_e('Number of columns on mobile (< 767px)', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Spacing', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-slider-wrapper">
|
||||
<input type="range" id="igsp_spacing" name="igsp_spacing" min="0" max="50"
|
||||
value="<?php echo esc_attr(get_option('igsp_spacing', 10)); ?>">
|
||||
<span class="igsp-slider-value">
|
||||
<?php echo esc_html(get_option('igsp_spacing', 10)); ?>px
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Outer Padding', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-slider-wrapper">
|
||||
<input type="range" id="igsp_padding" name="igsp_padding" min="0" max="50"
|
||||
value="<?php echo esc_attr(get_option('igsp_padding', 0)); ?>">
|
||||
<span class="igsp-slider-value">
|
||||
<?php echo esc_html(get_option('igsp_padding', 0)); ?>px
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Image Settings', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_aspect_ratio">
|
||||
<?php esc_html_e('Aspect Ratio', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php $aspect = get_option('igsp_aspect_ratio', 'square'); ?>
|
||||
<select id="igsp_aspect_ratio" name="igsp_aspect_ratio">
|
||||
<option value="original" <?php selected($aspect, 'original'); ?>>
|
||||
<?php esc_html_e('Original', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="square" <?php selected($aspect, 'square'); ?>>
|
||||
<?php esc_html_e('Square (1:1)', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="4-3" <?php selected($aspect, '4-3'); ?>>
|
||||
<?php esc_html_e('4:3', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="16-9" <?php selected($aspect, '16-9'); ?>>
|
||||
<?php esc_html_e('16:9', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="3-4" <?php selected($aspect, '3-4'); ?>>
|
||||
<?php esc_html_e('3:4 (Portrait)', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_object_fit">
|
||||
<?php esc_html_e('Object Fit', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php $fit = get_option('igsp_object_fit', 'cover'); ?>
|
||||
<select id="igsp_object_fit" name="igsp_object_fit">
|
||||
<option value="cover" <?php selected($fit, 'cover'); ?>>
|
||||
<?php esc_html_e('Cover (Crop to fill)', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="contain" <?php selected($fit, 'contain'); ?>>
|
||||
<?php esc_html_e('Contain (Fit inside)', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Border Radius', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-slider-wrapper">
|
||||
<input type="range" id="igsp_border_radius" name="igsp_border_radius" min="0" max="30"
|
||||
value="<?php echo esc_attr(get_option('igsp_border_radius', 0)); ?>">
|
||||
<span class="igsp-slider-value">
|
||||
<?php echo esc_html(get_option('igsp_border_radius', 0)); ?>px
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_hover_effect">
|
||||
<?php esc_html_e('Hover Effect', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php $hover = get_option('igsp_hover_effect', 'zoom'); ?>
|
||||
<select id="igsp_hover_effect" name="igsp_hover_effect">
|
||||
<option value="none" <?php selected($hover, 'none'); ?>>
|
||||
<?php esc_html_e('None', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="zoom" <?php selected($hover, 'zoom'); ?>>
|
||||
<?php esc_html_e('Zoom', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="fade" <?php selected($hover, 'fade'); ?>>
|
||||
<?php esc_html_e('Fade + Caption', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="overlay" <?php selected($hover, 'overlay'); ?>>
|
||||
<?php esc_html_e('Overlay with Icon', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="grayscale" <?php selected($hover, 'grayscale'); ?>>
|
||||
<?php esc_html_e('Grayscale to Color', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
<option value="lift" <?php selected($hover, 'lift'); ?>>
|
||||
<?php esc_html_e('Lift (Shadow + Transform)', 'instagram-gallery-sync-pro'); ?>
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php submit_button(); ?>
|
||||
</form>
|
||||
158
admin/views/tab-styling.php
Normal file
158
admin/views/tab-styling.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
* Styling Settings Tab
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<form method="post" action="options.php" class="igsp-settings-form">
|
||||
<?php settings_fields('igsp_styling_settings'); ?>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Colors', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_primary_color">
|
||||
<?php esc_html_e('Primary Color', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" id="igsp_primary_color" name="igsp_primary_color"
|
||||
value="<?php echo esc_attr(get_option('igsp_primary_color', '#e1306c')); ?>"
|
||||
class="igsp-color-picker" data-default-color="#e1306c">
|
||||
<p class="description">
|
||||
<?php esc_html_e('Used for buttons and highlights.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_hover_color">
|
||||
<?php esc_html_e('Hover Color', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" id="igsp_hover_color" name="igsp_hover_color"
|
||||
value="<?php echo esc_attr(get_option('igsp_hover_color', '#c13584')); ?>"
|
||||
class="igsp-color-picker" data-default-color="#c13584">
|
||||
<p class="description">
|
||||
<?php esc_html_e('Used for overlay and hover effects.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_text_color">
|
||||
<?php esc_html_e('Text Color', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" id="igsp_text_color" name="igsp_text_color"
|
||||
value="<?php echo esc_attr(get_option('igsp_text_color', '#ffffff')); ?>"
|
||||
class="igsp-color-picker" data-default-color="#ffffff">
|
||||
<p class="description">
|
||||
<?php esc_html_e('Used for caption and overlay text.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Typography', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e('Caption Font Size', 'instagram-gallery-sync-pro'); ?>
|
||||
</th>
|
||||
<td>
|
||||
<div class="igsp-slider-wrapper">
|
||||
<input type="range" id="igsp_caption_font_size" name="igsp_caption_font_size" min="10" max="24"
|
||||
value="<?php echo esc_attr(get_option('igsp_caption_font_size', 14)); ?>">
|
||||
<span class="igsp-slider-value">
|
||||
<?php echo esc_html(get_option('igsp_caption_font_size', 14)); ?>px
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="igsp-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Advanced Styling', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_css_prefix">
|
||||
<?php esc_html_e('CSS Class Prefix', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" id="igsp_css_prefix" name="igsp_css_prefix"
|
||||
value="<?php echo esc_attr(get_option('igsp_css_prefix', 'igsp')); ?>" class="regular-text">
|
||||
<p class="description">
|
||||
<?php esc_html_e('Prefix for all CSS classes. Change only if you have conflicts with your theme.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="igsp_custom_css">
|
||||
<?php esc_html_e('Custom CSS', 'instagram-gallery-sync-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<textarea id="igsp_custom_css" name="igsp_custom_css" rows="10" class="large-text code"
|
||||
placeholder="/* Add your custom CSS here */"><?php echo esc_textarea(get_option('igsp_custom_css', '')); ?></textarea>
|
||||
<p class="description">
|
||||
<?php esc_html_e('Add custom CSS to override default styles. Use cautiously.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="igsp-section igsp-preview-section">
|
||||
<h2>
|
||||
<?php esc_html_e('Live Preview', 'instagram-gallery-sync-pro'); ?>
|
||||
</h2>
|
||||
<div class="igsp-style-preview">
|
||||
<div class="igsp-preview-item" style="--primary: <?php echo esc_attr(get_option('igsp_primary_color', '#e1306c')); ?>;
|
||||
--hover: <?php echo esc_attr(get_option('igsp_hover_color', '#c13584')); ?>;
|
||||
--text: <?php echo esc_attr(get_option('igsp_text_color', '#ffffff')); ?>;">
|
||||
<div class="igsp-preview-image">
|
||||
<div class="igsp-preview-overlay">
|
||||
<span class="igsp-preview-caption"
|
||||
style="font-size: <?php echo esc_attr(get_option('igsp_caption_font_size', 14)); ?>px;">
|
||||
<?php esc_html_e('Sample Caption', 'instagram-gallery-sync-pro'); ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="igsp-preview-button">
|
||||
<?php esc_html_e('Sample Button', 'instagram-gallery-sync-pro'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php submit_button(); ?>
|
||||
</form>
|
||||
395
includes/class-admin.php
Normal file
395
includes/class-admin.php
Normal file
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin Class
|
||||
*
|
||||
* Handles all admin functionality including settings pages and AJAX handlers.
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class IGSP_Admin
|
||||
*/
|
||||
class IGSP_Admin
|
||||
{
|
||||
|
||||
/**
|
||||
* Settings page slug
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PAGE_SLUG = 'instagram-gallery-sync-pro';
|
||||
|
||||
/**
|
||||
* Initialize admin
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
// Add admin menu
|
||||
add_action('admin_menu', array($this, 'add_admin_menu'));
|
||||
|
||||
// Register settings
|
||||
add_action('admin_init', array($this, 'register_settings'));
|
||||
|
||||
// Enqueue admin assets
|
||||
add_action('admin_enqueue_scripts', array($this, 'enqueue_assets'));
|
||||
|
||||
// AJAX handlers
|
||||
add_action('wp_ajax_igsp_manual_sync', array($this, 'ajax_manual_sync'));
|
||||
add_action('wp_ajax_igsp_clear_cache', array($this, 'ajax_clear_cache'));
|
||||
add_action('wp_ajax_igsp_reset_data', array($this, 'ajax_reset_data'));
|
||||
add_action('wp_ajax_igsp_get_sync_status', array($this, 'ajax_get_sync_status'));
|
||||
add_action('wp_ajax_igsp_clear_logs', array($this, 'ajax_clear_logs'));
|
||||
|
||||
// Add settings link to plugins page
|
||||
add_filter('plugin_action_links_' . IGSP_PLUGIN_BASENAME, array($this, 'add_settings_link'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin menu
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_admin_menu()
|
||||
{
|
||||
add_menu_page(
|
||||
__('Instagram Gallery', 'instagram-gallery-sync-pro'),
|
||||
__('Instagram Gallery', 'instagram-gallery-sync-pro'),
|
||||
'manage_options',
|
||||
self::PAGE_SLUG,
|
||||
array($this, 'render_settings_page'),
|
||||
'dashicons-instagram',
|
||||
30
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_settings()
|
||||
{
|
||||
// Instagram settings
|
||||
register_setting('igsp_instagram_settings', 'igsp_username', 'sanitize_text_field');
|
||||
register_setting('igsp_instagram_settings', 'igsp_max_images', 'absint');
|
||||
register_setting('igsp_instagram_settings', 'igsp_save_locally', 'sanitize_text_field');
|
||||
register_setting('igsp_instagram_settings', 'igsp_image_quality', 'sanitize_text_field');
|
||||
register_setting('igsp_instagram_settings', 'igsp_sync_interval', 'sanitize_text_field');
|
||||
|
||||
// Layout settings
|
||||
register_setting('igsp_layout_settings', 'igsp_layout_type', 'sanitize_text_field');
|
||||
register_setting('igsp_layout_settings', 'igsp_columns_desktop', 'absint');
|
||||
register_setting('igsp_layout_settings', 'igsp_columns_tablet', 'absint');
|
||||
register_setting('igsp_layout_settings', 'igsp_columns_mobile', 'absint');
|
||||
register_setting('igsp_layout_settings', 'igsp_spacing', 'absint');
|
||||
register_setting('igsp_layout_settings', 'igsp_padding', 'absint');
|
||||
register_setting('igsp_layout_settings', 'igsp_aspect_ratio', 'sanitize_text_field');
|
||||
register_setting('igsp_layout_settings', 'igsp_object_fit', 'sanitize_text_field');
|
||||
register_setting('igsp_layout_settings', 'igsp_border_radius', 'absint');
|
||||
register_setting('igsp_layout_settings', 'igsp_hover_effect', 'sanitize_text_field');
|
||||
|
||||
// Display settings
|
||||
register_setting('igsp_display_settings', 'igsp_display_limit', 'absint');
|
||||
register_setting('igsp_display_settings', 'igsp_order', 'sanitize_text_field');
|
||||
register_setting('igsp_display_settings', 'igsp_show_caption', 'sanitize_text_field');
|
||||
register_setting('igsp_display_settings', 'igsp_caption_position', 'sanitize_text_field');
|
||||
register_setting('igsp_display_settings', 'igsp_show_instagram_btn', 'sanitize_text_field');
|
||||
register_setting('igsp_display_settings', 'igsp_link_behavior', 'sanitize_text_field');
|
||||
register_setting('igsp_display_settings', 'igsp_lightbox', 'sanitize_text_field');
|
||||
register_setting('igsp_display_settings', 'igsp_lazy_loading', 'sanitize_text_field');
|
||||
register_setting('igsp_display_settings', 'igsp_loader_type', 'sanitize_text_field');
|
||||
|
||||
// Styling settings
|
||||
register_setting('igsp_styling_settings', 'igsp_primary_color', 'sanitize_hex_color');
|
||||
register_setting('igsp_styling_settings', 'igsp_hover_color', 'sanitize_hex_color');
|
||||
register_setting('igsp_styling_settings', 'igsp_text_color', 'sanitize_hex_color');
|
||||
register_setting('igsp_styling_settings', 'igsp_caption_font_size', 'absint');
|
||||
register_setting('igsp_styling_settings', 'igsp_custom_css', array($this, 'sanitize_css'));
|
||||
register_setting('igsp_styling_settings', 'igsp_css_prefix', 'sanitize_text_field');
|
||||
|
||||
// Advanced settings
|
||||
register_setting('igsp_advanced_settings', 'igsp_debug_mode', 'sanitize_text_field');
|
||||
register_setting('igsp_advanced_settings', 'igsp_cache_duration', 'absint');
|
||||
register_setting('igsp_advanced_settings', 'igsp_request_timeout', 'absint');
|
||||
register_setting('igsp_advanced_settings', 'igsp_user_agent', 'sanitize_text_field');
|
||||
register_setting('igsp_advanced_settings', 'igsp_proxy_host', 'sanitize_text_field');
|
||||
register_setting('igsp_advanced_settings', 'igsp_proxy_port', 'absint');
|
||||
register_setting('igsp_advanced_settings', 'igsp_auto_delete_days', 'absint');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize CSS input
|
||||
*
|
||||
* @param string $css CSS string
|
||||
* @return string
|
||||
*/
|
||||
public function sanitize_css($css)
|
||||
{
|
||||
// Remove any script tags
|
||||
$css = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $css);
|
||||
|
||||
// Remove javascript: URLs
|
||||
$css = preg_replace('/javascript\s*:/i', '', $css);
|
||||
|
||||
// Remove expression()
|
||||
$css = preg_replace('/expression\s*\(/i', '', $css);
|
||||
|
||||
// Remove behavior: (IE specific)
|
||||
$css = preg_replace('/behavior\s*:/i', '', $css);
|
||||
|
||||
return wp_strip_all_tags($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin assets
|
||||
*
|
||||
* @param string $hook Current page hook
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets($hook)
|
||||
{
|
||||
if (strpos($hook, self::PAGE_SLUG) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Admin CSS
|
||||
wp_enqueue_style(
|
||||
'igsp-admin-style',
|
||||
IGSP_PLUGIN_URL . 'admin/css/admin-style.css',
|
||||
array(),
|
||||
IGSP_VERSION
|
||||
);
|
||||
|
||||
// WordPress components
|
||||
wp_enqueue_style('wp-color-picker');
|
||||
|
||||
// Admin JS
|
||||
wp_enqueue_script(
|
||||
'igsp-admin-script',
|
||||
IGSP_PLUGIN_URL . 'admin/js/admin-script.js',
|
||||
array('jquery', 'wp-color-picker'),
|
||||
IGSP_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
// AJAX Sync JS
|
||||
wp_enqueue_script(
|
||||
'igsp-ajax-sync',
|
||||
IGSP_PLUGIN_URL . 'admin/js/ajax-sync.js',
|
||||
array('jquery'),
|
||||
IGSP_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
// Localize script
|
||||
wp_localize_script('igsp-admin-script', 'igspAdmin', array(
|
||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('igsp_admin_nonce'),
|
||||
'strings' => array(
|
||||
'syncing' => __('Syncing...', 'instagram-gallery-sync-pro'),
|
||||
'syncComplete' => __('Sync complete!', 'instagram-gallery-sync-pro'),
|
||||
'syncError' => __('Sync failed. Check logs for details.', 'instagram-gallery-sync-pro'),
|
||||
'confirmReset' => __('Are you sure? This will delete ALL Instagram data including images. This action cannot be undone.', 'instagram-gallery-sync-pro'),
|
||||
'confirmClearLogs' => __('Are you sure you want to clear all logs?', 'instagram-gallery-sync-pro'),
|
||||
'processing' => __('Processing...', 'instagram-gallery-sync-pro'),
|
||||
'done' => __('Done!', 'instagram-gallery-sync-pro'),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render settings page
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_settings_page()
|
||||
{
|
||||
// Check permissions
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_die(__('You do not have sufficient permissions to access this page.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
include IGSP_PLUGIN_DIR . 'admin/views/settings-page.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings link to plugins page
|
||||
*
|
||||
* @param array $links Existing links
|
||||
* @return array
|
||||
*/
|
||||
public function add_settings_link($links)
|
||||
{
|
||||
$settings_link = sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
admin_url('admin.php?page=' . self::PAGE_SLUG),
|
||||
__('Settings', 'instagram-gallery-sync-pro')
|
||||
);
|
||||
|
||||
array_unshift($links, $settings_link);
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Manual sync
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_manual_sync()
|
||||
{
|
||||
check_ajax_referer('igsp_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error(array('message' => __('Permission denied.', 'instagram-gallery-sync-pro')));
|
||||
}
|
||||
|
||||
$cron = new IGSP_Cron();
|
||||
$result = $cron->manual_sync();
|
||||
|
||||
if ($result['success']) {
|
||||
wp_send_json_success($result);
|
||||
} else {
|
||||
wp_send_json_error($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Clear cache
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_clear_cache()
|
||||
{
|
||||
check_ajax_referer('igsp_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error(array('message' => __('Permission denied.', 'instagram-gallery-sync-pro')));
|
||||
}
|
||||
|
||||
igsp()->clear_transients();
|
||||
|
||||
wp_send_json_success(array('message' => __('Cache cleared successfully.', 'instagram-gallery-sync-pro')));
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Reset all data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_reset_data()
|
||||
{
|
||||
check_ajax_referer('igsp_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error(array('message' => __('Permission denied.', 'instagram-gallery-sync-pro')));
|
||||
}
|
||||
|
||||
// Delete all posts from database
|
||||
$database = new IGSP_Database();
|
||||
$database->delete_all_posts();
|
||||
|
||||
// Clear all images
|
||||
$image_handler = new IGSP_Image_Handler();
|
||||
$image_handler->clear_all_images();
|
||||
|
||||
// Clear logs
|
||||
$logger = new IGSP_Logger();
|
||||
$logger->clear_logs();
|
||||
|
||||
// Clear transients
|
||||
igsp()->clear_transients();
|
||||
|
||||
// Reset last sync
|
||||
update_option('igsp_last_sync', '');
|
||||
|
||||
wp_send_json_success(array('message' => __('All data has been reset.', 'instagram-gallery-sync-pro')));
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Get sync status
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_get_sync_status()
|
||||
{
|
||||
check_ajax_referer('igsp_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error(array('message' => __('Permission denied.', 'instagram-gallery-sync-pro')));
|
||||
}
|
||||
|
||||
$cron = new IGSP_Cron();
|
||||
$status = $cron->get_sync_status();
|
||||
|
||||
wp_send_json_success($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Clear logs
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_clear_logs()
|
||||
{
|
||||
check_ajax_referer('igsp_admin_nonce', 'nonce');
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error(array('message' => __('Permission denied.', 'instagram-gallery-sync-pro')));
|
||||
}
|
||||
|
||||
$logger = new IGSP_Logger();
|
||||
$logger->clear_logs();
|
||||
|
||||
wp_send_json_success(array('message' => __('Logs cleared successfully.', 'instagram-gallery-sync-pro')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current tab
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_current_tab()
|
||||
{
|
||||
return isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'instagram';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tabs configuration
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_tabs()
|
||||
{
|
||||
return array(
|
||||
'instagram' => array(
|
||||
'title' => __('Instagram', 'instagram-gallery-sync-pro'),
|
||||
'icon' => 'dashicons-instagram',
|
||||
),
|
||||
'layout' => array(
|
||||
'title' => __('Layout', 'instagram-gallery-sync-pro'),
|
||||
'icon' => 'dashicons-grid-view',
|
||||
),
|
||||
'display' => array(
|
||||
'title' => __('Display', 'instagram-gallery-sync-pro'),
|
||||
'icon' => 'dashicons-visibility',
|
||||
),
|
||||
'styling' => array(
|
||||
'title' => __('Styling', 'instagram-gallery-sync-pro'),
|
||||
'icon' => 'dashicons-art',
|
||||
),
|
||||
'advanced' => array(
|
||||
'title' => __('Advanced', 'instagram-gallery-sync-pro'),
|
||||
'icon' => 'dashicons-admin-generic',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
404
includes/class-cron.php
Normal file
404
includes/class-cron.php
Normal file
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
/**
|
||||
* Cron Handler Class
|
||||
*
|
||||
* Manages scheduled synchronization tasks.
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class IGSP_Cron
|
||||
*/
|
||||
class IGSP_Cron
|
||||
{
|
||||
|
||||
/**
|
||||
* Cron hook name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SYNC_HOOK = 'igsp_sync_instagram';
|
||||
|
||||
/**
|
||||
* Cleanup hook name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CLEANUP_HOOK = 'igsp_cleanup_logs';
|
||||
|
||||
/**
|
||||
* Lock transient name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LOCK_KEY = 'igsp_sync_lock';
|
||||
|
||||
/**
|
||||
* Logger instance
|
||||
*
|
||||
* @var IGSP_Logger
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->logger = new IGSP_Logger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize cron handlers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
// Register custom intervals
|
||||
add_filter('cron_schedules', array($this, 'add_custom_intervals'));
|
||||
|
||||
// Register cron hooks
|
||||
add_action(self::SYNC_HOOK, array($this, 'run_sync'));
|
||||
add_action(self::CLEANUP_HOOK, array($this, 'run_cleanup'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom cron intervals
|
||||
*
|
||||
* @param array $schedules Existing schedules
|
||||
* @return array
|
||||
*/
|
||||
public function add_custom_intervals($schedules)
|
||||
{
|
||||
$schedules['igsp_30min'] = array(
|
||||
'interval' => 30 * MINUTE_IN_SECONDS,
|
||||
'display' => __('Every 30 Minutes', 'instagram-gallery-sync-pro'),
|
||||
);
|
||||
|
||||
$schedules['igsp_6hours'] = array(
|
||||
'interval' => 6 * HOUR_IN_SECONDS,
|
||||
'display' => __('Every 6 Hours', 'instagram-gallery-sync-pro'),
|
||||
);
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available sync intervals
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_intervals()
|
||||
{
|
||||
return array(
|
||||
'igsp_30min' => __('Every 30 Minutes', 'instagram-gallery-sync-pro'),
|
||||
'hourly' => __('Hourly', 'instagram-gallery-sync-pro'),
|
||||
'igsp_6hours' => __('Every 6 Hours', 'instagram-gallery-sync-pro'),
|
||||
'daily' => __('Daily', 'instagram-gallery-sync-pro'),
|
||||
'weekly' => __('Weekly', 'instagram-gallery-sync-pro'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule sync job
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function schedule_sync()
|
||||
{
|
||||
// Get interval setting
|
||||
$interval = get_option('igsp_sync_interval', 'daily');
|
||||
|
||||
// Clear existing schedule
|
||||
$this->unschedule_sync();
|
||||
|
||||
// Schedule new sync
|
||||
if (!wp_next_scheduled(self::SYNC_HOOK)) {
|
||||
wp_schedule_event(time(), $interval, self::SYNC_HOOK);
|
||||
$this->logger->info(sprintf(__('Scheduled sync with interval: %s', 'instagram-gallery-sync-pro'), $interval));
|
||||
}
|
||||
|
||||
// Schedule daily cleanup if not set
|
||||
if (!wp_next_scheduled(self::CLEANUP_HOOK)) {
|
||||
wp_schedule_event(time(), 'daily', self::CLEANUP_HOOK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedule sync job
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unschedule_sync()
|
||||
{
|
||||
$timestamp = wp_next_scheduled(self::SYNC_HOOK);
|
||||
|
||||
if ($timestamp) {
|
||||
wp_unschedule_event($timestamp, self::SYNC_HOOK);
|
||||
}
|
||||
|
||||
wp_clear_scheduled_hook(self::SYNC_HOOK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if sync is scheduled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_scheduled()
|
||||
{
|
||||
return (bool) wp_next_scheduled(self::SYNC_HOOK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next scheduled sync time
|
||||
*
|
||||
* @return int|false Timestamp or false
|
||||
*/
|
||||
public function get_next_sync()
|
||||
{
|
||||
return wp_next_scheduled(self::SYNC_HOOK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the sync process
|
||||
*
|
||||
* @param bool $manual Whether this is a manual sync
|
||||
* @return array Result data
|
||||
*/
|
||||
public function run_sync($manual = false)
|
||||
{
|
||||
// Check if already running
|
||||
if ($this->is_locked()) {
|
||||
$this->logger->warning(__('Sync already in progress, skipping.', 'instagram-gallery-sync-pro'));
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __('Sync already in progress.', 'instagram-gallery-sync-pro'),
|
||||
);
|
||||
}
|
||||
|
||||
// Acquire lock
|
||||
$this->acquire_lock();
|
||||
|
||||
$result = array(
|
||||
'success' => false,
|
||||
'message' => '',
|
||||
'posts_added' => 0,
|
||||
'posts_updated' => 0,
|
||||
'posts_skipped' => 0,
|
||||
'errors' => array(),
|
||||
);
|
||||
|
||||
try {
|
||||
// Get username
|
||||
$username = get_option('igsp_username', '');
|
||||
|
||||
if (empty($username)) {
|
||||
throw new Exception(__('No Instagram username configured.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
$this->logger->info(sprintf(__('Starting sync for @%s', 'instagram-gallery-sync-pro'), $username));
|
||||
|
||||
// Initialize components
|
||||
$scraper = new IGSP_Scraper();
|
||||
$database = new IGSP_Database();
|
||||
$image_handler = new IGSP_Image_Handler();
|
||||
|
||||
// Fetch posts from Instagram
|
||||
$posts = $scraper->fetch_profile_data($username);
|
||||
|
||||
if (is_wp_error($posts)) {
|
||||
throw new Exception($posts->get_error_message());
|
||||
}
|
||||
|
||||
if (empty($posts)) {
|
||||
throw new Exception(__('No posts found.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
// Get existing IDs
|
||||
$existing_ids = $database->get_existing_instagram_ids();
|
||||
$save_locally = get_option('igsp_save_locally', 'yes') === 'yes';
|
||||
|
||||
// Process each post
|
||||
foreach ($posts as $post) {
|
||||
// Validate post data
|
||||
if (!$scraper->validate_post_data($post)) {
|
||||
$result['posts_skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_new = !in_array($post['instagram_id'], $existing_ids, true);
|
||||
|
||||
// Prepare post data
|
||||
$post_data = array(
|
||||
'instagram_id' => $post['instagram_id'],
|
||||
'username' => $post['username'],
|
||||
'post_url' => $post['post_url'],
|
||||
'caption' => $post['caption'],
|
||||
'likes_count' => $post['likes_count'],
|
||||
'comments_count' => $post['comments_count'],
|
||||
'posted_at' => $post['posted_at'],
|
||||
'image_width' => $post['image_width'],
|
||||
'image_height' => $post['image_height'],
|
||||
);
|
||||
|
||||
// Download image if saving locally and is new post
|
||||
if ($save_locally && $is_new && !empty($post['image_url'])) {
|
||||
$image_result = $image_handler->download_image($post['image_url'], $post['instagram_id']);
|
||||
|
||||
if (!is_wp_error($image_result)) {
|
||||
$post_data['image_local_path'] = $image_result['local_path'];
|
||||
$post_data['image_thumbnail_path'] = $image_result['thumbnail_path'];
|
||||
$post_data['file_size'] = $image_result['file_size'];
|
||||
$post_data['image_width'] = $image_result['width'];
|
||||
$post_data['image_height'] = $image_result['height'];
|
||||
} else {
|
||||
$result['errors'][] = sprintf(
|
||||
__('Failed to download image for post %s: %s', 'instagram-gallery-sync-pro'),
|
||||
$post['instagram_id'],
|
||||
$image_result->get_error_message()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Save to database
|
||||
$saved = $database->insert_post($post_data);
|
||||
|
||||
if ($saved) {
|
||||
if ($is_new) {
|
||||
$result['posts_added']++;
|
||||
} else {
|
||||
$result['posts_updated']++;
|
||||
}
|
||||
} else {
|
||||
$result['errors'][] = sprintf(
|
||||
__('Failed to save post %s to database.', 'instagram-gallery-sync-pro'),
|
||||
$post['instagram_id']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update last sync time
|
||||
update_option('igsp_last_sync', current_time('mysql'));
|
||||
|
||||
// Delete old posts if auto-delete is enabled
|
||||
$auto_delete_days = (int) get_option('igsp_auto_delete_days', 0);
|
||||
if ($auto_delete_days > 0) {
|
||||
$deleted = $database->delete_old_posts($auto_delete_days);
|
||||
if ($deleted > 0) {
|
||||
$this->logger->info(sprintf(__('Auto-deleted %d old posts.', 'instagram-gallery-sync-pro'), $deleted));
|
||||
}
|
||||
}
|
||||
|
||||
// Clear cache
|
||||
igsp()->clear_transients();
|
||||
|
||||
$result['success'] = true;
|
||||
$result['message'] = sprintf(
|
||||
__('Sync completed. Added: %d, Updated: %d, Skipped: %d', 'instagram-gallery-sync-pro'),
|
||||
$result['posts_added'],
|
||||
$result['posts_updated'],
|
||||
$result['posts_skipped']
|
||||
);
|
||||
|
||||
$this->logger->success($result['message'], array(
|
||||
'added' => $result['posts_added'],
|
||||
'updated' => $result['posts_updated'],
|
||||
'skipped' => $result['posts_skipped'],
|
||||
));
|
||||
|
||||
} catch (Exception $e) {
|
||||
$result['message'] = $e->getMessage();
|
||||
$result['errors'][] = $e->getMessage();
|
||||
$this->logger->error(__('Sync failed: ', 'instagram-gallery-sync-pro') . $e->getMessage());
|
||||
}
|
||||
|
||||
// Release lock
|
||||
$this->release_lock();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run cleanup tasks
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run_cleanup()
|
||||
{
|
||||
$logger = new IGSP_Logger();
|
||||
|
||||
// Cleanup old logs (keep last 7 days)
|
||||
$logger->delete_logs_older_than(7);
|
||||
|
||||
// Other cleanup tasks can be added here
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if sync is locked
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_locked()
|
||||
{
|
||||
return (bool) get_transient(self::LOCK_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire sync lock
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function acquire_lock()
|
||||
{
|
||||
set_transient(self::LOCK_KEY, true, 10 * MINUTE_IN_SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release sync lock
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function release_lock()
|
||||
{
|
||||
delete_transient(self::LOCK_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually trigger sync
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function manual_sync()
|
||||
{
|
||||
return $this->run_sync(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sync status for AJAX
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sync_status()
|
||||
{
|
||||
$last_sync = get_option('igsp_last_sync', '');
|
||||
$next_sync = $this->get_next_sync();
|
||||
$is_running = $this->is_locked();
|
||||
|
||||
$database = new IGSP_Database();
|
||||
|
||||
return array(
|
||||
'is_running' => $is_running,
|
||||
'last_sync' => $last_sync ? mysql2date(get_option('date_format') . ' ' . get_option('time_format'), $last_sync) : __('Never', 'instagram-gallery-sync-pro'),
|
||||
'next_sync' => $next_sync ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $next_sync) : __('Not scheduled', 'instagram-gallery-sync-pro'),
|
||||
'total_posts' => $database->count_posts(),
|
||||
);
|
||||
}
|
||||
}
|
||||
459
includes/class-database.php
Normal file
459
includes/class-database.php
Normal file
@@ -0,0 +1,459 @@
|
||||
<?php
|
||||
/**
|
||||
* Database Handler Class
|
||||
*
|
||||
* Handles all database operations including table creation,
|
||||
* CRUD operations for Instagram posts and logging.
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class IGSP_Database
|
||||
*/
|
||||
class IGSP_Database
|
||||
{
|
||||
|
||||
/**
|
||||
* Database version
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $db_version = '1.0.0';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Check for database updates on admin init
|
||||
add_action('admin_init', array($this, 'maybe_upgrade'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create database tables
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function create_tables()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
|
||||
// Posts table
|
||||
$posts_table = IGSP_TABLE_POSTS;
|
||||
$sql_posts = "CREATE TABLE {$posts_table} (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
instagram_id VARCHAR(255) NOT NULL,
|
||||
username VARCHAR(255) NOT NULL,
|
||||
image_local_path VARCHAR(500) DEFAULT NULL,
|
||||
image_thumbnail_path VARCHAR(500) DEFAULT NULL,
|
||||
post_url VARCHAR(500) DEFAULT NULL,
|
||||
caption TEXT DEFAULT NULL,
|
||||
likes_count INT(11) DEFAULT NULL,
|
||||
comments_count INT(11) DEFAULT NULL,
|
||||
posted_at DATETIME DEFAULT NULL,
|
||||
synced_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
file_size INT(11) DEFAULT 0,
|
||||
image_width INT(11) DEFAULT 0,
|
||||
image_height INT(11) DEFAULT 0,
|
||||
is_active TINYINT(1) DEFAULT 1,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY instagram_id (instagram_id),
|
||||
KEY username (username),
|
||||
KEY posted_at (posted_at),
|
||||
KEY is_active (is_active)
|
||||
) {$charset_collate};";
|
||||
|
||||
// Log table
|
||||
$log_table = IGSP_TABLE_LOG;
|
||||
$sql_log = "CREATE TABLE {$log_table} (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
log_type VARCHAR(50) NOT NULL DEFAULT 'info',
|
||||
message TEXT NOT NULL,
|
||||
details LONGTEXT DEFAULT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY log_type (log_type),
|
||||
KEY created_at (created_at)
|
||||
) {$charset_collate};";
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
|
||||
dbDelta($sql_posts);
|
||||
dbDelta($sql_log);
|
||||
|
||||
// Store database version
|
||||
update_option('igsp_db_version', $this->db_version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and run database upgrades if needed
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_upgrade()
|
||||
{
|
||||
$installed_version = get_option('igsp_db_version', '0');
|
||||
|
||||
if (version_compare($installed_version, $this->db_version, '<')) {
|
||||
$this->create_tables();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update a post
|
||||
*
|
||||
* @param array $data Post data
|
||||
* @return int|false Insert ID or false on failure
|
||||
*/
|
||||
public function insert_post($data)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
// Check if post already exists
|
||||
$existing = $this->get_post_by_instagram_id($data['instagram_id']);
|
||||
|
||||
if ($existing) {
|
||||
// Update existing post
|
||||
return $this->update_post($existing->id, $data);
|
||||
}
|
||||
|
||||
// Sanitize data
|
||||
$insert_data = array(
|
||||
'instagram_id' => sanitize_text_field($data['instagram_id']),
|
||||
'username' => sanitize_text_field($data['username']),
|
||||
'image_local_path' => isset($data['image_local_path']) ? sanitize_text_field($data['image_local_path']) : '',
|
||||
'image_thumbnail_path' => isset($data['image_thumbnail_path']) ? sanitize_text_field($data['image_thumbnail_path']) : '',
|
||||
'post_url' => isset($data['post_url']) ? esc_url_raw($data['post_url']) : '',
|
||||
'caption' => isset($data['caption']) ? wp_kses_post($data['caption']) : '',
|
||||
'likes_count' => isset($data['likes_count']) ? absint($data['likes_count']) : null,
|
||||
'comments_count' => isset($data['comments_count']) ? absint($data['comments_count']) : null,
|
||||
'posted_at' => isset($data['posted_at']) ? sanitize_text_field($data['posted_at']) : current_time('mysql'),
|
||||
'synced_at' => current_time('mysql'),
|
||||
'file_size' => isset($data['file_size']) ? absint($data['file_size']) : 0,
|
||||
'image_width' => isset($data['image_width']) ? absint($data['image_width']) : 0,
|
||||
'image_height' => isset($data['image_height']) ? absint($data['image_height']) : 0,
|
||||
'is_active' => isset($data['is_active']) ? absint($data['is_active']) : 1,
|
||||
);
|
||||
|
||||
$result = $wpdb->insert(
|
||||
IGSP_TABLE_POSTS,
|
||||
$insert_data,
|
||||
array(
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%d',
|
||||
'%d',
|
||||
'%s',
|
||||
'%s',
|
||||
'%d',
|
||||
'%d',
|
||||
'%d',
|
||||
'%d'
|
||||
)
|
||||
);
|
||||
|
||||
return $result ? $wpdb->insert_id : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a post
|
||||
*
|
||||
* @param int $id Post ID
|
||||
* @param array $data Post data
|
||||
* @return bool
|
||||
*/
|
||||
public function update_post($id, $data)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$update_data = array();
|
||||
$formats = array();
|
||||
|
||||
// Map of fields and their formats
|
||||
$field_map = array(
|
||||
'image_local_path' => '%s',
|
||||
'image_thumbnail_path' => '%s',
|
||||
'post_url' => '%s',
|
||||
'caption' => '%s',
|
||||
'likes_count' => '%d',
|
||||
'comments_count' => '%d',
|
||||
'posted_at' => '%s',
|
||||
'file_size' => '%d',
|
||||
'image_width' => '%d',
|
||||
'image_height' => '%d',
|
||||
'is_active' => '%d',
|
||||
);
|
||||
|
||||
foreach ($field_map as $field => $format) {
|
||||
if (isset($data[$field])) {
|
||||
$update_data[$field] = $data[$field];
|
||||
$formats[] = $format;
|
||||
}
|
||||
}
|
||||
|
||||
// Always update synced_at
|
||||
$update_data['synced_at'] = current_time('mysql');
|
||||
$formats[] = '%s';
|
||||
|
||||
return $wpdb->update(
|
||||
IGSP_TABLE_POSTS,
|
||||
$update_data,
|
||||
array('id' => $id),
|
||||
$formats,
|
||||
array('%d')
|
||||
) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post by Instagram ID
|
||||
*
|
||||
* @param string $instagram_id Instagram post ID
|
||||
* @return object|null
|
||||
*/
|
||||
public function get_post_by_instagram_id($instagram_id)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
return $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM " . IGSP_TABLE_POSTS . " WHERE instagram_id = %s",
|
||||
$instagram_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all posts
|
||||
*
|
||||
* @param array $args Query arguments
|
||||
* @return array
|
||||
*/
|
||||
public function get_posts($args = array())
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$defaults = array(
|
||||
'limit' => 12,
|
||||
'offset' => 0,
|
||||
'order' => 'newest',
|
||||
'active' => true,
|
||||
'username' => '',
|
||||
);
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
// Build query
|
||||
$where = array('1=1');
|
||||
$values = array();
|
||||
|
||||
if ($args['active']) {
|
||||
$where[] = 'is_active = 1';
|
||||
}
|
||||
|
||||
if (!empty($args['username'])) {
|
||||
$where[] = 'username = %s';
|
||||
$values[] = $args['username'];
|
||||
}
|
||||
|
||||
// Order
|
||||
switch ($args['order']) {
|
||||
case 'oldest':
|
||||
$order = 'posted_at ASC';
|
||||
break;
|
||||
case 'random':
|
||||
$order = 'RAND()';
|
||||
break;
|
||||
case 'newest':
|
||||
default:
|
||||
$order = 'posted_at DESC';
|
||||
break;
|
||||
}
|
||||
|
||||
$where_clause = implode(' AND ', $where);
|
||||
|
||||
// Handle limit
|
||||
$limit_clause = '';
|
||||
if ($args['limit'] > 0) {
|
||||
$limit_clause = $wpdb->prepare('LIMIT %d OFFSET %d', $args['limit'], $args['offset']);
|
||||
}
|
||||
|
||||
$query = "SELECT * FROM " . IGSP_TABLE_POSTS . " WHERE {$where_clause} ORDER BY {$order} {$limit_clause}";
|
||||
|
||||
if (!empty($values)) {
|
||||
$query = $wpdb->prepare($query, $values);
|
||||
}
|
||||
|
||||
return $wpdb->get_results($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count posts
|
||||
*
|
||||
* @param bool $active_only Count only active posts
|
||||
* @return int
|
||||
*/
|
||||
public function count_posts($active_only = true)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$where = $active_only ? 'WHERE is_active = 1' : '';
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM " . IGSP_TABLE_POSTS . " {$where}"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete post
|
||||
*
|
||||
* @param int $id Post ID
|
||||
* @return bool
|
||||
*/
|
||||
public function delete_post($id)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
// Get post for file cleanup
|
||||
$post = $this->get_post($id);
|
||||
|
||||
if ($post) {
|
||||
// Delete associated files
|
||||
$this->delete_post_files($post);
|
||||
}
|
||||
|
||||
return $wpdb->delete(
|
||||
IGSP_TABLE_POSTS,
|
||||
array('id' => $id),
|
||||
array('%d')
|
||||
) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single post by ID
|
||||
*
|
||||
* @param int $id Post ID
|
||||
* @return object|null
|
||||
*/
|
||||
public function get_post($id)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
return $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM " . IGSP_TABLE_POSTS . " WHERE id = %d",
|
||||
$id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete post image files
|
||||
*
|
||||
* @param object $post Post object
|
||||
* @return void
|
||||
*/
|
||||
private function delete_post_files($post)
|
||||
{
|
||||
$upload_dir = wp_upload_dir();
|
||||
$base_path = $upload_dir['basedir'];
|
||||
|
||||
// Delete main image
|
||||
if (!empty($post->image_local_path)) {
|
||||
$file_path = $base_path . '/' . $post->image_local_path;
|
||||
if (file_exists($file_path)) {
|
||||
unlink($file_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete thumbnail
|
||||
if (!empty($post->image_thumbnail_path)) {
|
||||
$thumb_path = $base_path . '/' . $post->image_thumbnail_path;
|
||||
if (file_exists($thumb_path)) {
|
||||
unlink($thumb_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete old posts
|
||||
*
|
||||
* @param int $days Delete posts older than X days
|
||||
* @return int Number of deleted posts
|
||||
*/
|
||||
public function delete_old_posts($days)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
if ($days <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$date_threshold = date('Y-m-d H:i:s', strtotime("-{$days} days"));
|
||||
|
||||
// Get posts to delete for file cleanup
|
||||
$posts = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM " . IGSP_TABLE_POSTS . " WHERE synced_at < %s",
|
||||
$date_threshold
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($posts as $post) {
|
||||
$this->delete_post_files($post);
|
||||
}
|
||||
|
||||
// Delete from database
|
||||
return $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM " . IGSP_TABLE_POSTS . " WHERE synced_at < %s",
|
||||
$date_threshold
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all posts
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete_all_posts()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
// Get all posts for file cleanup
|
||||
$posts = $wpdb->get_results("SELECT * FROM " . IGSP_TABLE_POSTS);
|
||||
|
||||
foreach ($posts as $post) {
|
||||
$this->delete_post_files($post);
|
||||
}
|
||||
|
||||
// Truncate table
|
||||
return $wpdb->query("TRUNCATE TABLE " . IGSP_TABLE_POSTS) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get existing Instagram IDs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_existing_instagram_ids()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
return $wpdb->get_col(
|
||||
"SELECT instagram_id FROM " . IGSP_TABLE_POSTS
|
||||
);
|
||||
}
|
||||
}
|
||||
171
includes/class-gutenberg-block.php
Normal file
171
includes/class-gutenberg-block.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* Gutenberg Block Class
|
||||
*
|
||||
* Handles the Instagram Gallery Gutenberg block.
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class IGSP_Gutenberg_Block
|
||||
*/
|
||||
class IGSP_Gutenberg_Block
|
||||
{
|
||||
|
||||
/**
|
||||
* Initialize block
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
add_action('init', array($this, 'register_block'));
|
||||
add_action('enqueue_block_editor_assets', array($this, 'enqueue_editor_assets'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_block()
|
||||
{
|
||||
if (!function_exists('register_block_type')) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_block_type('igsp/instagram-gallery', array(
|
||||
'editor_script' => 'igsp-block-editor',
|
||||
'editor_style' => 'igsp-block-editor-style',
|
||||
'render_callback' => array($this, 'render_block'),
|
||||
'attributes' => $this->get_block_attributes(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block attributes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_block_attributes()
|
||||
{
|
||||
return array(
|
||||
'layout' => array(
|
||||
'type' => 'string',
|
||||
'default' => get_option('igsp_layout_type', 'grid'),
|
||||
),
|
||||
'columns' => array(
|
||||
'type' => 'number',
|
||||
'default' => get_option('igsp_columns_desktop', 3),
|
||||
),
|
||||
'spacing' => array(
|
||||
'type' => 'number',
|
||||
'default' => get_option('igsp_spacing', 10),
|
||||
),
|
||||
'limit' => array(
|
||||
'type' => 'number',
|
||||
'default' => get_option('igsp_display_limit', 12),
|
||||
),
|
||||
'order' => array(
|
||||
'type' => 'string',
|
||||
'default' => get_option('igsp_order', 'newest'),
|
||||
),
|
||||
'lightbox' => array(
|
||||
'type' => 'boolean',
|
||||
'default' => get_option('igsp_lightbox', 'yes') === 'yes',
|
||||
),
|
||||
'captions' => array(
|
||||
'type' => 'boolean',
|
||||
'default' => get_option('igsp_show_caption', 'no') === 'yes',
|
||||
),
|
||||
'className' => array(
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue editor assets
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_editor_assets()
|
||||
{
|
||||
// Editor script
|
||||
wp_enqueue_script(
|
||||
'igsp-block-editor',
|
||||
IGSP_PLUGIN_URL . 'public/js/block-editor.js',
|
||||
array('wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n', 'wp-server-side-render'),
|
||||
IGSP_VERSION
|
||||
);
|
||||
|
||||
// Editor style
|
||||
wp_enqueue_style(
|
||||
'igsp-block-editor-style',
|
||||
IGSP_PLUGIN_URL . 'public/css/block-editor.css',
|
||||
array(),
|
||||
IGSP_VERSION
|
||||
);
|
||||
|
||||
// Localize
|
||||
wp_localize_script('igsp-block-editor', 'igspBlock', array(
|
||||
'pluginUrl' => IGSP_PLUGIN_URL,
|
||||
'defaultLayout' => get_option('igsp_layout_type', 'grid'),
|
||||
'layouts' => array(
|
||||
array('value' => 'grid', 'label' => __('Grid', 'instagram-gallery-sync-pro')),
|
||||
array('value' => 'masonry', 'label' => __('Masonry', 'instagram-gallery-sync-pro')),
|
||||
array('value' => 'slider', 'label' => __('Slider', 'instagram-gallery-sync-pro')),
|
||||
array('value' => 'justified', 'label' => __('Justified', 'instagram-gallery-sync-pro')),
|
||||
array('value' => 'list', 'label' => __('List', 'instagram-gallery-sync-pro')),
|
||||
),
|
||||
'orders' => array(
|
||||
array('value' => 'newest', 'label' => __('Newest First', 'instagram-gallery-sync-pro')),
|
||||
array('value' => 'oldest', 'label' => __('Oldest First', 'instagram-gallery-sync-pro')),
|
||||
array('value' => 'random', 'label' => __('Random', 'instagram-gallery-sync-pro')),
|
||||
),
|
||||
'strings' => array(
|
||||
'title' => __('Instagram Gallery', 'instagram-gallery-sync-pro'),
|
||||
'description' => __('Display your Instagram photos in a beautiful gallery.', 'instagram-gallery-sync-pro'),
|
||||
'layout' => __('Layout', 'instagram-gallery-sync-pro'),
|
||||
'columns' => __('Columns', 'instagram-gallery-sync-pro'),
|
||||
'spacing' => __('Spacing', 'instagram-gallery-sync-pro'),
|
||||
'limit' => __('Number of Images', 'instagram-gallery-sync-pro'),
|
||||
'order' => __('Order', 'instagram-gallery-sync-pro'),
|
||||
'lightbox' => __('Enable Lightbox', 'instagram-gallery-sync-pro'),
|
||||
'captions' => __('Show Captions', 'instagram-gallery-sync-pro'),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render block
|
||||
*
|
||||
* @param array $attributes Block attributes
|
||||
* @return string
|
||||
*/
|
||||
public function render_block($attributes)
|
||||
{
|
||||
// Convert block attributes to shortcode attributes
|
||||
$atts = array(
|
||||
'layout' => $attributes['layout'] ?? 'grid',
|
||||
'columns' => $attributes['columns'] ?? 3,
|
||||
'spacing' => $attributes['spacing'] ?? 10,
|
||||
'limit' => $attributes['limit'] ?? 12,
|
||||
'order' => $attributes['order'] ?? 'newest',
|
||||
'lightbox' => $attributes['lightbox'] ? 'yes' : 'no',
|
||||
'captions' => $attributes['captions'] ? 'yes' : 'no',
|
||||
'class' => $attributes['className'] ?? '',
|
||||
);
|
||||
|
||||
// Use shortcode handler for rendering
|
||||
$shortcode = new IGSP_Shortcode();
|
||||
return $shortcode->render($atts);
|
||||
}
|
||||
}
|
||||
477
includes/class-image-handler.php
Normal file
477
includes/class-image-handler.php
Normal file
@@ -0,0 +1,477 @@
|
||||
<?php
|
||||
/**
|
||||
* Image Handler Class
|
||||
*
|
||||
* Handles downloading, saving, and processing of Instagram images.
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class IGSP_Image_Handler
|
||||
*/
|
||||
class IGSP_Image_Handler
|
||||
{
|
||||
|
||||
/**
|
||||
* Upload directory path
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $upload_path;
|
||||
|
||||
/**
|
||||
* Upload directory URL
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $upload_url;
|
||||
|
||||
/**
|
||||
* Thumbnail sizes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $thumbnail_sizes = array(
|
||||
'small' => 150,
|
||||
'medium' => 320,
|
||||
'large' => 640,
|
||||
);
|
||||
|
||||
/**
|
||||
* Logger instance
|
||||
*
|
||||
* @var IGSP_Logger
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->setup_paths();
|
||||
$this->logger = new IGSP_Logger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup upload paths
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setup_paths()
|
||||
{
|
||||
$upload_dir = wp_upload_dir();
|
||||
$this->upload_path = $upload_dir['basedir'] . '/' . IGSP_UPLOAD_DIR;
|
||||
$this->upload_url = $upload_dir['baseurl'] . '/' . IGSP_UPLOAD_DIR;
|
||||
|
||||
// Ensure directory exists
|
||||
if (!file_exists($this->upload_path)) {
|
||||
wp_mkdir_p($this->upload_path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download and save an image
|
||||
*
|
||||
* @param string $url Remote image URL
|
||||
* @param string $instagram_id Instagram post ID for naming
|
||||
* @return array|WP_Error Array with paths or WP_Error
|
||||
*/
|
||||
public function download_image($url, $instagram_id)
|
||||
{
|
||||
if (empty($url) || empty($instagram_id)) {
|
||||
return new WP_Error('invalid_params', __('Invalid parameters for image download.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
// Sanitize ID for filename
|
||||
$safe_id = sanitize_file_name($instagram_id);
|
||||
|
||||
// Download the image
|
||||
$temp_file = download_url($url, 30);
|
||||
|
||||
if (is_wp_error($temp_file)) {
|
||||
$this->logger->error(
|
||||
sprintf(__('Failed to download image: %s', 'instagram-gallery-sync-pro'), $temp_file->get_error_message()),
|
||||
array('url' => $url)
|
||||
);
|
||||
return $temp_file;
|
||||
}
|
||||
|
||||
// Validate the downloaded file
|
||||
$validation = $this->validate_image($temp_file);
|
||||
|
||||
if (is_wp_error($validation)) {
|
||||
@unlink($temp_file);
|
||||
return $validation;
|
||||
}
|
||||
|
||||
// Get file info
|
||||
$file_info = $validation;
|
||||
$extension = $file_info['extension'];
|
||||
|
||||
// Generate filenames
|
||||
$filename = $safe_id . '.' . $extension;
|
||||
$filepath = $this->upload_path . '/' . $filename;
|
||||
|
||||
// Move file to upload directory
|
||||
$moved = rename($temp_file, $filepath);
|
||||
|
||||
if (!$moved) {
|
||||
@unlink($temp_file);
|
||||
return new WP_Error('move_failed', __('Failed to move downloaded file.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
// Set correct permissions
|
||||
chmod($filepath, 0644);
|
||||
|
||||
// Generate thumbnails
|
||||
$thumbnail_path = $this->generate_thumbnail($filepath, $safe_id, $extension);
|
||||
|
||||
// Get image dimensions
|
||||
$dimensions = getimagesize($filepath);
|
||||
|
||||
// Prepare result
|
||||
$result = array(
|
||||
'local_path' => IGSP_UPLOAD_DIR . '/' . $filename,
|
||||
'thumbnail_path' => $thumbnail_path,
|
||||
'full_url' => $this->upload_url . '/' . $filename,
|
||||
'thumbnail_url' => !empty($thumbnail_path) ? $this->upload_url . '/' . str_replace(IGSP_UPLOAD_DIR . '/', '', $thumbnail_path) : '',
|
||||
'file_size' => filesize($filepath),
|
||||
'width' => $dimensions ? $dimensions[0] : 0,
|
||||
'height' => $dimensions ? $dimensions[1] : 0,
|
||||
'mime_type' => $file_info['mime_type'],
|
||||
);
|
||||
|
||||
$this->logger->info(
|
||||
sprintf(__('Image saved: %s', 'instagram-gallery-sync-pro'), $filename),
|
||||
array('size' => $result['file_size'], 'dimensions' => $dimensions[0] . 'x' . $dimensions[1])
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate downloaded image
|
||||
*
|
||||
* @param string $filepath Path to temp file
|
||||
* @return array|WP_Error File info or error
|
||||
*/
|
||||
private function validate_image($filepath)
|
||||
{
|
||||
if (!file_exists($filepath)) {
|
||||
return new WP_Error('file_not_found', __('Downloaded file not found.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
// Check file size (max 20MB)
|
||||
$max_size = 20 * 1024 * 1024;
|
||||
if (filesize($filepath) > $max_size) {
|
||||
return new WP_Error('file_too_large', __('File exceeds maximum size limit.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
// Verify it's actually an image
|
||||
$file_info = wp_check_filetype_and_ext($filepath, basename($filepath));
|
||||
$image_info = @getimagesize($filepath);
|
||||
|
||||
if (!$image_info) {
|
||||
return new WP_Error('not_an_image', __('File is not a valid image.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
// Check allowed types
|
||||
$allowed_types = array('image/jpeg', 'image/png', 'image/gif', 'image/webp');
|
||||
$mime_type = $image_info['mime'];
|
||||
|
||||
if (!in_array($mime_type, $allowed_types, true)) {
|
||||
return new WP_Error('invalid_type', __('Image type not allowed.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
// Map mime to extension
|
||||
$ext_map = array(
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif',
|
||||
'image/webp' => 'webp',
|
||||
);
|
||||
|
||||
return array(
|
||||
'mime_type' => $mime_type,
|
||||
'extension' => $ext_map[$mime_type] ?? 'jpg',
|
||||
'width' => $image_info[0],
|
||||
'height' => $image_info[1],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate thumbnail from image
|
||||
*
|
||||
* @param string $filepath Original image path
|
||||
* @param string $safe_id Sanitized ID for naming
|
||||
* @param string $extension File extension
|
||||
* @return string|null Thumbnail path or null on failure
|
||||
*/
|
||||
public function generate_thumbnail($filepath, $safe_id, $extension)
|
||||
{
|
||||
// Use WordPress image editor
|
||||
$editor = wp_get_image_editor($filepath);
|
||||
|
||||
if (is_wp_error($editor)) {
|
||||
$this->logger->warning(
|
||||
sprintf(__('Could not create image editor: %s', 'instagram-gallery-sync-pro'), $editor->get_error_message())
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get current size
|
||||
$size = $editor->get_size();
|
||||
|
||||
// Calculate thumbnail size (medium by default)
|
||||
$thumb_size = $this->thumbnail_sizes['medium'];
|
||||
|
||||
// Resize while maintaining aspect ratio
|
||||
$editor->resize($thumb_size, $thumb_size, true); // true = crop
|
||||
|
||||
// Optimize quality
|
||||
$editor->set_quality(85);
|
||||
|
||||
// Save thumbnail
|
||||
$thumb_dir = $this->upload_path . '/thumbnails';
|
||||
|
||||
if (!file_exists($thumb_dir)) {
|
||||
wp_mkdir_p($thumb_dir);
|
||||
}
|
||||
|
||||
$thumb_filename = $safe_id . '-thumb.' . $extension;
|
||||
$thumb_path = $thumb_dir . '/' . $thumb_filename;
|
||||
|
||||
$saved = $editor->save($thumb_path);
|
||||
|
||||
if (is_wp_error($saved)) {
|
||||
$this->logger->warning(
|
||||
sprintf(__('Failed to save thumbnail: %s', 'instagram-gallery-sync-pro'), $saved->get_error_message())
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return IGSP_UPLOAD_DIR . '/thumbnails/' . $thumb_filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert image to WebP (optional)
|
||||
*
|
||||
* @param string $filepath Original image path
|
||||
* @return string|null WebP path or null
|
||||
*/
|
||||
public function convert_to_webp($filepath)
|
||||
{
|
||||
if (!function_exists('imagewebp')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$image_info = getimagesize($filepath);
|
||||
|
||||
if (!$image_info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mime = $image_info['mime'];
|
||||
|
||||
// Create image resource based on type
|
||||
switch ($mime) {
|
||||
case 'image/jpeg':
|
||||
$image = imagecreatefromjpeg($filepath);
|
||||
break;
|
||||
case 'image/png':
|
||||
$image = imagecreatefrompng($filepath);
|
||||
break;
|
||||
case 'image/gif':
|
||||
$image = imagecreatefromgif($filepath);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$image) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generate WebP path
|
||||
$webp_path = preg_replace('/\.(jpe?g|png|gif)$/i', '.webp', $filepath);
|
||||
|
||||
// Save as WebP
|
||||
$success = imagewebp($image, $webp_path, 85);
|
||||
imagedestroy($image);
|
||||
|
||||
if (!$success) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str_replace($this->upload_path, IGSP_UPLOAD_DIR, $webp_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip EXIF data from image
|
||||
*
|
||||
* @param string $filepath Image path
|
||||
* @return bool
|
||||
*/
|
||||
public function strip_exif($filepath)
|
||||
{
|
||||
$image_info = getimagesize($filepath);
|
||||
|
||||
if (!$image_info || $image_info['mime'] !== 'image/jpeg') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$image = imagecreatefromjpeg($filepath);
|
||||
|
||||
if (!$image) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Re-save without EXIF
|
||||
$result = imagejpeg($image, $filepath, 90);
|
||||
imagedestroy($image);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete image files for a post
|
||||
*
|
||||
* @param string $local_path Main image path
|
||||
* @param string $thumbnail_path Thumbnail path
|
||||
* @return bool
|
||||
*/
|
||||
public function delete_images($local_path, $thumbnail_path = '')
|
||||
{
|
||||
$upload_dir = wp_upload_dir();
|
||||
$base_path = $upload_dir['basedir'];
|
||||
|
||||
$deleted = false;
|
||||
|
||||
if (!empty($local_path)) {
|
||||
$full_path = $base_path . '/' . $local_path;
|
||||
if (file_exists($full_path) && strpos($full_path, IGSP_UPLOAD_DIR) !== false) {
|
||||
$deleted = unlink($full_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($thumbnail_path)) {
|
||||
$thumb_full_path = $base_path . '/' . $thumbnail_path;
|
||||
if (file_exists($thumb_full_path) && strpos($thumb_full_path, IGSP_UPLOAD_DIR) !== false) {
|
||||
unlink($thumb_full_path);
|
||||
}
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image URL from local path
|
||||
*
|
||||
* @param string $local_path Local path
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_url($local_path)
|
||||
{
|
||||
if (empty($local_path)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$upload_dir = wp_upload_dir();
|
||||
return $upload_dir['baseurl'] . '/' . $local_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if image exists locally
|
||||
*
|
||||
* @param string $local_path Local path
|
||||
* @return bool
|
||||
*/
|
||||
public function image_exists($local_path)
|
||||
{
|
||||
if (empty($local_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$upload_dir = wp_upload_dir();
|
||||
$full_path = $upload_dir['basedir'] . '/' . $local_path;
|
||||
|
||||
return file_exists($full_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total size of all stored images
|
||||
*
|
||||
* @return int Size in bytes
|
||||
*/
|
||||
public function get_total_storage_used()
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
if (!is_dir($this->upload_path)) {
|
||||
return $total;
|
||||
}
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($this->upload_path, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$total += $file->getSize();
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-readable storage size
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_storage_used_formatted()
|
||||
{
|
||||
$bytes = $this->get_total_storage_used();
|
||||
return size_format($bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all stored images
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear_all_images()
|
||||
{
|
||||
if (!is_dir($this->upload_path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($this->upload_path, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($iterator as $item) {
|
||||
if ($item->isDir()) {
|
||||
rmdir($item->getRealPath());
|
||||
} else {
|
||||
// Don't delete index.php and .htaccess
|
||||
$filename = $item->getFilename();
|
||||
if ($filename !== 'index.php' && $filename !== '.htaccess') {
|
||||
unlink($item->getRealPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recreate thumbnails directory
|
||||
wp_mkdir_p($this->upload_path . '/thumbnails');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
331
includes/class-logger.php
Normal file
331
includes/class-logger.php
Normal file
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
/**
|
||||
* Logger Class
|
||||
*
|
||||
* Handles logging of plugin events, errors, and debugging information.
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class IGSP_Logger
|
||||
*/
|
||||
class IGSP_Logger
|
||||
{
|
||||
|
||||
/**
|
||||
* Log types
|
||||
*/
|
||||
const TYPE_SUCCESS = 'success';
|
||||
const TYPE_ERROR = 'error';
|
||||
const TYPE_WARNING = 'warning';
|
||||
const TYPE_INFO = 'info';
|
||||
|
||||
/**
|
||||
* Maximum log entries to keep
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $max_entries = 500;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Schedule log cleanup
|
||||
add_action('igsp_cleanup_logs', array($this, 'cleanup_old_logs'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a log entry
|
||||
*
|
||||
* @param string $type Log type (success, error, warning, info)
|
||||
* @param string $message Log message
|
||||
* @param array $details Additional details (optional)
|
||||
* @return int|false Insert ID or false
|
||||
*/
|
||||
public function log($type, $message, $details = array())
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
// Validate type
|
||||
$valid_types = array(self::TYPE_SUCCESS, self::TYPE_ERROR, self::TYPE_WARNING, self::TYPE_INFO);
|
||||
if (!in_array($type, $valid_types, true)) {
|
||||
$type = self::TYPE_INFO;
|
||||
}
|
||||
|
||||
// Only log in debug mode unless it's an error
|
||||
$debug_mode = get_option('igsp_debug_mode', 'no');
|
||||
if ($debug_mode !== 'yes' && $type !== self::TYPE_ERROR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = $wpdb->insert(
|
||||
IGSP_TABLE_LOG,
|
||||
array(
|
||||
'log_type' => $type,
|
||||
'message' => sanitize_text_field($message),
|
||||
'details' => !empty($details) ? wp_json_encode($details) : null,
|
||||
'created_at' => current_time('mysql'),
|
||||
),
|
||||
array('%s', '%s', '%s', '%s')
|
||||
);
|
||||
|
||||
// Also log to error_log if debug is enabled
|
||||
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||
error_log(sprintf(
|
||||
'[IGSP %s] %s %s',
|
||||
strtoupper($type),
|
||||
$message,
|
||||
!empty($details) ? wp_json_encode($details) : ''
|
||||
));
|
||||
}
|
||||
|
||||
return $result ? $wpdb->insert_id : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log success
|
||||
*
|
||||
* @param string $message Message
|
||||
* @param array $details Details
|
||||
* @return int|false
|
||||
*/
|
||||
public function success($message, $details = array())
|
||||
{
|
||||
return $this->log(self::TYPE_SUCCESS, $message, $details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error
|
||||
*
|
||||
* @param string $message Message
|
||||
* @param array $details Details
|
||||
* @return int|false
|
||||
*/
|
||||
public function error($message, $details = array())
|
||||
{
|
||||
return $this->log(self::TYPE_ERROR, $message, $details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log warning
|
||||
*
|
||||
* @param string $message Message
|
||||
* @param array $details Details
|
||||
* @return int|false
|
||||
*/
|
||||
public function warning($message, $details = array())
|
||||
{
|
||||
return $this->log(self::TYPE_WARNING, $message, $details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log info
|
||||
*
|
||||
* @param string $message Message
|
||||
* @param array $details Details
|
||||
* @return int|false
|
||||
*/
|
||||
public function info($message, $details = array())
|
||||
{
|
||||
return $this->log(self::TYPE_INFO, $message, $details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get log entries
|
||||
*
|
||||
* @param array $args Query arguments
|
||||
* @return array
|
||||
*/
|
||||
public function get_logs($args = array())
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$defaults = array(
|
||||
'limit' => 50,
|
||||
'offset' => 0,
|
||||
'type' => '',
|
||||
'search' => '',
|
||||
);
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
$where = array('1=1');
|
||||
$values = array();
|
||||
|
||||
if (!empty($args['type'])) {
|
||||
$where[] = 'log_type = %s';
|
||||
$values[] = $args['type'];
|
||||
}
|
||||
|
||||
if (!empty($args['search'])) {
|
||||
$where[] = 'message LIKE %s';
|
||||
$values[] = '%' . $wpdb->esc_like($args['search']) . '%';
|
||||
}
|
||||
|
||||
$where_clause = implode(' AND ', $where);
|
||||
|
||||
$query = "SELECT * FROM " . IGSP_TABLE_LOG . "
|
||||
WHERE {$where_clause}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT %d OFFSET %d";
|
||||
|
||||
$values[] = $args['limit'];
|
||||
$values[] = $args['offset'];
|
||||
|
||||
return $wpdb->get_results(
|
||||
$wpdb->prepare($query, $values)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count log entries
|
||||
*
|
||||
* @param string $type Optional log type filter
|
||||
* @return int
|
||||
*/
|
||||
public function count_logs($type = '')
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
if (!empty($type)) {
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM " . IGSP_TABLE_LOG . " WHERE log_type = %s",
|
||||
$type
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM " . IGSP_TABLE_LOG
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all logs
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear_logs()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
return $wpdb->query("TRUNCATE TABLE " . IGSP_TABLE_LOG) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup old log entries
|
||||
*
|
||||
* @return int Number of deleted entries
|
||||
*/
|
||||
public function cleanup_old_logs()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
// Keep only the most recent entries
|
||||
return $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM " . IGSP_TABLE_LOG . "
|
||||
WHERE id NOT IN (
|
||||
SELECT id FROM (
|
||||
SELECT id FROM " . IGSP_TABLE_LOG . "
|
||||
ORDER BY created_at DESC
|
||||
LIMIT %d
|
||||
) AS t
|
||||
)",
|
||||
$this->max_entries
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete logs older than X days
|
||||
*
|
||||
* @param int $days Days to keep
|
||||
* @return int
|
||||
*/
|
||||
public function delete_logs_older_than($days)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$date_threshold = date('Y-m-d H:i:s', strtotime("-{$days} days"));
|
||||
|
||||
return $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM " . IGSP_TABLE_LOG . " WHERE created_at < %s",
|
||||
$date_threshold
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logs formatted for display
|
||||
*
|
||||
* @param int $limit Number of entries
|
||||
* @return array
|
||||
*/
|
||||
public function get_formatted_logs($limit = 50)
|
||||
{
|
||||
$logs = $this->get_logs(array('limit' => $limit));
|
||||
$formatted = array();
|
||||
|
||||
foreach ($logs as $log) {
|
||||
$formatted[] = array(
|
||||
'id' => $log->id,
|
||||
'type' => $log->log_type,
|
||||
'type_label' => $this->get_type_label($log->log_type),
|
||||
'type_class' => $this->get_type_class($log->log_type),
|
||||
'message' => $log->message,
|
||||
'details' => !empty($log->details) ? json_decode($log->details, true) : array(),
|
||||
'date' => mysql2date(get_option('date_format') . ' ' . get_option('time_format'), $log->created_at),
|
||||
'relative' => human_time_diff(strtotime($log->created_at), current_time('timestamp')) . ' ' . __('ago', 'instagram-gallery-sync-pro'),
|
||||
);
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get type label
|
||||
*
|
||||
* @param string $type Log type
|
||||
* @return string
|
||||
*/
|
||||
private function get_type_label($type)
|
||||
{
|
||||
$labels = array(
|
||||
self::TYPE_SUCCESS => __('Success', 'instagram-gallery-sync-pro'),
|
||||
self::TYPE_ERROR => __('Error', 'instagram-gallery-sync-pro'),
|
||||
self::TYPE_WARNING => __('Warning', 'instagram-gallery-sync-pro'),
|
||||
self::TYPE_INFO => __('Info', 'instagram-gallery-sync-pro'),
|
||||
);
|
||||
|
||||
return isset($labels[$type]) ? $labels[$type] : $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get type CSS class
|
||||
*
|
||||
* @param string $type Log type
|
||||
* @return string
|
||||
*/
|
||||
private function get_type_class($type)
|
||||
{
|
||||
$classes = array(
|
||||
self::TYPE_SUCCESS => 'igsp-log-success',
|
||||
self::TYPE_ERROR => 'igsp-log-error',
|
||||
self::TYPE_WARNING => 'igsp-log-warning',
|
||||
self::TYPE_INFO => 'igsp-log-info',
|
||||
);
|
||||
|
||||
return isset($classes[$type]) ? $classes[$type] : 'igsp-log-info';
|
||||
}
|
||||
}
|
||||
938
includes/class-scraper.php
Normal file
938
includes/class-scraper.php
Normal file
@@ -0,0 +1,938 @@
|
||||
<?php
|
||||
/**
|
||||
* Instagram Scraper Class
|
||||
*
|
||||
* Handles scraping of public Instagram profiles without official API.
|
||||
* Uses multiple fallback methods and anti-blocking measures.
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class IGSP_Scraper
|
||||
*/
|
||||
class IGSP_Scraper
|
||||
{
|
||||
|
||||
/**
|
||||
* Array of User-Agents for rotation
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $user_agents = array(
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15',
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
||||
);
|
||||
|
||||
/**
|
||||
* Request timeout in seconds
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $timeout;
|
||||
|
||||
/**
|
||||
* Maximum retry attempts
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $max_retries = 3;
|
||||
|
||||
/**
|
||||
* Minimum delay between requests (seconds)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $min_delay = 2;
|
||||
|
||||
/**
|
||||
* Maximum delay between requests (seconds)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $max_delay = 5;
|
||||
|
||||
/**
|
||||
* Logger instance
|
||||
*
|
||||
* @var IGSP_Logger
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->timeout = (int) get_option('igsp_request_timeout', 30);
|
||||
$this->logger = new IGSP_Logger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Instagram profile data
|
||||
*
|
||||
* @param string $username Instagram username
|
||||
* @return array|WP_Error Array of posts or WP_Error on failure
|
||||
*/
|
||||
public function fetch_profile_data($username)
|
||||
{
|
||||
$username = sanitize_text_field($username);
|
||||
|
||||
if (empty($username)) {
|
||||
return new WP_Error('no_username', __('Username not provided.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
// Clean username (remove @ if present)
|
||||
$username = ltrim($username, '@');
|
||||
|
||||
$this->logger->info(sprintf(__('Starting fetch for username: %s', 'instagram-gallery-sync-pro'), $username));
|
||||
|
||||
// Try multiple methods in order
|
||||
$methods = array(
|
||||
'fetch_via_web_profile_info',
|
||||
'fetch_via_embed_page',
|
||||
'fetch_via_profile_page',
|
||||
);
|
||||
|
||||
$last_error = null;
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$this->random_delay();
|
||||
|
||||
$result = $this->$method($username);
|
||||
|
||||
if (!is_wp_error($result) && !empty($result)) {
|
||||
$this->logger->success(
|
||||
sprintf(__('Successfully fetched %d posts using %s', 'instagram-gallery-sync-pro'), count($result), $method),
|
||||
array('method' => $method, 'count' => count($result))
|
||||
);
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
$last_error = $result;
|
||||
$this->logger->warning(
|
||||
sprintf(__('Method %s failed: %s', 'instagram-gallery-sync-pro'), $method, $result->get_error_message()),
|
||||
array('method' => $method)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// All methods failed
|
||||
$error = $last_error ?? new WP_Error('fetch_failed', __('All scraping methods failed.', 'instagram-gallery-sync-pro'));
|
||||
$this->logger->error(__('Failed to fetch Instagram data after trying all methods.', 'instagram-gallery-sync-pro'));
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch via web profile info API
|
||||
* This uses the internal API that Instagram's web app uses
|
||||
*
|
||||
* @param string $username Instagram username
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function fetch_via_web_profile_info($username)
|
||||
{
|
||||
// First, we need to get the app id and other required headers from the main page
|
||||
$main_url = 'https://www.instagram.com/';
|
||||
$main_response = $this->make_request($main_url);
|
||||
|
||||
if (is_wp_error($main_response)) {
|
||||
return $main_response;
|
||||
}
|
||||
|
||||
$main_body = wp_remote_retrieve_body($main_response);
|
||||
|
||||
// Extract the app ID
|
||||
$app_id = $this->extract_app_id($main_body);
|
||||
|
||||
$this->random_delay();
|
||||
|
||||
// Now fetch the profile using the web profile info endpoint
|
||||
$url = 'https://www.instagram.com/api/v1/users/web_profile_info/?username=' . urlencode($username);
|
||||
|
||||
$headers = array(
|
||||
'X-IG-App-ID' => $app_id ?: '936619743392459',
|
||||
'X-ASBD-ID' => '129477',
|
||||
'X-IG-WWW-Claim' => '0',
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Referer' => 'https://www.instagram.com/' . $username . '/',
|
||||
'Accept' => '*/*',
|
||||
);
|
||||
|
||||
$response = $this->make_request($url, array('headers' => $headers));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
$data = json_decode($body, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
return new WP_Error('json_error', __('Invalid JSON response from web profile info.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
// Check for user data
|
||||
if (empty($data['data']['user'])) {
|
||||
return new WP_Error('no_user', __('No user data found.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
$user = $data['data']['user'];
|
||||
|
||||
// Check if profile is private
|
||||
if (!empty($user['is_private'])) {
|
||||
return new WP_Error('private_profile', __('This Instagram profile is private.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
// Get media
|
||||
$edges = $user['edge_owner_to_timeline_media']['edges'] ?? array();
|
||||
|
||||
if (empty($edges)) {
|
||||
return new WP_Error('no_media', __('No media found on this profile.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
return $this->parse_graphql_edges($edges, $username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch via embed page (iframeable content)
|
||||
*
|
||||
* @param string $username Instagram username
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function fetch_via_embed_page($username)
|
||||
{
|
||||
// First get the profile to find some post shortcodes
|
||||
$profile_url = 'https://www.instagram.com/' . $username . '/';
|
||||
$response = $this->make_request($profile_url);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
|
||||
// Try to extract shortcodes from the HTML
|
||||
$shortcodes = $this->extract_shortcodes($body);
|
||||
|
||||
if (empty($shortcodes)) {
|
||||
return new WP_Error('no_shortcodes', __('Could not find any post links.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
$posts = array();
|
||||
$max_images = min((int) get_option('igsp_max_images', 12), count($shortcodes));
|
||||
|
||||
// Fetch each post via embed
|
||||
for ($i = 0; $i < $max_images; $i++) {
|
||||
$this->random_delay(1, 2);
|
||||
|
||||
$embed_url = 'https://www.instagram.com/p/' . $shortcodes[$i] . '/embed/';
|
||||
$embed_response = $this->make_request($embed_url);
|
||||
|
||||
if (is_wp_error($embed_response)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$embed_body = wp_remote_retrieve_body($embed_response);
|
||||
$post_data = $this->parse_embed_page($embed_body, $shortcodes[$i], $username);
|
||||
|
||||
if ($post_data) {
|
||||
$posts[] = $post_data;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($posts)) {
|
||||
return new WP_Error('no_posts', __('Could not extract any posts from embeds.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch via profile page HTML parsing (fallback)
|
||||
*
|
||||
* @param string $username Instagram username
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function fetch_via_profile_page($username)
|
||||
{
|
||||
$url = 'https://www.instagram.com/' . $username . '/';
|
||||
|
||||
$response = $this->make_request($url);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
|
||||
if (empty($body)) {
|
||||
return new WP_Error('empty_response', __('Empty response from Instagram.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
// Try multiple parsing strategies
|
||||
$posts = $this->parse_require_js_data($body, $username);
|
||||
|
||||
if (!empty($posts)) {
|
||||
return $posts;
|
||||
}
|
||||
|
||||
$posts = $this->parse_shared_data($body, $username);
|
||||
|
||||
if (!empty($posts)) {
|
||||
return $posts;
|
||||
}
|
||||
|
||||
$posts = $this->parse_preloaded_data($body, $username);
|
||||
|
||||
if (!empty($posts)) {
|
||||
return $posts;
|
||||
}
|
||||
|
||||
$posts = $this->parse_og_images($body, $username);
|
||||
|
||||
if (!empty($posts)) {
|
||||
return $posts;
|
||||
}
|
||||
|
||||
return new WP_Error('parse_failed', __('Could not parse Instagram profile page.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract app ID from Instagram main page
|
||||
*
|
||||
* @param string $html HTML content
|
||||
* @return string|null
|
||||
*/
|
||||
private function extract_app_id($html)
|
||||
{
|
||||
// Look for the app ID in various places
|
||||
$patterns = array(
|
||||
'/"X-IG-App-ID":"(\d+)"/',
|
||||
'/appId":"(\d+)"/',
|
||||
'/APP_ID":"(\d+)"/',
|
||||
'/{"APP_ID":"(\d+)"/',
|
||||
);
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match($pattern, $html, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract shortcodes from profile HTML
|
||||
*
|
||||
* @param string $html HTML content
|
||||
* @return array
|
||||
*/
|
||||
private function extract_shortcodes($html)
|
||||
{
|
||||
$shortcodes = array();
|
||||
|
||||
// Pattern to find post links
|
||||
$patterns = array(
|
||||
'/\/p\/([A-Za-z0-9_-]+)\//',
|
||||
'/\/reel\/([A-Za-z0-9_-]+)\//',
|
||||
'/"shortcode":"([A-Za-z0-9_-]+)"/',
|
||||
'/"code":"([A-Za-z0-9_-]+)"/',
|
||||
);
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match_all($pattern, $html, $matches)) {
|
||||
$shortcodes = array_merge($shortcodes, $matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicates and return
|
||||
return array_unique($shortcodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse embed page HTML
|
||||
*
|
||||
* @param string $html HTML content
|
||||
* @param string $shortcode Post shortcode
|
||||
* @param string $username Username
|
||||
* @return array|null
|
||||
*/
|
||||
private function parse_embed_page($html, $shortcode, $username)
|
||||
{
|
||||
// Look for image URL in embed page
|
||||
$image_url = null;
|
||||
$caption = '';
|
||||
|
||||
// Try to find the main image
|
||||
$img_patterns = array(
|
||||
'/<img[^>]+class="[^"]*EmbeddedMediaImage[^"]*"[^>]+src="([^"]+)"/',
|
||||
'/<img[^>]+src="([^"]+instagram[^"]+)"[^>]+class="[^"]*Image/',
|
||||
'/property="og:image"[^>]+content="([^"]+)"/',
|
||||
'/content="([^"]+)"[^>]+property="og:image"/',
|
||||
'/<img[^>]+srcset="([^"]+)"/',
|
||||
);
|
||||
|
||||
foreach ($img_patterns as $pattern) {
|
||||
if (preg_match($pattern, $html, $matches)) {
|
||||
$image_url = $matches[1];
|
||||
// Handle srcset - get the first URL
|
||||
if (strpos($image_url, ' ') !== false) {
|
||||
$image_url = explode(' ', $image_url)[0];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to extract from JSON in the page
|
||||
if (empty($image_url)) {
|
||||
if (preg_match('/"display_url":"([^"]+)"/', $html, $matches)) {
|
||||
$image_url = stripcslashes($matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($image_url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract caption
|
||||
if (preg_match('/<div class="[^"]*Caption[^"]*"[^>]*>.*?<span[^>]*>(.+?)<\/span>/s', $html, $matches)) {
|
||||
$caption = strip_tags($matches[1]);
|
||||
}
|
||||
|
||||
// Try to get timestamp
|
||||
$timestamp = null;
|
||||
if (preg_match('/datetime="([^"]+)"/', $html, $matches)) {
|
||||
$timestamp = strtotime($matches[1]);
|
||||
}
|
||||
|
||||
return array(
|
||||
'instagram_id' => md5($shortcode . $username),
|
||||
'shortcode' => $shortcode,
|
||||
'username' => $username,
|
||||
'image_url' => $image_url,
|
||||
'thumbnail_url' => $image_url,
|
||||
'post_url' => 'https://www.instagram.com/p/' . $shortcode . '/',
|
||||
'caption' => $caption,
|
||||
'likes_count' => null,
|
||||
'comments_count' => null,
|
||||
'posted_at' => $timestamp ? date('Y-m-d H:i:s', $timestamp) : current_time('mysql'),
|
||||
'image_width' => 0,
|
||||
'image_height' => 0,
|
||||
'is_video' => false,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse require.js data from modern Instagram
|
||||
*
|
||||
* @param string $html HTML content
|
||||
* @param string $username Username
|
||||
* @return array
|
||||
*/
|
||||
private function parse_require_js_data($html, $username)
|
||||
{
|
||||
// Modern Instagram uses require.js with data in script tags
|
||||
$pattern = '/requireLazy\(\["JSScheduler"[^}]+},\s*function\(\)[^{]*{\s*"use strict";\s*(.+?)\s*}\s*\)/s';
|
||||
|
||||
// Try to find JSON data in script tags
|
||||
$json_patterns = array(
|
||||
'/"xdt_api__v1__feed__user_timeline_graphql_connection":\s*({.+?})\s*,\s*"extensions"/',
|
||||
'/"edge_owner_to_timeline_media":\s*({.+?})\s*,\s*"edge_/',
|
||||
'/{"xdt_api__v1__users__web_profile_info".*?"user":\s*({.+?})\s*}\s*}/',
|
||||
);
|
||||
|
||||
foreach ($json_patterns as $pattern) {
|
||||
if (preg_match($pattern, $html, $matches)) {
|
||||
$data = json_decode($matches[1], true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && !empty($data)) {
|
||||
$edges = $data['edges'] ?? array();
|
||||
if (!empty($edges)) {
|
||||
return $this->parse_graphql_edges($edges, $username);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse preloaded data from script tags
|
||||
*
|
||||
* @param string $html HTML content
|
||||
* @param string $username Username
|
||||
* @return array
|
||||
*/
|
||||
private function parse_preloaded_data($html, $username)
|
||||
{
|
||||
// Look for preloaded data
|
||||
$patterns = array(
|
||||
'/window\.__additionalDataLoaded\s*\(\s*[\'"][^\'"]+[\'"]\s*,\s*({.+?})\s*\)\s*;/s',
|
||||
'/window\._sharedData\s*=\s*({.+?});/s',
|
||||
'/<script type="application\/json"[^>]*>(\{.+?\})<\/script>/s',
|
||||
);
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match_all($pattern, $html, $matches)) {
|
||||
foreach ($matches[1] as $json_str) {
|
||||
$data = json_decode($json_str, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to find edges in various paths
|
||||
$edges = $this->find_edges_in_data($data);
|
||||
|
||||
if (!empty($edges)) {
|
||||
return $this->parse_graphql_edges($edges, $username);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find edges in nested data structure
|
||||
*
|
||||
* @param array $data Data array
|
||||
* @return array
|
||||
*/
|
||||
private function find_edges_in_data($data)
|
||||
{
|
||||
if (!is_array($data)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Direct path check
|
||||
$paths = array(
|
||||
array('entry_data', 'ProfilePage', 0, 'graphql', 'user', 'edge_owner_to_timeline_media', 'edges'),
|
||||
array('graphql', 'user', 'edge_owner_to_timeline_media', 'edges'),
|
||||
array('data', 'user', 'edge_owner_to_timeline_media', 'edges'),
|
||||
array('user', 'edge_owner_to_timeline_media', 'edges'),
|
||||
array('edge_owner_to_timeline_media', 'edges'),
|
||||
array('require', 0, 3, 0, '__bbox', 'require', 0, 3, 1, '__bbox', 'result', 'data', 'xdt_api__v1__feed__user_timeline_graphql_connection', 'edges'),
|
||||
);
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$result = $data;
|
||||
foreach ($path as $key) {
|
||||
if (is_array($result) && isset($result[$key])) {
|
||||
$result = $result[$key];
|
||||
} else {
|
||||
$result = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($result) && is_array($result)) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Recursive search for edges key
|
||||
return $this->recursive_find_edges($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively search for edges in data
|
||||
*
|
||||
* @param array $data Data array
|
||||
* @param int $depth Current depth
|
||||
* @return array
|
||||
*/
|
||||
private function recursive_find_edges($data, $depth = 0)
|
||||
{
|
||||
if ($depth > 10 || !is_array($data)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if ($key === 'edges' && is_array($value) && !empty($value)) {
|
||||
// Validate it looks like media edges
|
||||
if (isset($value[0]['node']['display_url']) || isset($value[0]['node']['thumbnail_src'])) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$result = $this->recursive_find_edges($value, $depth + 1);
|
||||
if (!empty($result)) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse OG images from page as fallback
|
||||
*
|
||||
* @param string $html HTML content
|
||||
* @param string $username Username
|
||||
* @return array
|
||||
*/
|
||||
private function parse_og_images($html, $username)
|
||||
{
|
||||
$posts = array();
|
||||
|
||||
// Get OG image
|
||||
if (preg_match('/property="og:image"[^>]+content="([^"]+)"/', $html, $matches)) {
|
||||
$image_url = $matches[1];
|
||||
|
||||
// This is just the profile picture or first post, but it's something
|
||||
$posts[] = array(
|
||||
'instagram_id' => md5($username . '_og_' . time()),
|
||||
'shortcode' => '',
|
||||
'username' => $username,
|
||||
'image_url' => $image_url,
|
||||
'thumbnail_url' => $image_url,
|
||||
'post_url' => 'https://www.instagram.com/' . $username . '/',
|
||||
'caption' => '',
|
||||
'likes_count' => null,
|
||||
'comments_count' => null,
|
||||
'posted_at' => current_time('mysql'),
|
||||
'image_width' => 0,
|
||||
'image_height' => 0,
|
||||
'is_video' => false,
|
||||
);
|
||||
}
|
||||
|
||||
// Also try to find any image URLs that look like Instagram CDN
|
||||
$pattern = '/(https:\/\/[^"\']+?instagram[^"\']+?\.jpg[^"\']*)/i';
|
||||
if (preg_match_all($pattern, $html, $matches)) {
|
||||
$seen = array();
|
||||
foreach (array_slice(array_unique($matches[1]), 0, 12) as $idx => $img_url) {
|
||||
// Skip profile pictures and very small images
|
||||
if (strpos($img_url, '_a.jpg') !== false || strpos($img_url, '150x150') !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hash = md5($img_url);
|
||||
if (isset($seen[$hash])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$hash] = true;
|
||||
|
||||
$posts[] = array(
|
||||
'instagram_id' => md5($username . '_img_' . $idx),
|
||||
'shortcode' => '',
|
||||
'username' => $username,
|
||||
'image_url' => $img_url,
|
||||
'thumbnail_url' => $img_url,
|
||||
'post_url' => 'https://www.instagram.com/' . $username . '/',
|
||||
'caption' => '',
|
||||
'likes_count' => null,
|
||||
'comments_count' => null,
|
||||
'posted_at' => current_time('mysql'),
|
||||
'image_width' => 0,
|
||||
'image_height' => 0,
|
||||
'is_video' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse window._sharedData from HTML
|
||||
*
|
||||
* @param string $html HTML content
|
||||
* @param string $username Username
|
||||
* @return array
|
||||
*/
|
||||
private function parse_shared_data($html, $username)
|
||||
{
|
||||
$pattern = '/window\._sharedData\s*=\s*({.+?});/s';
|
||||
|
||||
if (!preg_match($pattern, $html, $matches)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$data = json_decode($matches[1], true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Navigate to media edges
|
||||
$edges = $data['entry_data']['ProfilePage'][0]['graphql']['user']['edge_owner_to_timeline_media']['edges'] ?? array();
|
||||
|
||||
if (empty($edges)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $this->parse_graphql_edges($edges, $username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse GraphQL edges into normalized post data
|
||||
*
|
||||
* @param array $edges GraphQL edges
|
||||
* @param string $username Username
|
||||
* @return array
|
||||
*/
|
||||
private function parse_graphql_edges($edges, $username)
|
||||
{
|
||||
$posts = array();
|
||||
$max_images = (int) get_option('igsp_max_images', 12);
|
||||
|
||||
foreach ($edges as $index => $edge) {
|
||||
if ($index >= $max_images) {
|
||||
break;
|
||||
}
|
||||
|
||||
$node = $edge['node'] ?? $edge;
|
||||
|
||||
if (empty($node)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$post = array(
|
||||
'instagram_id' => $node['id'] ?? $node['pk'] ?? md5(json_encode($node)),
|
||||
'shortcode' => $node['shortcode'] ?? $node['code'] ?? '',
|
||||
'username' => $username,
|
||||
'image_url' => $this->get_best_image_url($node),
|
||||
'thumbnail_url' => $node['thumbnail_src'] ?? $node['display_url'] ?? $node['image_versions2']['candidates'][0]['url'] ?? '',
|
||||
'post_url' => 'https://www.instagram.com/p/' . ($node['shortcode'] ?? $node['code'] ?? '') . '/',
|
||||
'caption' => $this->extract_caption($node),
|
||||
'likes_count' => $node['edge_liked_by']['count'] ?? $node['edge_media_preview_like']['count'] ?? $node['like_count'] ?? null,
|
||||
'comments_count' => $node['edge_media_to_comment']['count'] ?? $node['edge_media_preview_comment']['count'] ?? $node['comment_count'] ?? null,
|
||||
'posted_at' => isset($node['taken_at_timestamp']) ? date('Y-m-d H:i:s', $node['taken_at_timestamp']) : (isset($node['taken_at']) ? date('Y-m-d H:i:s', $node['taken_at']) : current_time('mysql')),
|
||||
'image_width' => $node['dimensions']['width'] ?? $node['original_width'] ?? 0,
|
||||
'image_height' => $node['dimensions']['height'] ?? $node['original_height'] ?? 0,
|
||||
'is_video' => $node['is_video'] ?? $node['media_type'] === 2 ?? false,
|
||||
);
|
||||
|
||||
// Skip videos if not supported
|
||||
if (!empty($post['is_video'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only add if we have an image
|
||||
if (!empty($post['image_url'])) {
|
||||
$posts[] = $post;
|
||||
}
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get best image URL based on quality setting
|
||||
*
|
||||
* @param array $node Node data
|
||||
* @return string
|
||||
*/
|
||||
private function get_best_image_url($node)
|
||||
{
|
||||
$quality = get_option('igsp_image_quality', 'high');
|
||||
|
||||
// Check for image_versions2 format (newer API)
|
||||
if (!empty($node['image_versions2']['candidates'])) {
|
||||
$candidates = $node['image_versions2']['candidates'];
|
||||
switch ($quality) {
|
||||
case 'thumbnail':
|
||||
return end($candidates)['url'] ?? $candidates[0]['url'];
|
||||
case 'medium':
|
||||
$mid = floor(count($candidates) / 2);
|
||||
return $candidates[$mid]['url'] ?? $candidates[0]['url'];
|
||||
case 'high':
|
||||
default:
|
||||
return $candidates[0]['url'];
|
||||
}
|
||||
}
|
||||
|
||||
switch ($quality) {
|
||||
case 'thumbnail':
|
||||
return $node['thumbnail_src'] ?? $node['thumbnail_resources'][0]['src'] ?? $node['display_url'] ?? '';
|
||||
|
||||
case 'medium':
|
||||
$resources = $node['thumbnail_resources'] ?? array();
|
||||
$mid_index = (int) floor(count($resources) / 2);
|
||||
return $resources[$mid_index]['src'] ?? $node['display_url'] ?? '';
|
||||
|
||||
case 'high':
|
||||
default:
|
||||
return $node['display_url'] ?? $node['thumbnail_src'] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract caption from node
|
||||
*
|
||||
* @param array $node Node data
|
||||
* @return string
|
||||
*/
|
||||
private function extract_caption($node)
|
||||
{
|
||||
$caption = '';
|
||||
|
||||
if (isset($node['edge_media_to_caption']['edges'][0]['node']['text'])) {
|
||||
$caption = $node['edge_media_to_caption']['edges'][0]['node']['text'];
|
||||
} elseif (isset($node['caption']['text'])) {
|
||||
$caption = $node['caption']['text'];
|
||||
} elseif (isset($node['caption']) && is_string($node['caption'])) {
|
||||
$caption = $node['caption'];
|
||||
}
|
||||
|
||||
return wp_kses_post($caption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make HTTP request
|
||||
*
|
||||
* @param string $url URL to request
|
||||
* @param array $args Additional arguments
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function make_request($url, $args = array())
|
||||
{
|
||||
$custom_ua = get_option('igsp_user_agent', '');
|
||||
$user_agent = !empty($custom_ua) ? $custom_ua : $this->get_random_user_agent();
|
||||
|
||||
$default_args = array(
|
||||
'timeout' => $this->timeout,
|
||||
'user-agent' => $user_agent,
|
||||
'headers' => array(
|
||||
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
||||
'Accept-Language' => 'en-US,en;q=0.9',
|
||||
'Accept-Encoding' => 'gzip, deflate, br',
|
||||
'Connection' => 'keep-alive',
|
||||
'Upgrade-Insecure-Requests' => '1',
|
||||
'Sec-Fetch-Dest' => 'document',
|
||||
'Sec-Fetch-Mode' => 'navigate',
|
||||
'Sec-Fetch-Site' => 'none',
|
||||
'Sec-Fetch-User' => '?1',
|
||||
'Sec-Ch-Ua' => '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
|
||||
'Sec-Ch-Ua-Mobile' => '?0',
|
||||
'Sec-Ch-Ua-Platform' => '"Windows"',
|
||||
'Cache-Control' => 'max-age=0',
|
||||
),
|
||||
'sslverify' => true,
|
||||
'cookies' => array(),
|
||||
);
|
||||
|
||||
// Merge custom headers
|
||||
if (isset($args['headers'])) {
|
||||
$args['headers'] = array_merge($default_args['headers'], $args['headers']);
|
||||
}
|
||||
|
||||
$args = wp_parse_args($args, $default_args);
|
||||
|
||||
// Add proxy if configured
|
||||
$proxy_host = get_option('igsp_proxy_host', '');
|
||||
$proxy_port = get_option('igsp_proxy_port', '');
|
||||
|
||||
if (!empty($proxy_host) && !empty($proxy_port)) {
|
||||
$args['proxy'] = $proxy_host . ':' . $proxy_port;
|
||||
}
|
||||
|
||||
// Retry logic
|
||||
$last_error = null;
|
||||
|
||||
for ($attempt = 1; $attempt <= $this->max_retries; $attempt++) {
|
||||
$response = wp_remote_get($url, $args);
|
||||
|
||||
if (!is_wp_error($response)) {
|
||||
$status_code = wp_remote_retrieve_response_code($response);
|
||||
|
||||
if ($status_code === 200) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($status_code === 429) {
|
||||
// Rate limited - wait longer
|
||||
$this->logger->warning(__('Rate limited by Instagram, waiting...', 'instagram-gallery-sync-pro'));
|
||||
sleep(30);
|
||||
} elseif ($status_code === 302 || $status_code === 301) {
|
||||
// Follow redirect
|
||||
$location = wp_remote_retrieve_header($response, 'location');
|
||||
if (!empty($location)) {
|
||||
$this->random_delay(1, 2);
|
||||
return $this->make_request($location, $args);
|
||||
}
|
||||
} elseif ($status_code >= 400) {
|
||||
$last_error = new WP_Error(
|
||||
'http_error',
|
||||
sprintf(__('HTTP Error: %d', 'instagram-gallery-sync-pro'), $status_code)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$last_error = $response;
|
||||
}
|
||||
|
||||
if ($attempt < $this->max_retries) {
|
||||
$this->random_delay($this->min_delay * $attempt, $this->max_delay * $attempt);
|
||||
}
|
||||
}
|
||||
|
||||
return $last_error ?? new WP_Error('request_failed', __('Request failed after retries.', 'instagram-gallery-sync-pro'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get random User-Agent
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_random_user_agent()
|
||||
{
|
||||
return $this->user_agents[array_rand($this->user_agents)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Random delay between requests
|
||||
*
|
||||
* @param int $min Minimum seconds
|
||||
* @param int $max Maximum seconds
|
||||
* @return void
|
||||
*/
|
||||
private function random_delay($min = null, $max = null)
|
||||
{
|
||||
$min = $min ?? $this->min_delay;
|
||||
$max = $max ?? $this->max_delay;
|
||||
|
||||
$delay = rand($min * 1000000, $max * 1000000); // Microseconds
|
||||
usleep($delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate fetched data
|
||||
*
|
||||
* @param array $data Post data
|
||||
* @return bool
|
||||
*/
|
||||
public function validate_post_data($data)
|
||||
{
|
||||
if (empty($data['instagram_id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($data['image_url'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
if (!filter_var($data['image_url'], FILTER_VALIDATE_URL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
532
includes/class-shortcode.php
Normal file
532
includes/class-shortcode.php
Normal file
@@ -0,0 +1,532 @@
|
||||
<?php
|
||||
/**
|
||||
* Shortcode Handler Class
|
||||
*
|
||||
* Handles the [instagram_gallery] shortcode rendering.
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class IGSP_Shortcode
|
||||
*/
|
||||
class IGSP_Shortcode
|
||||
{
|
||||
|
||||
/**
|
||||
* Default shortcode attributes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $defaults;
|
||||
|
||||
/**
|
||||
* Initialize shortcode
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
add_shortcode('instagram_gallery', array($this, 'render'));
|
||||
add_action('wp_enqueue_scripts', array($this, 'register_assets'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default attributes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_defaults()
|
||||
{
|
||||
if (!$this->defaults) {
|
||||
$this->defaults = array(
|
||||
'layout' => get_option('igsp_layout_type', 'grid'),
|
||||
'columns' => get_option('igsp_columns_desktop', 3),
|
||||
'spacing' => get_option('igsp_spacing', 10),
|
||||
'limit' => get_option('igsp_display_limit', 12),
|
||||
'order' => get_option('igsp_order', 'newest'),
|
||||
'lightbox' => get_option('igsp_lightbox', 'yes'),
|
||||
'captions' => get_option('igsp_show_caption', 'no'),
|
||||
'autoplay' => 'false',
|
||||
'class' => '',
|
||||
);
|
||||
}
|
||||
|
||||
return $this->defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register frontend assets
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_assets()
|
||||
{
|
||||
$prefix = get_option('igsp_css_prefix', 'igsp');
|
||||
|
||||
// Base styles
|
||||
wp_register_style(
|
||||
'igsp-gallery-base',
|
||||
IGSP_PLUGIN_URL . 'public/css/gallery-base.css',
|
||||
array(),
|
||||
IGSP_VERSION
|
||||
);
|
||||
|
||||
// Layout-specific styles
|
||||
wp_register_style(
|
||||
'igsp-gallery-masonry',
|
||||
IGSP_PLUGIN_URL . 'public/css/gallery-masonry.css',
|
||||
array('igsp-gallery-base'),
|
||||
IGSP_VERSION
|
||||
);
|
||||
|
||||
wp_register_style(
|
||||
'igsp-gallery-grid',
|
||||
IGSP_PLUGIN_URL . 'public/css/gallery-grid.css',
|
||||
array('igsp-gallery-base'),
|
||||
IGSP_VERSION
|
||||
);
|
||||
|
||||
wp_register_style(
|
||||
'igsp-gallery-slider',
|
||||
IGSP_PLUGIN_URL . 'public/css/gallery-slider.css',
|
||||
array('igsp-gallery-base'),
|
||||
IGSP_VERSION
|
||||
);
|
||||
|
||||
// Lightbox
|
||||
wp_register_style(
|
||||
'igsp-lightbox',
|
||||
IGSP_PLUGIN_URL . 'public/css/lightbox.css',
|
||||
array(),
|
||||
IGSP_VERSION
|
||||
);
|
||||
|
||||
// Scripts
|
||||
wp_register_script(
|
||||
'igsp-masonry',
|
||||
IGSP_PLUGIN_URL . 'public/js/masonry.pkgd.min.js',
|
||||
array(),
|
||||
'4.2.2',
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'igsp-imagesloaded',
|
||||
IGSP_PLUGIN_URL . 'public/js/imagesloaded.pkgd.min.js',
|
||||
array(),
|
||||
'5.0.0',
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'igsp-glightbox',
|
||||
IGSP_PLUGIN_URL . 'public/js/glightbox.min.js',
|
||||
array(),
|
||||
'3.2.0',
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'igsp-swiper',
|
||||
IGSP_PLUGIN_URL . 'public/js/swiper.min.js',
|
||||
array(),
|
||||
'11.0.5',
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'igsp-gallery-frontend',
|
||||
IGSP_PLUGIN_URL . 'public/js/gallery-frontend.js',
|
||||
array('jquery'),
|
||||
IGSP_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render shortcode
|
||||
*
|
||||
* @param array $atts Shortcode attributes
|
||||
* @return string
|
||||
*/
|
||||
public function render($atts = array())
|
||||
{
|
||||
$atts = shortcode_atts($this->get_defaults(), $atts, 'instagram_gallery');
|
||||
|
||||
// Sanitize attributes
|
||||
$atts = $this->sanitize_atts($atts);
|
||||
|
||||
// Check cache
|
||||
$cache_key = 'igsp_gallery_' . md5(serialize($atts));
|
||||
$cache_duration = (int) get_option('igsp_cache_duration', 3600);
|
||||
|
||||
$cached = get_transient($cache_key);
|
||||
if ($cached !== false && !is_admin()) {
|
||||
$this->enqueue_assets($atts);
|
||||
return $cached;
|
||||
}
|
||||
|
||||
// Get posts from database
|
||||
$database = new IGSP_Database();
|
||||
$posts = $database->get_posts(array(
|
||||
'limit' => $atts['limit'],
|
||||
'order' => $atts['order'],
|
||||
));
|
||||
|
||||
if (empty($posts)) {
|
||||
return $this->render_empty_state();
|
||||
}
|
||||
|
||||
// Enqueue assets
|
||||
$this->enqueue_assets($atts);
|
||||
|
||||
// Render gallery
|
||||
$output = $this->render_gallery($posts, $atts);
|
||||
|
||||
// Cache output
|
||||
set_transient($cache_key, $output, $cache_duration);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize attributes
|
||||
*
|
||||
* @param array $atts Attributes
|
||||
* @return array
|
||||
*/
|
||||
private function sanitize_atts($atts)
|
||||
{
|
||||
$atts['layout'] = sanitize_key($atts['layout']);
|
||||
$atts['columns'] = absint($atts['columns']);
|
||||
$atts['spacing'] = absint($atts['spacing']);
|
||||
$atts['limit'] = absint($atts['limit']);
|
||||
$atts['order'] = sanitize_key($atts['order']);
|
||||
$atts['lightbox'] = in_array($atts['lightbox'], array('yes', 'true', '1'), true) ? true : false;
|
||||
$atts['captions'] = in_array($atts['captions'], array('yes', 'true', '1'), true) ? true : false;
|
||||
$atts['autoplay'] = in_array($atts['autoplay'], array('yes', 'true', '1'), true) ? true : false;
|
||||
$atts['class'] = sanitize_html_class($atts['class']);
|
||||
|
||||
// Clamp values
|
||||
$atts['columns'] = max(1, min(6, $atts['columns']));
|
||||
$atts['spacing'] = max(0, min(50, $atts['spacing']));
|
||||
$atts['limit'] = max(1, min(100, $atts['limit']));
|
||||
|
||||
return $atts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue required assets
|
||||
*
|
||||
* @param array $atts Attributes
|
||||
* @return void
|
||||
*/
|
||||
private function enqueue_assets($atts)
|
||||
{
|
||||
// Base CSS
|
||||
wp_enqueue_style('igsp-gallery-base');
|
||||
|
||||
// Layout-specific CSS
|
||||
switch ($atts['layout']) {
|
||||
case 'masonry':
|
||||
wp_enqueue_style('igsp-gallery-masonry');
|
||||
wp_enqueue_script('igsp-masonry');
|
||||
wp_enqueue_script('igsp-imagesloaded');
|
||||
break;
|
||||
case 'slider':
|
||||
wp_enqueue_style('igsp-gallery-slider');
|
||||
wp_enqueue_script('igsp-swiper');
|
||||
break;
|
||||
default:
|
||||
wp_enqueue_style('igsp-gallery-grid');
|
||||
}
|
||||
|
||||
// Lightbox
|
||||
if ($atts['lightbox']) {
|
||||
wp_enqueue_style('igsp-lightbox');
|
||||
wp_enqueue_script('igsp-glightbox');
|
||||
}
|
||||
|
||||
// Frontend JS
|
||||
wp_enqueue_script('igsp-gallery-frontend');
|
||||
|
||||
// Pass settings to JS
|
||||
wp_localize_script('igsp-gallery-frontend', 'igspFrontend', array(
|
||||
'lightbox' => $atts['lightbox'],
|
||||
'layout' => $atts['layout'],
|
||||
'autoplay' => $atts['autoplay'],
|
||||
));
|
||||
|
||||
// Add inline custom CSS
|
||||
$this->add_custom_styles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom inline styles
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function add_custom_styles()
|
||||
{
|
||||
$primary = get_option('igsp_primary_color', '#e1306c');
|
||||
$hover = get_option('igsp_hover_color', '#c13584');
|
||||
$text = get_option('igsp_text_color', '#ffffff');
|
||||
$font_size = get_option('igsp_caption_font_size', 14);
|
||||
$border_radius = get_option('igsp_border_radius', 0);
|
||||
$custom_css = get_option('igsp_custom_css', '');
|
||||
|
||||
$css = "
|
||||
:root {
|
||||
--igsp-primary: {$primary};
|
||||
--igsp-hover: {$hover};
|
||||
--igsp-text: {$text};
|
||||
--igsp-font-size: {$font_size}px;
|
||||
--igsp-radius: {$border_radius}px;
|
||||
}
|
||||
";
|
||||
|
||||
if (!empty($custom_css)) {
|
||||
$css .= "\n" . $custom_css;
|
||||
}
|
||||
|
||||
wp_add_inline_style('igsp-gallery-base', $css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render gallery HTML
|
||||
*
|
||||
* @param array $posts Posts
|
||||
* @param array $atts Attributes
|
||||
* @return string
|
||||
*/
|
||||
private function render_gallery($posts, $atts)
|
||||
{
|
||||
$prefix = get_option('igsp_css_prefix', 'igsp');
|
||||
$hover_effect = get_option('igsp_hover_effect', 'zoom');
|
||||
$aspect_ratio = get_option('igsp_aspect_ratio', 'square');
|
||||
$object_fit = get_option('igsp_object_fit', 'cover');
|
||||
$caption_position = get_option('igsp_caption_position', 'overlay');
|
||||
$link_behavior = get_option('igsp_link_behavior', 'new_tab');
|
||||
$lazy_loading = get_option('igsp_lazy_loading', 'yes') === 'yes';
|
||||
$columns_tablet = get_option('igsp_columns_tablet', 2);
|
||||
$columns_mobile = get_option('igsp_columns_mobile', 1);
|
||||
|
||||
$wrapper_class = array(
|
||||
$prefix . '-gallery',
|
||||
$prefix . '-' . $atts['layout'],
|
||||
$prefix . '-hover-' . $hover_effect,
|
||||
$prefix . '-aspect-' . $aspect_ratio,
|
||||
);
|
||||
|
||||
if (!empty($atts['class'])) {
|
||||
$wrapper_class[] = $atts['class'];
|
||||
}
|
||||
|
||||
// Build gallery container
|
||||
ob_start();
|
||||
?>
|
||||
<div class="<?php echo esc_attr(implode(' ', $wrapper_class)); ?>"
|
||||
data-columns="<?php echo esc_attr($atts['columns']); ?>"
|
||||
data-columns-tablet="<?php echo esc_attr($columns_tablet); ?>"
|
||||
data-columns-mobile="<?php echo esc_attr($columns_mobile); ?>"
|
||||
data-spacing="<?php echo esc_attr($atts['spacing']); ?>" data-layout="<?php echo esc_attr($atts['layout']); ?>"
|
||||
style="--columns: <?php echo esc_attr($atts['columns']); ?>; --spacing: <?php echo esc_attr($atts['spacing']); ?>px;">
|
||||
|
||||
<?php if ($atts['layout'] === 'slider'): ?>
|
||||
<div class="swiper">
|
||||
<div class="swiper-wrapper">
|
||||
<?php endif; ?>
|
||||
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<?php echo $this->render_item($post, $atts, $lazy_loading, $caption_position, $link_behavior); ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if ($atts['layout'] === 'slider'): ?>
|
||||
</div>
|
||||
<div class="swiper-pagination"></div>
|
||||
<div class="swiper-button-prev"></div>
|
||||
<div class="swiper-button-next"></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<?php if (get_option('igsp_show_instagram_btn', 'yes') === 'yes'): ?>
|
||||
<?php echo $this->render_instagram_button(); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render single gallery item
|
||||
*
|
||||
* @param object $post Post object
|
||||
* @param array $atts Attributes
|
||||
* @param bool $lazy_loading Use lazy loading
|
||||
* @param string $caption_position Caption position
|
||||
* @param string $link_behavior Link behavior
|
||||
* @return string
|
||||
*/
|
||||
private function render_item($post, $atts, $lazy_loading, $caption_position, $link_behavior)
|
||||
{
|
||||
$prefix = get_option('igsp_css_prefix', 'igsp');
|
||||
$image_handler = new IGSP_Image_Handler();
|
||||
|
||||
// Get image URL
|
||||
$image_url = '';
|
||||
if (!empty($post->image_local_path)) {
|
||||
$image_url = $image_handler->get_image_url($post->image_local_path);
|
||||
}
|
||||
|
||||
$thumb_url = '';
|
||||
if (!empty($post->image_thumbnail_path)) {
|
||||
$thumb_url = $image_handler->get_image_url($post->image_thumbnail_path);
|
||||
}
|
||||
|
||||
// Fallback to main image if no thumbnail
|
||||
$display_url = !empty($thumb_url) ? $thumb_url : $image_url;
|
||||
$full_url = $image_url;
|
||||
|
||||
if (empty($display_url)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Determine link
|
||||
$link_url = '';
|
||||
$link_target = '';
|
||||
$lightbox_attr = '';
|
||||
|
||||
if ($link_behavior === 'lightbox' && $atts['lightbox']) {
|
||||
$link_url = $full_url;
|
||||
$lightbox_attr = 'data-gallery="igsp-lightbox"';
|
||||
} elseif ($link_behavior === 'new_tab') {
|
||||
$link_url = $post->post_url;
|
||||
$link_target = 'target="_blank" rel="noopener noreferrer"';
|
||||
} elseif ($link_behavior === 'same_window') {
|
||||
$link_url = $post->post_url;
|
||||
}
|
||||
|
||||
// Caption
|
||||
$caption = '';
|
||||
if ($atts['captions'] && !empty($post->caption)) {
|
||||
$caption = wp_trim_words($post->caption, 20);
|
||||
}
|
||||
|
||||
// Build class based on layout
|
||||
$item_class = $prefix . '-item';
|
||||
if ($atts['layout'] === 'slider') {
|
||||
$item_class = 'swiper-slide ' . $item_class;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="<?php echo esc_attr($item_class); ?>">
|
||||
<?php if (!empty($link_url)): ?>
|
||||
<a href="<?php echo esc_url($link_url); ?>" class="<?php echo esc_attr($prefix . '-link'); ?>" <?php echo $link_target; ?>
|
||||
<?php echo $lightbox_attr; ?>
|
||||
title="
|
||||
<?php echo esc_attr($caption); ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="<?php echo esc_attr($prefix . '-image-wrapper'); ?>">
|
||||
<img src="<?php echo $lazy_loading ? '' : esc_url($display_url); ?>" <?php echo $lazy_loading ? 'data-src="' . esc_url($display_url) . '"' : ''; ?>
|
||||
class="
|
||||
<?php echo esc_attr($prefix . '-image'); ?>
|
||||
<?php echo $lazy_loading ? 'lazyload' : ''; ?>"
|
||||
alt="
|
||||
<?php echo esc_attr($caption); ?>"
|
||||
<?php echo $lazy_loading ? 'loading="lazy"' : ''; ?>>
|
||||
|
||||
<?php if ($caption_position === 'overlay' && $atts['captions'] && !empty($caption)): ?>
|
||||
<div class="<?php echo esc_attr($prefix . '-overlay'); ?>">
|
||||
<span class="<?php echo esc_attr($prefix . '-caption'); ?>">
|
||||
<?php echo esc_html($caption); ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="<?php echo esc_attr($prefix . '-hover-overlay'); ?>">
|
||||
<span class="<?php echo esc_attr($prefix . '-icon'); ?>">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm0 7.082c1.602 0 1.792.006 2.425.035.598.028.922.127 1.138.211.286.111.491.244.705.458s.347.419.458.705c.084.216.183.54.211 1.138.029.633.035.823.035 2.425s-.006 1.792-.035 2.425c-.028.598-.127.922-.211 1.138-.111.286-.244.491-.458.705s-.419.347-.705.458c-.216.084-.54.183-1.138.211-.633.029-.823.035-2.425.035s-1.792-.006-2.425-.035c-.598-.028-.922-.127-1.138-.211-.286-.111-.491-.244-.705-.458s-.347-.419-.458-.705c-.084-.216-.183-.54-.211-1.138-.029-.633-.035-.823-.035-2.425s.006-1.792.035-2.425c.028-.598.127-.922.211-1.138.111-.286.244-.491.458-.705s.419-.347.705-.458c.216-.084.54-.183 1.138-.211.633-.029.823-.035 2.425-.035zm0-1.082c-1.63 0-1.833.007-2.474.036-.639.029-1.076.131-1.459.28-.396.154-.731.359-1.066.693s-.539.67-.693 1.066c-.149.383-.251.82-.28 1.459-.029.641-.036.844-.036 2.474s.007 1.833.036 2.474c.029.639.131 1.076.28 1.459.154.396.359.731.693 1.066s.67.539 1.066.693c.383.149.82.251 1.459.28.641.029.844.036 2.474.036s1.833-.007 2.474-.036c.639-.029 1.076-.131 1.459-.28.396-.154.731-.359 1.066-.693s.539-.67.693-1.066c.149-.383.251-.82.28-1.459.029-.641.036-.844.036-2.474s-.007-1.833-.036-2.474c-.029-.639-.131-1.076-.28-1.459-.154-.396-.359-.731-.693-1.066s-.67-.539-1.066-.693c-.383-.149-.82-.251-1.459-.28-.641-.029-.844-.036-2.474-.036zm0 2.919c-1.701 0-3.081 1.379-3.081 3.081s1.379 3.081 3.081 3.081 3.081-1.379 3.081-3.081-1.379-3.081-3.081-3.081zm0 5.081c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2zm3.202-5.922c-.398 0-.72.322-.72.72s.322.72.72.72.72-.322.72-.72-.322-.72-.72-.72z" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($link_url)): ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($caption_position === 'below' && $atts['captions'] && !empty($caption)): ?>
|
||||
<div class="<?php echo esc_attr($prefix . '-caption-below'); ?>">
|
||||
<?php echo esc_html($caption); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Instagram button
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function render_instagram_button()
|
||||
{
|
||||
$prefix = get_option('igsp_css_prefix', 'igsp');
|
||||
$username = get_option('igsp_username', '');
|
||||
|
||||
if (empty($username)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="<?php echo esc_attr($prefix . '-follow-btn-wrapper'); ?>">
|
||||
<a href="https://www.instagram.com/<?php echo esc_attr($username); ?>/" target="_blank" rel="noopener noreferrer"
|
||||
class="<?php echo esc_attr($prefix . '-follow-btn'); ?>">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
|
||||
<path
|
||||
d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" />
|
||||
</svg>
|
||||
<?php esc_html_e('Follow on Instagram', 'instagram-gallery-sync-pro'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render empty state
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function render_empty_state()
|
||||
{
|
||||
$prefix = get_option('igsp_css_prefix', 'igsp');
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="<?php echo esc_attr($prefix . '-empty'); ?>">
|
||||
<p>
|
||||
<?php esc_html_e('No Instagram posts found. Please sync your Instagram account in the admin settings.', 'instagram-gallery-sync-pro'); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
1
includes/index.php
Normal file
1
includes/index.php
Normal file
@@ -0,0 +1 @@
|
||||
<?php // Silence is golden
|
||||
369
instagram-gallery-sync-pro.php
Normal file
369
instagram-gallery-sync-pro.php
Normal file
@@ -0,0 +1,369 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Instagram Gallery Sync Pro
|
||||
* Plugin URI: https://example.com/instagram-gallery-sync-pro
|
||||
* Description: Synchronize and display Instagram photos in beautiful galleries without the official API.
|
||||
* Version: 1.0.0
|
||||
* Author: Your Name
|
||||
* Author URI: https://example.com
|
||||
* License: GPL-2.0+
|
||||
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
||||
* Text Domain: instagram-gallery-sync-pro
|
||||
* Domain Path: /languages
|
||||
* Requires at least: 5.8
|
||||
* Requires PHP: 7.4
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin Constants
|
||||
*/
|
||||
define( 'IGSP_VERSION', '1.0.0' );
|
||||
define( 'IGSP_PLUGIN_FILE', __FILE__ );
|
||||
define( 'IGSP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'IGSP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||
define( 'IGSP_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
|
||||
define( 'IGSP_UPLOAD_DIR', 'instagram-gallery' );
|
||||
|
||||
/**
|
||||
* Database Table Names
|
||||
*/
|
||||
global $wpdb;
|
||||
define( 'IGSP_TABLE_POSTS', $wpdb->prefix . 'instagram_gallery_posts' );
|
||||
define( 'IGSP_TABLE_LOG', $wpdb->prefix . 'instagram_gallery_log' );
|
||||
|
||||
/**
|
||||
* Autoloader for plugin classes
|
||||
*/
|
||||
spl_autoload_register( function( $class ) {
|
||||
// Check if the class uses our namespace prefix
|
||||
$prefix = 'IGSP_';
|
||||
|
||||
if ( strpos( $class, $prefix ) !== 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert class name to file name
|
||||
$class_name = str_replace( $prefix, '', $class );
|
||||
$class_name = strtolower( str_replace( '_', '-', $class_name ) );
|
||||
$file = IGSP_PLUGIN_DIR . 'includes/class-' . $class_name . '.php';
|
||||
|
||||
if ( file_exists( $file ) ) {
|
||||
require_once $file;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Main Plugin Class
|
||||
*/
|
||||
final class Instagram_Gallery_Sync_Pro {
|
||||
|
||||
/**
|
||||
* Plugin instance
|
||||
*
|
||||
* @var Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Plugin components
|
||||
*/
|
||||
public $database;
|
||||
public $logger;
|
||||
public $scraper;
|
||||
public $image_handler;
|
||||
public $cron;
|
||||
public $admin;
|
||||
public $shortcode;
|
||||
public $gutenberg;
|
||||
|
||||
/**
|
||||
* Get plugin instance
|
||||
*
|
||||
* @return Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
private function __construct() {
|
||||
$this->init_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize hooks
|
||||
*/
|
||||
private function init_hooks() {
|
||||
// Activation and deactivation hooks
|
||||
register_activation_hook( IGSP_PLUGIN_FILE, array( $this, 'activate' ) );
|
||||
register_deactivation_hook( IGSP_PLUGIN_FILE, array( $this, 'deactivate' ) );
|
||||
|
||||
// Initialize plugin after WordPress is loaded
|
||||
add_action( 'plugins_loaded', array( $this, 'init_plugin' ) );
|
||||
|
||||
// Load text domain
|
||||
add_action( 'init', array( $this, 'load_textdomain' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin activation
|
||||
*/
|
||||
public function activate() {
|
||||
// Ensure components are loaded
|
||||
$this->load_components();
|
||||
|
||||
// Create database tables
|
||||
$this->database->create_tables();
|
||||
|
||||
// Create upload directory
|
||||
$this->create_upload_directory();
|
||||
|
||||
// Set default options
|
||||
$this->set_default_options();
|
||||
|
||||
// Schedule cron if enabled
|
||||
$this->cron->schedule_sync();
|
||||
|
||||
// Log activation
|
||||
$this->logger->log( 'info', __( 'Plugin activated successfully.', 'instagram-gallery-sync-pro' ) );
|
||||
|
||||
// Flush rewrite rules
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin deactivation
|
||||
*/
|
||||
public function deactivate() {
|
||||
// Clear scheduled cron
|
||||
$this->cron->unschedule_sync();
|
||||
|
||||
// Clear transients
|
||||
$this->clear_transients();
|
||||
|
||||
// Log deactivation
|
||||
$this->logger->log( 'info', __( 'Plugin deactivated.', 'instagram-gallery-sync-pro' ) );
|
||||
|
||||
// Flush rewrite rules
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize plugin components
|
||||
*/
|
||||
public function init_plugin() {
|
||||
$this->load_components();
|
||||
$this->init_components();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plugin components
|
||||
*/
|
||||
private function load_components() {
|
||||
// Core components
|
||||
$this->database = new IGSP_Database();
|
||||
$this->logger = new IGSP_Logger();
|
||||
$this->image_handler = new IGSP_Image_Handler();
|
||||
$this->scraper = new IGSP_Scraper();
|
||||
$this->cron = new IGSP_Cron();
|
||||
|
||||
// Frontend components
|
||||
$this->shortcode = new IGSP_Shortcode();
|
||||
$this->gutenberg = new IGSP_Gutenberg_Block();
|
||||
|
||||
// Admin components (only in admin)
|
||||
if ( is_admin() ) {
|
||||
$this->admin = new IGSP_Admin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize components
|
||||
*/
|
||||
private function init_components() {
|
||||
$this->shortcode->init();
|
||||
$this->gutenberg->init();
|
||||
$this->cron->init();
|
||||
|
||||
if ( is_admin() && $this->admin ) {
|
||||
$this->admin->init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plugin text domain
|
||||
*/
|
||||
public function load_textdomain() {
|
||||
load_plugin_textdomain(
|
||||
'instagram-gallery-sync-pro',
|
||||
false,
|
||||
dirname( IGSP_PLUGIN_BASENAME ) . '/languages'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create upload directory
|
||||
*/
|
||||
private function create_upload_directory() {
|
||||
$upload_dir = wp_upload_dir();
|
||||
$instagram_dir = $upload_dir['basedir'] . '/' . IGSP_UPLOAD_DIR;
|
||||
|
||||
if ( ! file_exists( $instagram_dir ) ) {
|
||||
wp_mkdir_p( $instagram_dir );
|
||||
|
||||
// Create index.php for security
|
||||
file_put_contents( $instagram_dir . '/index.php', '<?php // Silence is golden' );
|
||||
|
||||
// Create .htaccess for additional security
|
||||
$htaccess = "Options -Indexes\n";
|
||||
$htaccess .= "<Files *.php>\n";
|
||||
$htaccess .= "deny from all\n";
|
||||
$htaccess .= "</Files>\n";
|
||||
file_put_contents( $instagram_dir . '/.htaccess', $htaccess );
|
||||
}
|
||||
|
||||
// Create thumbnails subfolder
|
||||
$thumb_dir = $instagram_dir . '/thumbnails';
|
||||
if ( ! file_exists( $thumb_dir ) ) {
|
||||
wp_mkdir_p( $thumb_dir );
|
||||
file_put_contents( $thumb_dir . '/index.php', '<?php // Silence is golden' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default options
|
||||
*/
|
||||
private function set_default_options() {
|
||||
$defaults = array(
|
||||
// Instagram settings
|
||||
'igsp_username' => '',
|
||||
'igsp_max_images' => 12,
|
||||
'igsp_save_locally' => 'yes',
|
||||
'igsp_image_quality' => 'high',
|
||||
'igsp_sync_interval' => 'daily',
|
||||
'igsp_last_sync' => '',
|
||||
|
||||
// Layout settings
|
||||
'igsp_layout_type' => 'grid',
|
||||
'igsp_columns_desktop' => 3,
|
||||
'igsp_columns_tablet' => 2,
|
||||
'igsp_columns_mobile' => 1,
|
||||
'igsp_spacing' => 10,
|
||||
'igsp_padding' => 0,
|
||||
'igsp_aspect_ratio' => 'square',
|
||||
'igsp_object_fit' => 'cover',
|
||||
'igsp_border_radius' => 0,
|
||||
'igsp_hover_effect' => 'zoom',
|
||||
|
||||
// Display settings
|
||||
'igsp_display_limit' => 12,
|
||||
'igsp_order' => 'newest',
|
||||
'igsp_show_caption' => 'no',
|
||||
'igsp_caption_position' => 'overlay',
|
||||
'igsp_show_instagram_btn' => 'yes',
|
||||
'igsp_link_behavior' => 'new_tab',
|
||||
'igsp_lightbox' => 'yes',
|
||||
'igsp_lazy_loading' => 'yes',
|
||||
'igsp_loader_type' => 'spinner',
|
||||
|
||||
// Styling
|
||||
'igsp_primary_color' => '#e1306c',
|
||||
'igsp_hover_color' => '#c13584',
|
||||
'igsp_text_color' => '#ffffff',
|
||||
'igsp_caption_font_size' => 14,
|
||||
'igsp_custom_css' => '',
|
||||
'igsp_css_prefix' => 'igsp',
|
||||
|
||||
// Advanced
|
||||
'igsp_debug_mode' => 'no',
|
||||
'igsp_cache_duration' => 3600,
|
||||
'igsp_request_timeout' => 30,
|
||||
'igsp_user_agent' => '',
|
||||
'igsp_proxy_host' => '',
|
||||
'igsp_proxy_port' => '',
|
||||
'igsp_auto_delete_days' => 0,
|
||||
);
|
||||
|
||||
foreach ( $defaults as $key => $value ) {
|
||||
if ( get_option( $key ) === false ) {
|
||||
add_option( $key, $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all transients
|
||||
*/
|
||||
public function clear_transients() {
|
||||
global $wpdb;
|
||||
|
||||
$wpdb->query(
|
||||
"DELETE FROM {$wpdb->options}
|
||||
WHERE option_name LIKE '_transient_igsp_%'
|
||||
OR option_name LIKE '_transient_timeout_igsp_%'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upload directory path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_upload_path() {
|
||||
$upload_dir = wp_upload_dir();
|
||||
return $upload_dir['basedir'] . '/' . IGSP_UPLOAD_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upload directory URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_upload_url() {
|
||||
$upload_dir = wp_upload_dir();
|
||||
return $upload_dir['baseurl'] . '/' . IGSP_UPLOAD_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin settings
|
||||
*
|
||||
* @param string $key Option key
|
||||
* @param mixed $default Default value
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_setting( $key, $default = '' ) {
|
||||
return get_option( 'igsp_' . $key, $default );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main plugin instance
|
||||
*
|
||||
* @return Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
function igsp() {
|
||||
return Instagram_Gallery_Sync_Pro::get_instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Template function for displaying the gallery
|
||||
*
|
||||
* @param array $args Gallery arguments
|
||||
* @return void
|
||||
*/
|
||||
function display_instagram_gallery( $args = array() ) {
|
||||
echo igsp()->shortcode->render( $args );
|
||||
}
|
||||
|
||||
// Initialize the plugin
|
||||
igsp();
|
||||
585
languages/instagram-gallery-sync-pro.pot
Normal file
585
languages/instagram-gallery-sync-pro.pot
Normal file
@@ -0,0 +1,585 @@
|
||||
# Copyright (C) 2024 Instagram Gallery Sync Pro
|
||||
# This file is distributed under the GPL-2.0+.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Instagram Gallery Sync Pro 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://example.com\n"
|
||||
"POT-Creation-Date: 2024-01-01 00:00+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2024-01-01 00:00+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
||||
#: includes/class-admin.php
|
||||
msgid "Instagram Gallery"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-admin.php
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/settings-page.php
|
||||
msgid "Instagram Gallery Sync Pro"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/settings-page.php
|
||||
msgid "Version %s"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/settings-page.php
|
||||
msgid "Sync Status"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/settings-page.php
|
||||
msgid "Total Posts:"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/settings-page.php
|
||||
msgid "Last Sync:"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/settings-page.php
|
||||
msgid "Next Sync:"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/settings-page.php
|
||||
msgid "Shortcode"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/settings-page.php
|
||||
msgid "Use this shortcode to display the gallery on any page or post."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/settings-page.php
|
||||
msgid "Need Help?"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/settings-page.php
|
||||
msgid "Documentation"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/settings-page.php
|
||||
msgid "Support"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Account Settings"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Instagram Username"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Enter the Instagram username without the @ symbol. The profile must be public."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Maximum Images to Fetch"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "All available"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Number of images to fetch from Instagram."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Save Images Locally"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Yes - Download and store images on your server (recommended)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "No - Link directly to Instagram (may cause loading issues)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Image Quality"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Thumbnail (150px)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Medium (320px)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "High (640px+)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Synchronization"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Sync Interval"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "How often to check for new posts."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Manual Sync"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Sync Now"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-instagram.php
|
||||
msgid "Syncing..."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Layout Type"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Grid"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Masonry"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Justified"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Slider"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "List"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Grid Settings"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Columns (Desktop)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Columns (Tablet)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Columns (Mobile)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Spacing"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Outer Padding"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Image Settings"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Aspect Ratio"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Original"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Square (1:1)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Object Fit"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Cover (Crop to fill)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Contain (Fit inside)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Border Radius"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Hover Effect"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Zoom"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Fade + Caption"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Overlay with Icon"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Grayscale to Color"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-layout.php
|
||||
msgid "Lift (Shadow + Transform)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Display Options"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Number of Images to Display"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Display Order"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Newest First"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Oldest First"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Random"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Show Captions"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Caption Position"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Overlay on Image"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Below Image"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Show \"More on Instagram\" Button"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Interactivity"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Link Behavior"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Open in New Tab"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Open in Same Window"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Open in Lightbox"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "No Link"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Enable Lightbox"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Allow clicking images to view them in a lightbox."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Performance"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Lazy Loading"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Yes (Recommended)"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Loading Animation"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Spinner"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Dots"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Pulse"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-display.php
|
||||
msgid "Skeleton"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Colors"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Primary Color"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Used for buttons and highlights."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Hover Color"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Used for overlay and hover effects."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Text Color"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Used for caption and overlay text."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Typography"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Caption Font Size"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Advanced Styling"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "CSS Class Prefix"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Custom CSS"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Add custom CSS to override default styles. Use cautiously."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Live Preview"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Sample Caption"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-styling.php
|
||||
msgid "Sample Button"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Debug & Development"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Debug Mode"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Enable to log all sync activities. Disable in production for better performance."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Cache Duration"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Request Timeout"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Auto-Delete Old Posts"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Never"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Request Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Custom User-Agent"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Proxy Settings"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Tools"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Clear Cache"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Clear all cached gallery data."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Storage Used"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Total: %s"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Reset All Data"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Delete ALL Instagram data, images, and logs."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Reset Everything"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Activity Logs"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Clear Logs"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "No log entries yet. Enable debug mode to see activity logs."
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Message"
|
||||
msgstr ""
|
||||
|
||||
#: admin/views/tab-advanced.php
|
||||
msgid "Date"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-shortcode.php
|
||||
msgid "No Instagram posts found. Please sync your Instagram account in the admin settings."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-shortcode.php
|
||||
msgid "Follow on Instagram"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-cron.php
|
||||
msgid "Every 30 Minutes"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-cron.php
|
||||
msgid "Hourly"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-cron.php
|
||||
msgid "Every 6 Hours"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-cron.php
|
||||
msgid "Daily"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-cron.php
|
||||
msgid "Weekly"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-logger.php
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-logger.php
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-logger.php
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-logger.php
|
||||
msgid "Info"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-logger.php
|
||||
msgid "ago"
|
||||
msgstr ""
|
||||
BIN
public/.DS_Store
vendored
Normal file
BIN
public/.DS_Store
vendored
Normal file
Binary file not shown.
44
public/css/block-editor.css
Normal file
44
public/css/block-editor.css
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Instagram Gallery Sync Pro - Block Editor Styles
|
||||
*
|
||||
* Styles for the Gutenberg block in the editor
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
/* Block placeholder */
|
||||
.wp-block-igsp-instagram-gallery {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.igsp-block-preview {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
/* Editor-specific overrides */
|
||||
.editor-styles-wrapper .igsp-gallery {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Ensure hover effects don't interfere with editing */
|
||||
.editor-styles-wrapper .igsp-item:hover .igsp-hover-overlay {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* Block preview loading state */
|
||||
.components-server-side-render__loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 200px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Block empty state in editor */
|
||||
.editor-styles-wrapper .igsp-empty {
|
||||
background: #f0f0f0;
|
||||
border: 2px dashed #ccc;
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
408
public/css/gallery-base.css
Normal file
408
public/css/gallery-base.css
Normal file
@@ -0,0 +1,408 @@
|
||||
/**
|
||||
* Instagram Gallery Sync Pro - Base Styles
|
||||
*
|
||||
* Core styles for all gallery layouts
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
/* ============================================
|
||||
CSS Variables (customizable via admin)
|
||||
============================================ */
|
||||
:root {
|
||||
--igsp-primary: #e1306c;
|
||||
--igsp-hover: #c13584;
|
||||
--igsp-text: #ffffff;
|
||||
--igsp-font-size: 14px;
|
||||
--igsp-radius: 0px;
|
||||
--columns: 3;
|
||||
--spacing: 10px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Gallery Container
|
||||
============================================ */
|
||||
.igsp-gallery {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.igsp-gallery *,
|
||||
.igsp-gallery *::before,
|
||||
.igsp-gallery *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Gallery Item
|
||||
============================================ */
|
||||
.igsp-item {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #f0f0f0;
|
||||
border-radius: var(--igsp-radius);
|
||||
}
|
||||
|
||||
.igsp-link {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.igsp-image-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.igsp-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.4s ease, filter 0.4s ease;
|
||||
}
|
||||
|
||||
/* Aspect Ratios */
|
||||
.igsp-aspect-square .igsp-image-wrapper {
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
|
||||
.igsp-aspect-4-3 .igsp-image-wrapper {
|
||||
padding-bottom: 75%;
|
||||
}
|
||||
|
||||
.igsp-aspect-16-9 .igsp-image-wrapper {
|
||||
padding-bottom: 56.25%;
|
||||
}
|
||||
|
||||
.igsp-aspect-3-4 .igsp-image-wrapper {
|
||||
padding-bottom: 133.33%;
|
||||
}
|
||||
|
||||
.igsp-aspect-square .igsp-image,
|
||||
.igsp-aspect-4-3 .igsp-image,
|
||||
.igsp-aspect-16-9 .igsp-image,
|
||||
.igsp-aspect-3-4 .igsp-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Overlays
|
||||
============================================ */
|
||||
.igsp-overlay,
|
||||
.igsp-hover-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.igsp-overlay {
|
||||
background: linear-gradient(transparent 50%, rgba(0, 0, 0, 0.7));
|
||||
align-items: flex-end;
|
||||
padding: 15px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.igsp-hover-overlay {
|
||||
background: var(--igsp-hover);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.igsp-item:hover .igsp-hover-overlay {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.igsp-caption {
|
||||
color: var(--igsp-text);
|
||||
font-size: var(--igsp-font-size);
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.igsp-caption-below {
|
||||
padding: 12px;
|
||||
font-size: var(--igsp-font-size);
|
||||
color: #333;
|
||||
background: #fff;
|
||||
border: 1px solid #eee;
|
||||
border-top: none;
|
||||
border-radius: 0 0 var(--igsp-radius) var(--igsp-radius);
|
||||
}
|
||||
|
||||
.igsp-icon {
|
||||
color: var(--igsp-text);
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.igsp-icon svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.igsp-item:hover .igsp-icon {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Hover Effects
|
||||
============================================ */
|
||||
|
||||
/* Zoom */
|
||||
.igsp-hover-zoom .igsp-item:hover .igsp-image {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Fade */
|
||||
.igsp-hover-fade .igsp-overlay {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.igsp-hover-fade .igsp-item:hover .igsp-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Overlay with Icon */
|
||||
.igsp-hover-overlay .igsp-hover-overlay {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Grayscale */
|
||||
.igsp-hover-grayscale .igsp-image {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.igsp-hover-grayscale .igsp-item:hover .igsp-image {
|
||||
filter: grayscale(0%);
|
||||
}
|
||||
|
||||
/* Lift */
|
||||
.igsp-hover-lift .igsp-item {
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.igsp-hover-lift .igsp-item:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Follow Button
|
||||
============================================ */
|
||||
.igsp-follow-btn-wrapper {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.igsp-follow-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 28px;
|
||||
background: var(--igsp-primary);
|
||||
color: var(--igsp-text);
|
||||
text-decoration: none;
|
||||
border-radius: 25px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.igsp-follow-btn:hover {
|
||||
background: var(--igsp-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 20px rgba(225, 48, 108, 0.3);
|
||||
}
|
||||
|
||||
.igsp-follow-btn svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Empty State
|
||||
============================================ */
|
||||
.igsp-empty {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
background: #f9f9f9;
|
||||
border-radius: var(--igsp-radius);
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Loading States
|
||||
============================================ */
|
||||
.igsp-loading {
|
||||
position: relative;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.igsp-loader {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
/* Spinner */
|
||||
.igsp-loader-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid var(--igsp-primary);
|
||||
border-radius: 50%;
|
||||
animation: igsp-spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes igsp-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dots */
|
||||
.igsp-loader-dots {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.igsp-loader-dots span {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--igsp-primary);
|
||||
border-radius: 50%;
|
||||
animation: igsp-bounce 1.4s ease-in-out infinite both;
|
||||
}
|
||||
|
||||
.igsp-loader-dots span:nth-child(2) {
|
||||
animation-delay: 0.16s;
|
||||
}
|
||||
|
||||
.igsp-loader-dots span:nth-child(3) {
|
||||
animation-delay: 0.32s;
|
||||
}
|
||||
|
||||
@keyframes igsp-bounce {
|
||||
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pulse */
|
||||
.igsp-loader-pulse {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--igsp-primary);
|
||||
border-radius: 50%;
|
||||
animation: igsp-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes igsp-pulse {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1.2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Skeleton */
|
||||
.igsp-skeleton .igsp-item {
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: igsp-skeleton 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes igsp-skeleton {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lazy load fade-in */
|
||||
.igsp-image.lazyload {
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.igsp-image.loaded {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Responsive Breakpoints
|
||||
============================================ */
|
||||
@media (max-width: 1024px) {
|
||||
.igsp-gallery {
|
||||
--columns: var(--columns-tablet, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.igsp-gallery {
|
||||
--columns: var(--columns-mobile, 1);
|
||||
}
|
||||
|
||||
.igsp-follow-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Accessibility
|
||||
============================================ */
|
||||
.igsp-link:focus {
|
||||
outline: 2px solid var(--igsp-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
.igsp-image,
|
||||
.igsp-overlay,
|
||||
.igsp-hover-overlay,
|
||||
.igsp-icon,
|
||||
.igsp-item,
|
||||
.igsp-follow-btn {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.igsp-loader-spinner,
|
||||
.igsp-loader-dots span,
|
||||
.igsp-loader-pulse {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
103
public/css/gallery-grid.css
Normal file
103
public/css/gallery-grid.css
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Instagram Gallery Sync Pro - Grid Layout
|
||||
*
|
||||
* Standard grid layout styles
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
.igsp-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), 1fr);
|
||||
gap: var(--spacing);
|
||||
}
|
||||
|
||||
/* Responsive grid columns */
|
||||
@media (max-width: 1024px) {
|
||||
.igsp-grid {
|
||||
grid-template-columns: repeat(var(--columns-tablet, 2), 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.igsp-grid {
|
||||
grid-template-columns: repeat(var(--columns-mobile, 1), 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* List layout */
|
||||
.igsp-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing);
|
||||
}
|
||||
|
||||
.igsp-list .igsp-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.igsp-list .igsp-image-wrapper {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.igsp-list .igsp-caption-below {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border: 1px solid #eee;
|
||||
border-left: none;
|
||||
border-radius: 0 var(--igsp-radius) var(--igsp-radius) 0;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.igsp-list .igsp-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.igsp-list .igsp-image-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.igsp-list .igsp-caption-below {
|
||||
border-left: 1px solid #eee;
|
||||
border-top: none;
|
||||
border-radius: 0 0 var(--igsp-radius) var(--igsp-radius);
|
||||
}
|
||||
}
|
||||
|
||||
/* Justified layout */
|
||||
.igsp-justified {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing);
|
||||
}
|
||||
|
||||
.igsp-justified .igsp-item {
|
||||
flex-grow: 1;
|
||||
min-width: 200px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.igsp-justified .igsp-image-wrapper {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.igsp-justified .igsp-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.igsp-justified .igsp-item {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.igsp-justified .igsp-image-wrapper {
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
66
public/css/gallery-masonry.css
Normal file
66
public/css/gallery-masonry.css
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Instagram Gallery Sync Pro - Masonry Layout
|
||||
*
|
||||
* Pinterest-style masonry layout
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
.igsp-masonry {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* CSS-only masonry fallback */
|
||||
.igsp-masonry.no-js {
|
||||
column-count: var(--columns);
|
||||
column-gap: var(--spacing);
|
||||
}
|
||||
|
||||
.igsp-masonry.no-js .igsp-item {
|
||||
break-inside: avoid;
|
||||
margin-bottom: var(--spacing);
|
||||
}
|
||||
|
||||
/* JS-powered masonry */
|
||||
.igsp-masonry.masonry-initialized {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: calc(var(--spacing) / -2);
|
||||
}
|
||||
|
||||
.igsp-masonry.masonry-initialized .igsp-item {
|
||||
padding: calc(var(--spacing) / 2);
|
||||
}
|
||||
|
||||
/* Natural height for masonry */
|
||||
.igsp-masonry .igsp-image-wrapper {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.igsp-masonry .igsp-image {
|
||||
position: relative !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
/* Responsive masonry */
|
||||
@media (max-width: 1024px) {
|
||||
.igsp-masonry.no-js {
|
||||
column-count: var(--columns-tablet, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.igsp-masonry.no-js {
|
||||
column-count: var(--columns-mobile, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading state */
|
||||
.igsp-masonry.loading .igsp-item {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.igsp-masonry.loaded .igsp-item {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
131
public/css/gallery-slider.css
Normal file
131
public/css/gallery-slider.css
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Instagram Gallery Sync Pro - Slider Layout
|
||||
*
|
||||
* Swiper-based carousel/slider styles
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
.igsp-slider {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.igsp-slider .swiper {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.igsp-slider .swiper-slide {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.igsp-slider .swiper-slide .igsp-image-wrapper {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.igsp-slider .swiper-slide .igsp-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* Navigation arrows */
|
||||
.igsp-slider .swiper-button-prev,
|
||||
.igsp-slider .swiper-button-next {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.igsp-slider .swiper-button-prev::after,
|
||||
.igsp-slider .swiper-button-next::after {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.igsp-slider .swiper-button-prev:hover,
|
||||
.igsp-slider .swiper-button-next:hover {
|
||||
background: var(--igsp-primary);
|
||||
}
|
||||
|
||||
.igsp-slider .swiper-button-prev:hover::after,
|
||||
.igsp-slider .swiper-button-next:hover::after {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.igsp-slider .swiper-button-disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* Pagination */
|
||||
.igsp-slider .swiper-pagination {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.igsp-slider .swiper-pagination-bullet {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #ccc;
|
||||
opacity: 1;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.igsp-slider .swiper-pagination-bullet-active {
|
||||
background: var(--igsp-primary);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
/* Centered slides option */
|
||||
.igsp-slider.centered .swiper-slide {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.9);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.igsp-slider.centered .swiper-slide-active {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1024px) {
|
||||
.igsp-slider .swiper-slide .igsp-image-wrapper {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.igsp-slider .swiper-slide .igsp-image-wrapper {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
height: auto;
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
|
||||
.igsp-slider .swiper-button-prev,
|
||||
.igsp-slider .swiper-button-next {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.igsp-slider .swiper-button-prev::after,
|
||||
.igsp-slider .swiper-button-next::after {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide navigation on touch devices */
|
||||
@media (hover: none) {
|
||||
|
||||
.igsp-slider .swiper-button-prev,
|
||||
.igsp-slider .swiper-button-next {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
137
public/css/lightbox.css
Normal file
137
public/css/lightbox.css
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Instagram Gallery Sync Pro - Lightbox Styles
|
||||
*
|
||||
* Custom styles for GLightbox
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
/* Override GLightbox styles */
|
||||
.glightbox-container .gslide-media {
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.glightbox-container .gslide-image img {
|
||||
max-width: 90vw;
|
||||
max-height: 85vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.glightbox-container .gslide-description {
|
||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
.glightbox-container .gdesc-inner {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.glightbox-container .gslide-title {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
font-weight: normal;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.glightbox-container .gnext,
|
||||
.glightbox-container .gprev {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.glightbox-container .gnext svg,
|
||||
.glightbox-container .gprev svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
fill: #333;
|
||||
}
|
||||
|
||||
.glightbox-container .gnext:hover,
|
||||
.glightbox-container .gprev:hover {
|
||||
background: var(--igsp-primary);
|
||||
}
|
||||
|
||||
.glightbox-container .gnext:hover svg,
|
||||
.glightbox-container .gprev:hover svg {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
/* Close button */
|
||||
.glightbox-container .gclose {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 50%;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.glightbox-container .gclose svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: #333;
|
||||
}
|
||||
|
||||
.glightbox-container .gclose:hover {
|
||||
background: var(--igsp-primary);
|
||||
}
|
||||
|
||||
.glightbox-container .gclose:hover svg {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
/* Counter */
|
||||
.glightbox-container .gcounter {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding: 5px 12px;
|
||||
border-radius: 20px;
|
||||
top: 25px;
|
||||
left: 25px;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.glightbox-container .gloader {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
border-left-color: var(--igsp-primary);
|
||||
}
|
||||
|
||||
/* Mobile adjustments */
|
||||
@media (max-width: 767px) {
|
||||
|
||||
.glightbox-container .gnext,
|
||||
.glightbox-container .gprev,
|
||||
.glightbox-container .gclose {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.glightbox-container .gnext svg,
|
||||
.glightbox-container .gprev svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.glightbox-container .gclose svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.glightbox-container .gclose {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.glightbox-container .gcounter {
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
}
|
||||
}
|
||||
1
public/index.php
Normal file
1
public/index.php
Normal file
@@ -0,0 +1 @@
|
||||
<?php // Silence is golden
|
||||
165
public/js/block-editor.js
Normal file
165
public/js/block-editor.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Instagram Gallery Sync Pro - Gutenberg Block Editor
|
||||
*
|
||||
* Block registration and editor UI
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
(function (blocks, element, blockEditor, components, serverSideRender, i18n) {
|
||||
'use strict';
|
||||
|
||||
const { registerBlockType } = blocks;
|
||||
const { createElement: el, Fragment } = element;
|
||||
const { InspectorControls, useBlockProps } = blockEditor;
|
||||
const { PanelBody, SelectControl, RangeControl, ToggleControl, Placeholder } = components;
|
||||
const ServerSideRender = serverSideRender;
|
||||
const { __ } = i18n;
|
||||
|
||||
// Get localized settings
|
||||
const settings = window.igspBlock || {};
|
||||
const strings = settings.strings || {};
|
||||
|
||||
/**
|
||||
* Block Icon
|
||||
*/
|
||||
const blockIcon = el('svg', {
|
||||
viewBox: '0 0 24 24',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
el('path', {
|
||||
d: 'M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z'
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Register Block
|
||||
*/
|
||||
registerBlockType('igsp/instagram-gallery', {
|
||||
title: strings.title || 'Instagram Gallery',
|
||||
description: strings.description || 'Display your Instagram photos in a beautiful gallery.',
|
||||
icon: blockIcon,
|
||||
category: 'widgets',
|
||||
keywords: ['instagram', 'gallery', 'photos', 'social'],
|
||||
supports: {
|
||||
align: ['wide', 'full'],
|
||||
html: false
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit Component
|
||||
*/
|
||||
edit: function (props) {
|
||||
const { attributes, setAttributes } = props;
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
// Inspector controls
|
||||
const inspectorControls = el(InspectorControls, {},
|
||||
el(PanelBody, {
|
||||
title: __('Gallery Settings', 'instagram-gallery-sync-pro'),
|
||||
initialOpen: true
|
||||
},
|
||||
el(SelectControl, {
|
||||
label: strings.layout || 'Layout',
|
||||
value: attributes.layout,
|
||||
options: settings.layouts || [
|
||||
{ value: 'grid', label: 'Grid' },
|
||||
{ value: 'masonry', label: 'Masonry' },
|
||||
{ value: 'slider', label: 'Slider' }
|
||||
],
|
||||
onChange: function (value) {
|
||||
setAttributes({ layout: value });
|
||||
}
|
||||
}),
|
||||
el(RangeControl, {
|
||||
label: strings.columns || 'Columns',
|
||||
value: attributes.columns,
|
||||
min: 1,
|
||||
max: 6,
|
||||
onChange: function (value) {
|
||||
setAttributes({ columns: value });
|
||||
}
|
||||
}),
|
||||
el(RangeControl, {
|
||||
label: strings.spacing || 'Spacing',
|
||||
value: attributes.spacing,
|
||||
min: 0,
|
||||
max: 50,
|
||||
onChange: function (value) {
|
||||
setAttributes({ spacing: value });
|
||||
}
|
||||
}),
|
||||
el(RangeControl, {
|
||||
label: strings.limit || 'Number of Images',
|
||||
value: attributes.limit,
|
||||
min: 1,
|
||||
max: 50,
|
||||
onChange: function (value) {
|
||||
setAttributes({ limit: value });
|
||||
}
|
||||
}),
|
||||
el(SelectControl, {
|
||||
label: strings.order || 'Order',
|
||||
value: attributes.order,
|
||||
options: settings.orders || [
|
||||
{ value: 'newest', label: 'Newest First' },
|
||||
{ value: 'oldest', label: 'Oldest First' },
|
||||
{ value: 'random', label: 'Random' }
|
||||
],
|
||||
onChange: function (value) {
|
||||
setAttributes({ order: value });
|
||||
}
|
||||
})
|
||||
),
|
||||
el(PanelBody, {
|
||||
title: __('Display Options', 'instagram-gallery-sync-pro'),
|
||||
initialOpen: false
|
||||
},
|
||||
el(ToggleControl, {
|
||||
label: strings.lightbox || 'Enable Lightbox',
|
||||
checked: attributes.lightbox,
|
||||
onChange: function (value) {
|
||||
setAttributes({ lightbox: value });
|
||||
}
|
||||
}),
|
||||
el(ToggleControl, {
|
||||
label: strings.captions || 'Show Captions',
|
||||
checked: attributes.captions,
|
||||
onChange: function (value) {
|
||||
setAttributes({ captions: value });
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// Block preview
|
||||
const preview = el(ServerSideRender, {
|
||||
block: 'igsp/instagram-gallery',
|
||||
attributes: attributes,
|
||||
className: 'igsp-block-preview'
|
||||
});
|
||||
|
||||
return el(Fragment, {},
|
||||
inspectorControls,
|
||||
el('div', blockProps,
|
||||
preview
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Save - Uses server-side rendering
|
||||
*/
|
||||
save: function () {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
})(
|
||||
window.wp.blocks,
|
||||
window.wp.element,
|
||||
window.wp.blockEditor,
|
||||
window.wp.components,
|
||||
window.wp.serverSideRender,
|
||||
window.wp.i18n
|
||||
);
|
||||
257
public/js/gallery-frontend.js
Normal file
257
public/js/gallery-frontend.js
Normal file
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Instagram Gallery Sync Pro - Frontend Script
|
||||
*
|
||||
* Handles gallery initialization, masonry, lightbox, and lazy loading
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Gallery Controller
|
||||
*/
|
||||
const IGSPGallery = {
|
||||
|
||||
/**
|
||||
* Initialize all galleries on page
|
||||
*/
|
||||
init: function () {
|
||||
const self = this;
|
||||
|
||||
// Wait for DOM ready
|
||||
$(document).ready(function () {
|
||||
self.initGalleries();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize individual galleries
|
||||
*/
|
||||
initGalleries: function () {
|
||||
const self = this;
|
||||
|
||||
$('.igsp-gallery').each(function () {
|
||||
const $gallery = $(this);
|
||||
const layout = $gallery.data('layout');
|
||||
|
||||
// Set responsive CSS variables
|
||||
self.setResponsiveVariables($gallery);
|
||||
|
||||
// Initialize based on layout
|
||||
switch (layout) {
|
||||
case 'masonry':
|
||||
self.initMasonry($gallery);
|
||||
break;
|
||||
case 'slider':
|
||||
self.initSlider($gallery);
|
||||
break;
|
||||
default:
|
||||
self.initGrid($gallery);
|
||||
}
|
||||
|
||||
// Initialize lightbox if enabled
|
||||
if (typeof igspFrontend !== 'undefined' && igspFrontend.lightbox) {
|
||||
self.initLightbox($gallery);
|
||||
}
|
||||
|
||||
// Initialize lazy loading
|
||||
self.initLazyLoad($gallery);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set responsive CSS variables
|
||||
*/
|
||||
setResponsiveVariables: function ($gallery) {
|
||||
const columnsTablet = $gallery.data('columns-tablet') || 2;
|
||||
const columnsMobile = $gallery.data('columns-mobile') || 1;
|
||||
|
||||
$gallery.css({
|
||||
'--columns-tablet': columnsTablet,
|
||||
'--columns-mobile': columnsMobile
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize grid layout
|
||||
*/
|
||||
initGrid: function ($gallery) {
|
||||
$gallery.addClass('loaded');
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize masonry layout
|
||||
*/
|
||||
initMasonry: function ($gallery) {
|
||||
if (typeof Masonry === 'undefined' || typeof imagesLoaded === 'undefined') {
|
||||
// Fallback to CSS-only masonry
|
||||
$gallery.addClass('no-js loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
$gallery.addClass('loading');
|
||||
|
||||
// Wait for images to load
|
||||
imagesLoaded($gallery[0], function () {
|
||||
const columns = parseInt($gallery.data('columns')) || 3;
|
||||
const spacing = parseInt($gallery.data('spacing')) || 10;
|
||||
|
||||
// Calculate item width
|
||||
const columnWidth = Math.floor(($gallery.width() - (spacing * (columns - 1))) / columns);
|
||||
|
||||
const msnry = new Masonry($gallery[0], {
|
||||
itemSelector: '.igsp-item',
|
||||
columnWidth: columnWidth,
|
||||
gutter: spacing,
|
||||
percentPosition: false,
|
||||
transitionDuration: '0.3s'
|
||||
});
|
||||
|
||||
$gallery.removeClass('loading').addClass('masonry-initialized loaded');
|
||||
|
||||
// Handle responsive
|
||||
let resizeTimeout;
|
||||
$(window).on('resize', function () {
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(function () {
|
||||
msnry.layout();
|
||||
}, 250);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize slider layout
|
||||
*/
|
||||
initSlider: function ($gallery) {
|
||||
if (typeof Swiper === 'undefined') {
|
||||
console.warn('IGSP: Swiper library not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
const $swiperContainer = $gallery.find('.swiper');
|
||||
|
||||
if (!$swiperContainer.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const autoplay = typeof igspFrontend !== 'undefined' && igspFrontend.autoplay;
|
||||
|
||||
new Swiper($swiperContainer[0], {
|
||||
slidesPerView: 'auto',
|
||||
spaceBetween: parseInt($gallery.data('spacing')) || 10,
|
||||
loop: true,
|
||||
centeredSlides: false,
|
||||
autoplay: autoplay ? {
|
||||
delay: 4000,
|
||||
disableOnInteraction: false
|
||||
} : false,
|
||||
pagination: {
|
||||
el: '.swiper-pagination',
|
||||
clickable: true
|
||||
},
|
||||
navigation: {
|
||||
nextEl: '.swiper-button-next',
|
||||
prevEl: '.swiper-button-prev'
|
||||
},
|
||||
breakpoints: {
|
||||
320: {
|
||||
slidesPerView: 1,
|
||||
centeredSlides: true
|
||||
},
|
||||
640: {
|
||||
slidesPerView: 2,
|
||||
centeredSlides: false
|
||||
},
|
||||
1024: {
|
||||
slidesPerView: 3,
|
||||
centeredSlides: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$gallery.addClass('loaded');
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize lightbox
|
||||
*/
|
||||
initLightbox: function ($gallery) {
|
||||
if (typeof GLightbox === 'undefined') {
|
||||
console.warn('IGSP: GLightbox library not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
const lightbox = GLightbox({
|
||||
selector: '.igsp-gallery [data-gallery="igsp-lightbox"]',
|
||||
touchNavigation: true,
|
||||
loop: true,
|
||||
autoplayVideos: false,
|
||||
openEffect: 'zoom',
|
||||
closeEffect: 'zoom',
|
||||
cssEfects: {
|
||||
fade: { in: 'fadeIn', out: 'fadeOut' },
|
||||
zoom: { in: 'zoomIn', out: 'zoomOut' }
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize lazy loading
|
||||
*/
|
||||
initLazyLoad: function ($gallery) {
|
||||
const $lazyImages = $gallery.find('.igsp-image.lazyload');
|
||||
|
||||
if (!$lazyImages.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use native lazy loading if supported
|
||||
if ('loading' in HTMLImageElement.prototype) {
|
||||
$lazyImages.each(function () {
|
||||
const $img = $(this);
|
||||
$img.attr('src', $img.data('src'));
|
||||
$img.removeClass('lazyload').addClass('loaded');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: Intersection Observer
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observer = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
img.src = img.dataset.src;
|
||||
img.classList.remove('lazyload');
|
||||
img.classList.add('loaded');
|
||||
observer.unobserve(img);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
rootMargin: '100px'
|
||||
});
|
||||
|
||||
$lazyImages.each(function () {
|
||||
observer.observe(this);
|
||||
});
|
||||
} else {
|
||||
// Fallback: Load all images immediately
|
||||
$lazyImages.each(function () {
|
||||
const $img = $(this);
|
||||
$img.attr('src', $img.data('src'));
|
||||
$img.removeClass('lazyload').addClass('loaded');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize
|
||||
IGSPGallery.init();
|
||||
|
||||
// Make available globally
|
||||
window.IGSPGallery = IGSPGallery;
|
||||
|
||||
})(jQuery);
|
||||
1
public/js/glightbox.min.js
vendored
Normal file
1
public/js/glightbox.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/js/imagesloaded.pkgd.min.js
vendored
Normal file
12
public/js/imagesloaded.pkgd.min.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* imagesLoaded PACKAGED v5.0.0
|
||||
* JavaScript is all like "You images are done yet or what?"
|
||||
* MIT License
|
||||
*/
|
||||
!function(t,e){"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,(function(){function t(){}let e=t.prototype;return e.on=function(t,e){if(!t||!e)return this;let i=this._events=this._events||{},s=i[t]=i[t]||[];return s.includes(e)||s.push(e),this},e.once=function(t,e){if(!t||!e)return this;this.on(t,e);let i=this._onceEvents=this._onceEvents||{};return(i[t]=i[t]||{})[e]=!0,this},e.off=function(t,e){let i=this._events&&this._events[t];if(!i||!i.length)return this;let s=i.indexOf(e);return-1!=s&&i.splice(s,1),this},e.emitEvent=function(t,e){let i=this._events&&this._events[t];if(!i||!i.length)return this;i=i.slice(0),e=e||[];let s=this._onceEvents&&this._onceEvents[t];for(let n of i){s&&s[n]&&(this.off(t,n),delete s[n]),n.apply(this,e)}return this},e.allOff=function(){return delete this._events,delete this._onceEvents,this},t})),
|
||||
/*!
|
||||
* imagesLoaded v5.0.0
|
||||
* JavaScript is all like "You images are done yet or what?"
|
||||
* MIT License
|
||||
*/
|
||||
function(t,e){"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter")):t.imagesLoaded=e(t,t.EvEmitter)}("undefined"!=typeof window?window:this,(function(t,e){let i=t.jQuery,s=t.console;function n(t,e,o){if(!(this instanceof n))return new n(t,e,o);let r=t;var h;("string"==typeof t&&(r=document.querySelectorAll(t)),r)?(this.elements=(h=r,Array.isArray(h)?h:"object"==typeof h&&"number"==typeof h.length?[...h]:[h]),this.options={},"function"==typeof e?o=e:Object.assign(this.options,e),o&&this.on("always",o),this.getImages(),i&&(this.jqDeferred=new i.Deferred),setTimeout(this.check.bind(this))):s.error(`Bad element for imagesLoaded ${r||t}`)}n.prototype=Object.create(e.prototype),n.prototype.getImages=function(){this.images=[],this.elements.forEach(this.addElementImages,this)};const o=[1,9,11];n.prototype.addElementImages=function(t){"IMG"===t.nodeName&&this.addImage(t),!0===this.options.background&&this.addElementBackgroundImages(t);let{nodeType:e}=t;if(!e||!o.includes(e))return;let i=t.querySelectorAll("img");for(let t of i)this.addImage(t);if("string"==typeof this.options.background){let e=t.querySelectorAll(this.options.background);for(let t of e)this.addElementBackgroundImages(t)}};const r=/url\((['"])?(.*?)\1\)/gi;function h(t){this.img=t}function d(t,e){this.url=t,this.element=e,this.img=new Image}return n.prototype.addElementBackgroundImages=function(t){let e=getComputedStyle(t);if(!e)return;let i=r.exec(e.backgroundImage);for(;null!==i;){let s=i&&i[2];s&&this.addBackground(s,t),i=r.exec(e.backgroundImage)}},n.prototype.addImage=function(t){let e=new h(t);this.images.push(e)},n.prototype.addBackground=function(t,e){let i=new d(t,e);this.images.push(i)},n.prototype.check=function(){if(this.progressedCount=0,this.hasAnyBroken=!1,!this.images.length)return void this.complete();let t=(t,e,i)=>{setTimeout((()=>{this.progress(t,e,i)}))};this.images.forEach((function(e){e.once("progress",t),e.check()}))},n.prototype.progress=function(t,e,i){this.progressedCount++,this.hasAnyBroken=this.hasAnyBroken||!t.isLoaded,this.emitEvent("progress",[this,t,e]),this.jqDeferred&&this.jqDeferred.notify&&this.jqDeferred.notify(this,t),this.progressedCount===this.images.length&&this.complete(),this.options.debug&&s&&s.log(`progress: ${i}`,t,e)},n.prototype.complete=function(){let t=this.hasAnyBroken?"fail":"done";if(this.isComplete=!0,this.emitEvent(t,[this]),this.emitEvent("always",[this]),this.jqDeferred){let t=this.hasAnyBroken?"reject":"resolve";this.jqDeferred[t](this)}},h.prototype=Object.create(e.prototype),h.prototype.check=function(){this.getIsImageComplete()?this.confirm(0!==this.img.naturalWidth,"naturalWidth"):(this.proxyImage=new Image,this.img.crossOrigin&&(this.proxyImage.crossOrigin=this.img.crossOrigin),this.proxyImage.addEventListener("load",this),this.proxyImage.addEventListener("error",this),this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.proxyImage.src=this.img.currentSrc||this.img.src)},h.prototype.getIsImageComplete=function(){return this.img.complete&&this.img.naturalWidth},h.prototype.confirm=function(t,e){this.isLoaded=t;let{parentNode:i}=this.img,s="PICTURE"===i.nodeName?i:this.img;this.emitEvent("progress",[this,s,e])},h.prototype.handleEvent=function(t){let e="on"+t.type;this[e]&&this[e](t)},h.prototype.onload=function(){this.confirm(!0,"onload"),this.unbindEvents()},h.prototype.onerror=function(){this.confirm(!1,"onerror"),this.unbindEvents()},h.prototype.unbindEvents=function(){this.proxyImage.removeEventListener("load",this),this.proxyImage.removeEventListener("error",this),this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype=Object.create(h.prototype),d.prototype.check=function(){this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.img.src=this.url,this.getIsImageComplete()&&(this.confirm(0!==this.img.naturalWidth,"naturalWidth"),this.unbindEvents())},d.prototype.unbindEvents=function(){this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype.confirm=function(t,e){this.isLoaded=t,this.emitEvent("progress",[this,this.element,e])},n.makeJQueryPlugin=function(e){(e=e||t.jQuery)&&(i=e,i.fn.imagesLoaded=function(t,e){return new n(this,t,e).jqDeferred.promise(i(this))})},n.makeJQueryPlugin(),n}));
|
||||
9
public/js/masonry.pkgd.min.js
vendored
Normal file
9
public/js/masonry.pkgd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
14
public/js/swiper.min.js
vendored
Normal file
14
public/js/swiper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
156
uninstall.php
Normal file
156
uninstall.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/**
|
||||
* Uninstall Script for Instagram Gallery Sync Pro
|
||||
*
|
||||
* This file runs when the plugin is deleted from WordPress.
|
||||
* It removes all plugin data including:
|
||||
* - Custom database tables
|
||||
* - Plugin options
|
||||
* - Uploaded images
|
||||
* - Transients
|
||||
*
|
||||
* @package Instagram_Gallery_Sync_Pro
|
||||
*/
|
||||
|
||||
// Exit if not called by WordPress
|
||||
if (!defined('WP_UNINSTALL_PLUGIN')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Require WordPress database class
|
||||
global $wpdb;
|
||||
|
||||
/**
|
||||
* Remove all plugin options
|
||||
*/
|
||||
$options_to_delete = array(
|
||||
// Instagram settings
|
||||
'igsp_username',
|
||||
'igsp_max_images',
|
||||
'igsp_save_locally',
|
||||
'igsp_image_quality',
|
||||
'igsp_sync_interval',
|
||||
'igsp_last_sync',
|
||||
|
||||
// Layout settings
|
||||
'igsp_layout_type',
|
||||
'igsp_columns_desktop',
|
||||
'igsp_columns_tablet',
|
||||
'igsp_columns_mobile',
|
||||
'igsp_spacing',
|
||||
'igsp_padding',
|
||||
'igsp_aspect_ratio',
|
||||
'igsp_object_fit',
|
||||
'igsp_border_radius',
|
||||
'igsp_hover_effect',
|
||||
|
||||
// Display settings
|
||||
'igsp_display_limit',
|
||||
'igsp_order',
|
||||
'igsp_show_caption',
|
||||
'igsp_caption_position',
|
||||
'igsp_show_instagram_btn',
|
||||
'igsp_link_behavior',
|
||||
'igsp_lightbox',
|
||||
'igsp_lazy_loading',
|
||||
'igsp_loader_type',
|
||||
|
||||
// Styling
|
||||
'igsp_primary_color',
|
||||
'igsp_hover_color',
|
||||
'igsp_text_color',
|
||||
'igsp_caption_font_size',
|
||||
'igsp_custom_css',
|
||||
'igsp_css_prefix',
|
||||
|
||||
// Advanced
|
||||
'igsp_debug_mode',
|
||||
'igsp_cache_duration',
|
||||
'igsp_request_timeout',
|
||||
'igsp_user_agent',
|
||||
'igsp_proxy_host',
|
||||
'igsp_proxy_port',
|
||||
'igsp_auto_delete_days',
|
||||
|
||||
// Database version
|
||||
'igsp_db_version',
|
||||
);
|
||||
|
||||
foreach ($options_to_delete as $option) {
|
||||
delete_option($option);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop custom database tables
|
||||
*/
|
||||
$tables = array(
|
||||
$wpdb->prefix . 'instagram_gallery_posts',
|
||||
$wpdb->prefix . 'instagram_gallery_log',
|
||||
);
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$wpdb->query("DROP TABLE IF EXISTS {$table}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all transients
|
||||
*/
|
||||
$wpdb->query(
|
||||
"DELETE FROM {$wpdb->options}
|
||||
WHERE option_name LIKE '_transient_igsp_%'
|
||||
OR option_name LIKE '_transient_timeout_igsp_%'"
|
||||
);
|
||||
|
||||
/**
|
||||
* Remove uploaded images directory
|
||||
*/
|
||||
$upload_dir = wp_upload_dir();
|
||||
$instagram_dir = $upload_dir['basedir'] . '/instagram-gallery';
|
||||
|
||||
if (is_dir($instagram_dir)) {
|
||||
igsp_recursive_delete($instagram_dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively delete a directory and its contents
|
||||
*
|
||||
* @param string $dir Directory path
|
||||
* @return bool
|
||||
*/
|
||||
function igsp_recursive_delete($dir)
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$files = array_diff(scandir($dir), array('.', '..'));
|
||||
|
||||
foreach ($files as $file) {
|
||||
$path = $dir . '/' . $file;
|
||||
|
||||
if (is_dir($path)) {
|
||||
igsp_recursive_delete($path);
|
||||
} else {
|
||||
unlink($path);
|
||||
}
|
||||
}
|
||||
|
||||
return rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear scheduled cron events
|
||||
*/
|
||||
$timestamp = wp_next_scheduled('igsp_sync_instagram');
|
||||
if ($timestamp) {
|
||||
wp_unschedule_event($timestamp, 'igsp_sync_instagram');
|
||||
}
|
||||
|
||||
// Clear any remaining cron events
|
||||
wp_clear_scheduled_hook('igsp_sync_instagram');
|
||||
wp_clear_scheduled_hook('igsp_cleanup_logs');
|
||||
|
||||
/**
|
||||
* Flush rewrite rules
|
||||
*/
|
||||
flush_rewrite_rules();
|
||||
Reference in New Issue
Block a user