Prvi commit: WordPress tema Europe Wonder s CI/CD konfiguracijo
This commit is contained in:
commit
135cc3b4c6
|
|
@ -0,0 +1,29 @@
|
|||
name: Deploy to Test Environment
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Deploy to test environment
|
||||
run: |
|
||||
# Ustvari ciljni direktorij, če ne obstaja
|
||||
mkdir -p /home/spletnimojster-europewonder/htdocs/europewonder.spletnimojster.si/wp-content/themes/Arhiv
|
||||
|
||||
# Kopiraj vse datoteke iz repozitorija v ciljni direktorij
|
||||
# Uporabi rsync za ohranitev datotek, ki niso v repozitoriju
|
||||
rsync -av --delete \
|
||||
--exclude='.git' \
|
||||
--exclude='.gitea' \
|
||||
--exclude='.github' \
|
||||
--exclude='README.md' \
|
||||
./ /home/spletnimojster-europewonder/htdocs/europewonder.spletnimojster.si/wp-content/themes/Arhiv/
|
||||
|
||||
echo "Theme deployed to test environment successfully!"
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
# Europe Wonder WordPress Tema
|
||||
|
||||
WordPress tema za turistično agencijo Europe Wonder, specializirano za organizacijo potovanj po Evropi.
|
||||
|
||||
## CI/CD Avtomatizacija
|
||||
|
||||
Ta repozitorij uporablja Gitea Actions za avtomatsko postavitev (deployment) teme na različna okolja.
|
||||
|
||||
### Razvojna veja (develop)
|
||||
|
||||
Ko pushaš spremembe na vejo `develop`, se tema avtomatsko kopira na testno okolje:
|
||||
- **Testno okolje:** europewonder.spletnimojster.si
|
||||
- **Pot do teme:** /wp-content/themes/Arhiv
|
||||
|
||||
Proces kopiranja:
|
||||
1. Koda se prenese iz repozitorija
|
||||
2. Vse datoteke se kopirajo v ciljni direktorij s pomočjo rsync
|
||||
3. Obstoječe datoteke v ciljnem direktoriju se prepišejo
|
||||
4. Datoteke, ki so samo v ciljnem direktoriju (in ne v repozitoriju), ostanejo nedotaknjene
|
||||
|
||||
### Produkcijska veja (main)
|
||||
|
||||
*To be implemented*
|
||||
|
||||
## Razvoj
|
||||
|
||||
### Lokalno razvojno okolje
|
||||
|
||||
1. Kloniraj repozitorij:
|
||||
```
|
||||
git clone git@git.spletnimojster.si:mark/EuropeWonder.git
|
||||
```
|
||||
|
||||
2. Ustvari in preklopi na vejo develop:
|
||||
```
|
||||
git checkout -b develop
|
||||
```
|
||||
|
||||
3. Po končanem razvoju potisni spremembe:
|
||||
```
|
||||
git add .
|
||||
git commit -m "Opis sprememb"
|
||||
git push origin develop
|
||||
```
|
||||
|
||||
### Postavitev v produkcijo
|
||||
|
||||
Ko so spremembe testirane in pripravljene za produkcijo:
|
||||
|
||||
1. Preklopi na vejo main:
|
||||
```
|
||||
git checkout main
|
||||
```
|
||||
|
||||
2. Združi spremembe iz develop veje:
|
||||
```
|
||||
git merge develop
|
||||
```
|
||||
|
||||
3. Potisni spremembe:
|
||||
```
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## Struktura teme
|
||||
|
||||
- **CSS**: Stilske datoteke
|
||||
- **JS**: JavaScript datoteke
|
||||
- **Images**: Slike in drugi medijski elementi
|
||||
- **Template files**: PHP predloge za različne dele spletne strani
|
||||
|
||||
## Kontakt
|
||||
|
||||
Za vprašanja glede razvoja teme kontaktirajte: [kontaktni podatki]
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
jQuery(document).ready(function($) {
|
||||
// Funkcija za dodajanje novega polja
|
||||
function addNewField(container) {
|
||||
var $container = $('#' + container);
|
||||
var $template = $container.children().first().clone();
|
||||
|
||||
// Počisti vrednosti v novem polju
|
||||
$template.find('input[type="text"], textarea').val('');
|
||||
$template.find('.image-preview').empty();
|
||||
$template.find('.image-preview-wrapper img').remove();
|
||||
$template.find('.itinerary-image-url').val('');
|
||||
$template.find('.itinerary-image-remove').hide();
|
||||
|
||||
// Dodaj novo polje v kontejner
|
||||
$container.append($template);
|
||||
|
||||
// Posodobi indekse za itinerary polja
|
||||
if (container === 'itinerary-container') {
|
||||
updateItineraryIndices();
|
||||
}
|
||||
}
|
||||
|
||||
// Funkcija za odstranitev polja
|
||||
function removeField(button) {
|
||||
var $parent = $(button).closest('.repeatable-field');
|
||||
var $container = $parent.parent();
|
||||
|
||||
// Ne dovoli odstranitve zadnjega polja
|
||||
if ($container.children('.repeatable-field').length > 1) {
|
||||
$parent.remove();
|
||||
|
||||
// Posodobi indekse za itinerary polja
|
||||
if ($container.attr('id') === 'itinerary-container') {
|
||||
updateItineraryIndices();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Posodobi indekse za itinerary polja
|
||||
function updateItineraryIndices() {
|
||||
$('#itinerary-container .repeatable-field').each(function(index) {
|
||||
$(this).find('input, textarea').each(function() {
|
||||
var name = $(this).attr('name');
|
||||
if (name) {
|
||||
name = name.replace(/\[\d+\]/, '[' + index + ']');
|
||||
$(this).attr('name', name);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Event listener za gumbe "Add"
|
||||
$('.add-field').on('click', function() {
|
||||
var container = $(this).data('container');
|
||||
addNewField(container);
|
||||
});
|
||||
|
||||
// Event listener za gumbe "Remove"
|
||||
$(document).on('click', '.remove-field', function() {
|
||||
removeField(this);
|
||||
});
|
||||
|
||||
// Media Uploader za hero sliko
|
||||
var heroImageFrame;
|
||||
$('.hero-image-upload').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (heroImageFrame) {
|
||||
heroImageFrame.open();
|
||||
return;
|
||||
}
|
||||
|
||||
heroImageFrame = wp.media({
|
||||
title: 'Select or Upload Hero Image',
|
||||
button: {
|
||||
text: 'Use this image'
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
heroImageFrame.on('select', function() {
|
||||
var attachment = heroImageFrame.state().get('selection').first().toJSON();
|
||||
$('#hero_image').val(attachment.url);
|
||||
$('.image-preview-wrapper img').remove();
|
||||
$('.image-preview-wrapper').append('<img src="' + attachment.url + '" alt="Hero Image" style="max-width: 300px; height: auto;">');
|
||||
$('.hero-image-remove').show();
|
||||
});
|
||||
|
||||
heroImageFrame.open();
|
||||
});
|
||||
|
||||
// Odstrani hero sliko
|
||||
$('.hero-image-remove').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
$('#hero_image').val('');
|
||||
$('.image-preview-wrapper img').remove();
|
||||
$(this).hide();
|
||||
});
|
||||
|
||||
// Media Uploader za slike itinerarija
|
||||
$(document).on('click', '.itinerary-image-upload', function(e) {
|
||||
e.preventDefault();
|
||||
var $button = $(this);
|
||||
var $field = $button.closest('.itinerary-day');
|
||||
|
||||
var frame = wp.media({
|
||||
title: 'Select or Upload Day Image',
|
||||
button: {
|
||||
text: 'Use this image'
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
frame.on('select', function() {
|
||||
var attachment = frame.state().get('selection').first().toJSON();
|
||||
$field.find('.itinerary-image-url').val(attachment.url);
|
||||
$field.find('.image-preview-wrapper img').remove();
|
||||
$field.find('.image-preview-wrapper').append('<img src="' + attachment.url + '" alt="Day Image" style="max-width: 200px; height: auto;">');
|
||||
$field.find('.itinerary-image-remove').show();
|
||||
});
|
||||
|
||||
frame.open();
|
||||
});
|
||||
|
||||
// Odstrani sliko itinerarija
|
||||
$(document).on('click', '.itinerary-image-remove', function(e) {
|
||||
e.preventDefault();
|
||||
var $field = $(this).closest('.itinerary-day');
|
||||
$field.find('.itinerary-image-url').val('');
|
||||
$field.find('.image-preview-wrapper img').remove();
|
||||
$(this).hide();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"require": {
|
||||
"stripe/stripe-php": "^16.6"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "8a1765450f77ff32d3e0bda5c31034ae",
|
||||
"packages": [
|
||||
{
|
||||
"name": "stripe/stripe-php",
|
||||
"version": "v16.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/stripe/stripe-php.git",
|
||||
"reference": "d6de0a536f00b5c5c74f36b8f4d0d93b035499ff"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/d6de0a536f00b5c5c74f36b8f4d0d93b035499ff",
|
||||
"reference": "d6de0a536f00b5c5c74f36b8f4d0d93b035499ff",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=5.6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "3.5.0",
|
||||
"phpstan/phpstan": "^1.2",
|
||||
"phpunit/phpunit": "^5.7 || ^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Stripe\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Stripe and contributors",
|
||||
"homepage": "https://github.com/stripe/stripe-php/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Stripe PHP Library",
|
||||
"homepage": "https://stripe.com/",
|
||||
"keywords": [
|
||||
"api",
|
||||
"payment processing",
|
||||
"stripe"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/stripe/stripe-php/issues",
|
||||
"source": "https://github.com/stripe/stripe-php/tree/v16.6.0"
|
||||
},
|
||||
"time": "2025-02-24T22:35:29+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
.sortable-posts-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sortable-posts-list li {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
margin-bottom: 5px;
|
||||
cursor: move;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sortable-posts-list li .dashicons {
|
||||
margin-right: 10px;
|
||||
color: #a0a5aa;
|
||||
}
|
||||
|
||||
.sortable-posts-list li:hover .dashicons {
|
||||
color: #23282d;
|
||||
}
|
||||
|
||||
/* Stili za Individual Tours sortable */
|
||||
.sortable-tours-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 20px 0;
|
||||
border: 1px solid #ddd;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.sortable-tours-list .sortable-item {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
cursor: move;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sortable-tours-list .sortable-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.sortable-tours-list .sortable-item .dashicons {
|
||||
margin-right: 10px;
|
||||
color: #a0a5aa;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.sortable-tours-list .sortable-item:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.sortable-tours-list .sortable-item:hover .dashicons {
|
||||
color: #0073aa;
|
||||
}
|
||||
|
||||
.sortable-tours-list .ui-sortable-helper {
|
||||
box-shadow: 0 0 8px rgba(0,0,0,0.2);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.sortable-tours-list .ui-sortable-placeholder {
|
||||
visibility: visible !important;
|
||||
background: #e0f5ff;
|
||||
border: 1px dashed #0073aa;
|
||||
height: 40px;
|
||||
}
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
:root {
|
||||
--accent: #00798c;
|
||||
--accent-dark: #006778;
|
||||
}
|
||||
|
||||
.popup-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
background-color: #003d5b;
|
||||
padding: 40px;
|
||||
border-radius: 10px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background: none;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 42px;
|
||||
margin-bottom: 10px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.popup-subtitle {
|
||||
font-size: 32px;
|
||||
margin-bottom: 30px;
|
||||
color: white;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
form {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#email-form {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
input[type="email"] {
|
||||
width: 100% !important;
|
||||
height: 40px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 15px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
font-size: 16px;
|
||||
background-color: #f0f0f0;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input[type="email"]::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.claim-button {
|
||||
background-color: #edae49;
|
||||
color: white;
|
||||
padding: 15px 30px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.claim-button:hover {
|
||||
background-color: #d99b35;
|
||||
}
|
||||
|
||||
.skip-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
margin: 15px 0;
|
||||
transition: color 0.3s ease;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.skip-button:hover {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.terms-text {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-top: 20px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
display: none;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
margin-top: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Stili za promocijsko kodo */
|
||||
.popup-promo-container {
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.popup-promo-container label {
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
color: #333;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.promo-code-input {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.popup-promo-container input {
|
||||
flex: 1;
|
||||
padding: 12px 15px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.popup-promo-container input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 0 0 2px rgba(0, 121, 140, 0.2);
|
||||
}
|
||||
|
||||
.popup-promo-container input:disabled {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.popup-promo-container button {
|
||||
padding: 12px 24px;
|
||||
background: var(--accent);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.popup-promo-container button:hover {
|
||||
background: var(--accent-dark);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.popup-promo-container button:disabled {
|
||||
background: #4a5568;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
#popup-promo-message {
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#popup-promo-message .promo-success {
|
||||
color: #10B981;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#popup-promo-message .promo-error {
|
||||
color: #EF4444;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.popup-guide-info {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.guide-profile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.guide-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 3px solid #fff;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.guide-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.guide-details h4 {
|
||||
margin: 0 0 5px 0;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.guide-details p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.guide-message {
|
||||
margin-top: 8px !important;
|
||||
font-style: italic;
|
||||
color: #555 !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
/**
|
||||
* Custom SMTP Configuration for Europe Wonder
|
||||
*/
|
||||
|
||||
// Vklopi WordPress beleženje za razhroščevanje
|
||||
if (!defined('WP_DEBUG')) {
|
||||
define('WP_DEBUG', true);
|
||||
}
|
||||
if (!defined('WP_DEBUG_LOG')) {
|
||||
define('WP_DEBUG_LOG', true);
|
||||
}
|
||||
if (!defined('WP_DEBUG_DISPLAY')) {
|
||||
define('WP_DEBUG_DISPLAY', false);
|
||||
}
|
||||
|
||||
// Preveri, če WP Mail SMTP vtičnik še ni definiral teh konstant
|
||||
if (!defined('WPMS_ON') && function_exists('wp_mail')) {
|
||||
// Glavni SMTP konfiguracijski parametri
|
||||
add_action('phpmailer_init', 'custom_smtp_setup', 10);
|
||||
function custom_smtp_setup($phpmailer) {
|
||||
// Nastavi SMTP
|
||||
$phpmailer->isSMTP();
|
||||
|
||||
// Izdajatelj (from naslov)
|
||||
$phpmailer->From = "noreply@europewonder.com";
|
||||
$phpmailer->FromName = "Europe Wonder";
|
||||
|
||||
// Obvezne nastavitve
|
||||
$phpmailer->Host = 'mail.europewonder.com';
|
||||
$phpmailer->SMTPAuth = true;
|
||||
$phpmailer->Port = 465;
|
||||
$phpmailer->Username = 'noreply@europewonder.com';
|
||||
$phpmailer->Password = '#Napoti112358';
|
||||
|
||||
// Varnostne nastavitve
|
||||
$phpmailer->SMTPSecure = 'ssl';
|
||||
|
||||
// Dodatne nastavitve
|
||||
$phpmailer->SMTPAutoTLS = false;
|
||||
|
||||
// Razhroščevanje (nastavi true za razvoj, false za produkcijo)
|
||||
$phpmailer->SMTPDebug = 0;
|
||||
|
||||
// Beleženje
|
||||
error_log('SMTP setup complete with host: ' . $phpmailer->Host . ' and username: ' . $phpmailer->Username);
|
||||
}
|
||||
|
||||
// Definiramo konstante, ki preprečujejo dvojno konfiguracijo
|
||||
define('WPMS_ON', true);
|
||||
|
||||
// Beleženje, da je konfiguracija vključena
|
||||
error_log('Custom SMTP configuration activated');
|
||||
}
|
||||
|
||||
// Popravek za "From" naslov, ki ga včasih prepisuje WordPress
|
||||
add_filter('wp_mail_from', 'custom_mail_from');
|
||||
function custom_mail_from($email) {
|
||||
return 'noreply@europewonder.com';
|
||||
}
|
||||
|
||||
// Popravek za "From" ime, ki ga včasih prepisuje WordPress
|
||||
add_filter('wp_mail_from_name', 'custom_mail_from_name');
|
||||
function custom_mail_from_name($name) {
|
||||
return 'Europe Wonder';
|
||||
}
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
<?php
|
||||
/**
|
||||
* The template for displaying the footer
|
||||
*/
|
||||
?>
|
||||
</div><!-- .main-content -->
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-left">
|
||||
<div class="footer-logo">
|
||||
<img src="<?php echo esc_url(get_theme_mod('footer_logo', get_theme_file_uri('images/logo-footer.png'))); ?>" alt="<?php bloginfo('name'); ?>">
|
||||
</div>
|
||||
<p class="footer-description">
|
||||
<?php echo wp_kses_post(get_theme_mod('footer_description', 'We\'re a travel agency and tour operator for holidays, activities, and other unforgettable experiences in every corner of the Europe, and for every sort of traveler.')); ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<div class="footer-contact">
|
||||
<a href="mailto:<?php echo esc_attr(get_theme_mod('footer_email', 'info@europewonder.com')); ?>">
|
||||
<i class="far fa-envelope"></i>
|
||||
<?php echo esc_html(get_theme_mod('footer_email', 'info@europewonder.com')); ?>
|
||||
</a>
|
||||
<a href="tel:<?php echo esc_attr(str_replace(' ', '', get_theme_mod('footer_phone', '+386 (0) 31 332 823'))); ?>">
|
||||
<i class="fas fa-phone"></i>
|
||||
<?php echo esc_html(get_theme_mod('footer_phone', '+386 (0) 31 332 823')); ?>
|
||||
</a>
|
||||
<a href="https://wa.me/<?php echo esc_attr(get_theme_mod('footer_whatsapp', '38631332823')); ?>">
|
||||
<i class="fab fa-whatsapp"></i>
|
||||
WhatsApp chat
|
||||
</a>
|
||||
<a href="#" class="book-consultation" id="open-calendly">
|
||||
<i class="far fa-calendar-check"></i>
|
||||
Book a Free Consultation
|
||||
</a>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<a href="<?php echo esc_url(home_url('/terms-of-service')); ?>">Terms of Service</a>
|
||||
<a href="<?php echo esc_url(home_url('/privacy-policy')); ?>">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Menu Overlay -->
|
||||
<div class="menu-overlay"></div>
|
||||
|
||||
<!-- Calendly Modal -->
|
||||
<div id="calendly-modal" class="calendly-modal">
|
||||
<div class="calendly-modal-content">
|
||||
<span class="calendly-close">×</span>
|
||||
<div class="calendly-container" id="calendly-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendly CSS -->
|
||||
<style>
|
||||
.calendly-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.7);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.calendly-modal.active {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.calendly-modal-content {
|
||||
position: relative;
|
||||
background-color: #fefefe;
|
||||
margin: 5% auto;
|
||||
padding: 0;
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
height: 85vh;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
||||
transform: translateY(-20px);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.calendly-modal.active .calendly-modal-content {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.calendly-container {
|
||||
height: calc(100% - 40px);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.calendly-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 10px;
|
||||
color: #333;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.calendly-close:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
body.calendly-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.calendly-modal-content {
|
||||
width: 95%;
|
||||
margin: 10% auto;
|
||||
height: 80vh;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Mobile Menu JavaScript -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const mainNav = document.querySelector('.main-nav');
|
||||
const menuOverlay = document.querySelector('.menu-overlay');
|
||||
|
||||
// Toggle menu when hamburger icon is clicked
|
||||
menuToggle?.addEventListener('click', function() {
|
||||
mainNav.classList.toggle('active');
|
||||
menuOverlay.classList.toggle('active');
|
||||
document.body.classList.toggle('menu-open');
|
||||
});
|
||||
|
||||
// Close menu when overlay is clicked
|
||||
menuOverlay?.addEventListener('click', function() {
|
||||
mainNav.classList.remove('active');
|
||||
menuOverlay.classList.remove('active');
|
||||
document.body.classList.remove('menu-open');
|
||||
});
|
||||
|
||||
// Close menu when escape key is pressed
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && mainNav.classList.contains('active')) {
|
||||
mainNav.classList.remove('active');
|
||||
menuOverlay.classList.remove('active');
|
||||
document.body.classList.remove('menu-open');
|
||||
}
|
||||
|
||||
// Zapri tudi calendly modal ob pritisku ESC
|
||||
if (e.key === 'Escape' && document.getElementById('calendly-modal').classList.contains('active')) {
|
||||
closeCalendlyModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Add dropdown functionality for submenus on mobile
|
||||
const hasChildrenItems = document.querySelectorAll('.menu-item-has-children');
|
||||
|
||||
hasChildrenItems.forEach(function(item) {
|
||||
// Create dropdown toggle button
|
||||
const dropdownToggle = document.createElement('span');
|
||||
dropdownToggle.className = 'dropdown-toggle';
|
||||
dropdownToggle.innerHTML = '<i class="fas fa-chevron-down"></i>';
|
||||
|
||||
// Add toggle button after the link
|
||||
item.querySelector('a').after(dropdownToggle);
|
||||
|
||||
// Toggle submenu on click
|
||||
dropdownToggle.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
this.classList.toggle('active');
|
||||
const submenu = this.nextElementSibling;
|
||||
if (submenu && submenu.classList.contains('sub-menu')) {
|
||||
if (submenu.style.maxHeight) {
|
||||
submenu.style.maxHeight = null;
|
||||
} else {
|
||||
submenu.style.maxHeight = submenu.scrollHeight + 'px';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Calendly Modal functionality
|
||||
const openCalendlyBtn = document.getElementById('open-calendly');
|
||||
const calendlyModal = document.getElementById('calendly-modal');
|
||||
const calendlyClose = document.querySelector('.calendly-close');
|
||||
const calendlyContainer = document.getElementById('calendly-container');
|
||||
|
||||
// Naloži Calendly SDK
|
||||
function loadCalendlyScript() {
|
||||
if (!window.Calendly) {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://assets.calendly.com/assets/external/widget.js';
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
}
|
||||
|
||||
// Odpri Calendly modal
|
||||
function openCalendlyModal() {
|
||||
const calendlyLink = '<?php echo esc_url(get_theme_mod('calendly_link', '')); ?>';
|
||||
|
||||
// Če Calendly link še ni nastavljen, odpri mailto link
|
||||
if (!calendlyLink) {
|
||||
window.location.href = 'mailto:<?php echo esc_attr(get_theme_mod('footer_email', 'info@europewonder.com')); ?>?subject=Free%20Consultation';
|
||||
return;
|
||||
}
|
||||
|
||||
// Očisti prejšnjo vsebino
|
||||
calendlyContainer.innerHTML = '';
|
||||
|
||||
// Dodaj Calendly widget
|
||||
Calendly.initInlineWidget({
|
||||
url: calendlyLink,
|
||||
parentElement: calendlyContainer,
|
||||
prefill: {},
|
||||
utm: {}
|
||||
});
|
||||
|
||||
// Prikaži modal
|
||||
calendlyModal.classList.add('active');
|
||||
document.body.classList.add('calendly-open');
|
||||
}
|
||||
|
||||
// Zapri Calendly modal
|
||||
function closeCalendlyModal() {
|
||||
calendlyModal.classList.remove('active');
|
||||
document.body.classList.remove('calendly-open');
|
||||
|
||||
// Po kratki zakasnitvi resetiraj vsebino
|
||||
setTimeout(() => {
|
||||
calendlyContainer.innerHTML = '';
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Event listeners za Calendly modal
|
||||
if (openCalendlyBtn) {
|
||||
openCalendlyBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
loadCalendlyScript();
|
||||
openCalendlyModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (calendlyClose) {
|
||||
calendlyClose.addEventListener('click', closeCalendlyModal);
|
||||
}
|
||||
|
||||
// Zapri modal ob kliku izven modala
|
||||
calendlyModal?.addEventListener('click', function(e) {
|
||||
if (e.target === calendlyModal) {
|
||||
closeCalendlyModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Preloži Calendly knjižnico
|
||||
loadCalendlyScript();
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php wp_footer(); ?>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,666 @@
|
|||
<?php get_header(); ?>
|
||||
|
||||
<!-- Hero section -->
|
||||
<section class="hero" style="background-image: linear-gradient(to top,
|
||||
rgba(0, 0, 0, 0.7) 0%,
|
||||
rgba(0, 0, 0, 0.4) 30%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
), url('<?php echo esc_url(get_theme_mod('hero_background', get_theme_file_uri('images/hero.jpg'))); ?>');">
|
||||
<div class="hero-content">
|
||||
<h1><?php echo esc_html(get_theme_mod('hero_title', 'Europe Wonder')); ?></h1>
|
||||
<p><?php echo esc_html(get_theme_mod('hero_subtitle', 'Your path to unforgettable experiences')); ?></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Custom Inquiry Section -->
|
||||
<section class="inquiry-section" <?php if (get_theme_mod('inquiry_section_background')) : ?>style="background-image: linear-gradient(to top,
|
||||
rgba(255, 255, 255, 0.9) 0%,
|
||||
rgba(255, 255, 255, 0.8) 100%
|
||||
), url('<?php echo esc_url(get_theme_mod('inquiry_section_background')); ?>');"<?php endif; ?>>
|
||||
<div class="container">
|
||||
<div class="inquiry-content">
|
||||
<h2><?php echo esc_html(get_theme_mod('inquiry_section_title', 'Ready to Plan Your Custom Journey?')); ?></h2>
|
||||
<p><?php echo wp_kses_post(get_theme_mod('inquiry_section_description', 'A world of moonlit forests and twisting turquoise rivers, Slovenia is a fairytale destination. Home to storybook castles, historic towns, and picturesque alpine landscapes; let us create your perfect holiday experience.')); ?></p>
|
||||
<a href="<?php echo esc_url(home_url('/inquiry/')); ?>" class="btn-inquiry"><?php echo esc_html(get_theme_mod('inquiry_button_text', 'START PLANNING')); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Inquiry Form Popup -->
|
||||
<div id="inquiry-popup" class="inquiry-popup">
|
||||
<div class="inquiry-popup-content">
|
||||
<span class="inquiry-close">×</span>
|
||||
<h3>Custom Journey Inquiry</h3>
|
||||
<form id="custom-inquiry-form" class="inquiry-form">
|
||||
<?php wp_nonce_field('custom_inquiry_nonce', 'inquiry_security'); ?>
|
||||
<input type="hidden" name="action" value="custom_inquiry_submit">
|
||||
<div class="form-group">
|
||||
<label for="name">Your Name</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group half">
|
||||
<label for="travel-date">Planned Travel Date</label>
|
||||
<input type="date" id="travel-date" name="travel_date">
|
||||
</div>
|
||||
<div class="form-group half">
|
||||
<label for="travelers">Number of Travelers</label>
|
||||
<input type="number" id="travelers" name="travelers" min="1" value="2">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination">Preferred Destination(s)</label>
|
||||
<input type="text" id="destination" name="destination" placeholder="e.g. Slovenia, Croatia, Hungary...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="message">Your Travel Wishes</label>
|
||||
<textarea id="message" name="message" rows="4" placeholder="Tell us what you're looking for in your custom journey..."></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn-submit">Submit Inquiry</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tours section -->
|
||||
<section class="tours">
|
||||
<div class="container">
|
||||
<?php
|
||||
// Pridobi vse Experience Journeye
|
||||
$journeys = get_posts(array(
|
||||
'post_type' => 'experience_journey',
|
||||
'posts_per_page' => -1,
|
||||
));
|
||||
|
||||
// Pridobi uporabniško določen vrstni red journeyev
|
||||
$order = get_option('experience_journey_order', '');
|
||||
$order_array = !empty($order) ? explode(',', $order) : array();
|
||||
|
||||
// Če imamo vrstni red, sortiraj journeye po njem
|
||||
if (!empty($order_array)) {
|
||||
$position_map = array_flip($order_array);
|
||||
usort($journeys, function($a, $b) use ($position_map) {
|
||||
$pos_a = isset($position_map[$a->ID]) ? $position_map[$a->ID] : PHP_INT_MAX;
|
||||
$pos_b = isset($position_map[$b->ID]) ? $position_map[$b->ID] : PHP_INT_MAX;
|
||||
return $pos_a - $pos_b;
|
||||
});
|
||||
}
|
||||
|
||||
// Za vsak journey prikaži njegove ture
|
||||
foreach ($journeys as $journey) {
|
||||
// Pridobi naslov journeya
|
||||
$journey_title = get_post_meta($journey->ID, '_page_title', true);
|
||||
if (!$journey_title) {
|
||||
$journey_title = $journey->post_title;
|
||||
}
|
||||
|
||||
// Pridobi ture za ta journey
|
||||
$saved_order = get_option('individual_tour_order_' . $journey->ID, '');
|
||||
$tour_order_array = !empty($saved_order) ? explode(',', $saved_order) : array();
|
||||
|
||||
$args = array(
|
||||
'post_type' => 'individual_tour',
|
||||
'posts_per_page' => -1,
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_experience_journey',
|
||||
'value' => $journey->ID,
|
||||
'compare' => '='
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Če imamo shranjen vrstni red tur, uporabi ga
|
||||
if (!empty($tour_order_array)) {
|
||||
$args['post__in'] = $tour_order_array;
|
||||
$args['orderby'] = 'post__in';
|
||||
}
|
||||
|
||||
$tours = new WP_Query($args);
|
||||
|
||||
// Če ima journey ture, prikaži naslov in ture
|
||||
if ($tours->have_posts()) :
|
||||
?>
|
||||
<h3 class="journey-title"><?php echo esc_html($journey_title); ?></h3>
|
||||
<div class="tours-grid">
|
||||
<?php
|
||||
while ($tours->have_posts()) : $tours->the_post();
|
||||
// Pridobi hero sliko
|
||||
$hero_image_id = get_post_meta(get_the_ID(), '_hero_image', true);
|
||||
$image = wp_get_attachment_image_url($hero_image_id, 'full');
|
||||
// Če hero slika ne obstaja, uporabi featured image
|
||||
if (!$image) {
|
||||
$image = get_the_post_thumbnail_url(get_the_ID(), 'large');
|
||||
}
|
||||
// Če ni nobene slike, uporabi placeholder
|
||||
if (!$image) {
|
||||
$image = get_theme_file_uri('images/placeholder.jpg');
|
||||
}
|
||||
|
||||
$price = get_post_meta(get_the_ID(), '_tour_price', true);
|
||||
$duration = get_post_meta(get_the_ID(), '_tour_duration', true);
|
||||
?>
|
||||
<div class="tour-card">
|
||||
<div class="tour-image">
|
||||
<img src="<?php echo esc_url($image); ?>"
|
||||
alt="<?php echo esc_attr(get_the_title()); ?>"
|
||||
loading="lazy">
|
||||
</div>
|
||||
<div class="tour-info">
|
||||
<div>
|
||||
<h3><?php echo esc_html(get_the_title()); ?></h3>
|
||||
</div>
|
||||
<div>
|
||||
<div class="tour-details">
|
||||
<?php if ($price) : ?>
|
||||
<span class="price">€<?php echo esc_html($price); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($duration) : ?>
|
||||
<span class="duration"><?php echo esc_html($duration); ?> days</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<a href="<?php echo esc_url(get_permalink()); ?>" class="btn-view">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
endwhile;
|
||||
wp_reset_postdata();
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
endif;
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.tours-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 350px);
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
width: 350px;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
height: 420px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tour-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.tour-card .tour-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tour-card .tour-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.tour-card:hover .tour-image img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.tour-card .tour-info {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.tour-card h3 {
|
||||
margin: 0;
|
||||
color: var(--dark);
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.tour-card p {
|
||||
color: #666;
|
||||
margin-bottom: 1rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tour-card .tour-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tour-card .price {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.tour-card .duration {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tour-card .btn-view {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 0.8rem;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.tour-card .btn-view:hover {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
.journey-title {
|
||||
text-align: center;
|
||||
margin: 3rem 0 2rem;
|
||||
color: var(--dark);
|
||||
font-size: 2rem;
|
||||
position: relative;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.journey-title:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60px;
|
||||
height: 3px;
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
.journey-title:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tours-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: 450px;
|
||||
}
|
||||
|
||||
.journey-title {
|
||||
font-size: 1.75rem;
|
||||
margin: 2rem 0 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Inquiry Section CSS -->
|
||||
<style>
|
||||
.inquiry-section {
|
||||
background-color: #f7f7f7;
|
||||
padding: 4rem 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inquiry-content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.inquiry-content h2 {
|
||||
color: var(--dark);
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.inquiry-content p {
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.8;
|
||||
color: #555;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.btn-inquiry {
|
||||
display: inline-block;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
padding: 1rem 2.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-inquiry:hover {
|
||||
background-color: var(--accent-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Popup Styles */
|
||||
.inquiry-popup {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.inquiry-popup.active {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.inquiry-popup-content {
|
||||
background-color: white;
|
||||
margin: 5% auto;
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(-20px);
|
||||
transition: transform 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inquiry-popup.active .inquiry-popup-content {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.inquiry-close {
|
||||
position: absolute;
|
||||
top: 1.2rem;
|
||||
right: 1.5rem;
|
||||
font-size: 2rem;
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.inquiry-close:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.inquiry-popup h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--dark);
|
||||
font-size: 1.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inquiry-form .form-group {
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.inquiry-form .half {
|
||||
width: 49%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.inquiry-form .form-group:nth-child(3) {
|
||||
margin-right: 2%;
|
||||
}
|
||||
|
||||
.inquiry-form label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #555;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.inquiry-form input,
|
||||
.inquiry-form textarea {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.inquiry-form input:focus,
|
||||
.inquiry-form textarea:focus {
|
||||
border-color: var(--accent);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.inquiry-form .btn-submit {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.3s ease;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.inquiry-form .btn-submit:hover {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
body.popup-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.inquiry-content h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.inquiry-content p {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.inquiry-popup-content {
|
||||
padding: 1.5rem;
|
||||
margin: 10% auto;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.inquiry-form .half {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.inquiry-form .form-group:nth-child(3) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Inquiry Form JavaScript -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const openInquiryBtn = document.getElementById('open-inquiry-form');
|
||||
const inquiryPopup = document.getElementById('inquiry-popup');
|
||||
const inquiryClose = document.querySelector('.inquiry-close');
|
||||
const inquiryForm = document.getElementById('custom-inquiry-form');
|
||||
|
||||
// Zagotovimo, da je popup začetno skrit
|
||||
inquiryPopup.classList.remove('active');
|
||||
document.body.classList.remove('popup-open');
|
||||
|
||||
// Odpri popup
|
||||
openInquiryBtn.addEventListener('click', function() {
|
||||
inquiryPopup.classList.add('active');
|
||||
document.body.classList.add('popup-open');
|
||||
});
|
||||
|
||||
// Zapri popup (X gumb)
|
||||
inquiryClose.addEventListener('click', function() {
|
||||
inquiryPopup.classList.remove('active');
|
||||
document.body.classList.remove('popup-open');
|
||||
});
|
||||
|
||||
// Zapri popup (klik izven obrazca)
|
||||
inquiryPopup.addEventListener('click', function(e) {
|
||||
if (e.target === inquiryPopup) {
|
||||
inquiryPopup.classList.remove('active');
|
||||
document.body.classList.remove('popup-open');
|
||||
}
|
||||
});
|
||||
|
||||
// Zapri popup (tipka ESC)
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && inquiryPopup.classList.contains('active')) {
|
||||
inquiryPopup.classList.remove('active');
|
||||
document.body.classList.remove('popup-open');
|
||||
}
|
||||
});
|
||||
|
||||
// Pošlji obrazec
|
||||
inquiryForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Prikaži indikator nalaganja
|
||||
const submitBtn = inquiryForm.querySelector('.btn-submit');
|
||||
const originalBtnText = submitBtn.textContent;
|
||||
submitBtn.textContent = 'Sending...';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
// Pridobi podatke iz obrazca
|
||||
const formData = new FormData(inquiryForm);
|
||||
formData.append('security', document.querySelector('#inquiry_security').value);
|
||||
|
||||
console.log('Sending form data to AJAX endpoint...');
|
||||
|
||||
// Pošlji AJAX zahtevek
|
||||
fetch(<?php echo json_encode(admin_url('admin-ajax.php')); ?>, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Response status:', response.status);
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Response data:', data);
|
||||
|
||||
// Ponastavi gumb
|
||||
submitBtn.textContent = originalBtnText;
|
||||
submitBtn.disabled = false;
|
||||
|
||||
if (data.success) {
|
||||
// Uspešno poslano
|
||||
alert(data.data);
|
||||
inquiryForm.reset();
|
||||
inquiryPopup.classList.remove('active');
|
||||
document.body.classList.remove('popup-open');
|
||||
} else {
|
||||
// Napaka
|
||||
console.error('Form submission error:', data);
|
||||
alert(data.data || 'There was an error sending your inquiry. Please try again.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Ponastavi gumb
|
||||
submitBtn.textContent = originalBtnText;
|
||||
submitBtn.disabled = false;
|
||||
|
||||
console.error('Error during form submission:', error);
|
||||
alert('There was an error sending your inquiry: ' + error.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Video section -->
|
||||
<section class="video-section">
|
||||
<div class="container">
|
||||
<div style="padding:75% 0 0 0;position:relative;">
|
||||
<iframe src="<?php echo esc_url(get_theme_mod('video_url', 'https://player.vimeo.com/video/1061260497?h=7fb6d020c3')); ?>&badge=0&autopause=0&player_id=0&app_id=58479"
|
||||
frameborder="0"
|
||||
allow="autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media"
|
||||
style="position:absolute;top:0;left:0;width:100%;height:100%;"
|
||||
title="EU-Wonder_30sec">
|
||||
</iframe>
|
||||
</div>
|
||||
<script src="https://player.vimeo.com/api/player.js"></script>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Customer Reviews section -->
|
||||
<section class="reviews-section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">What Our Customers Say</h2>
|
||||
<div class="reviews-container">
|
||||
<!-- Elfsight Google Reviews | Untitled Google Reviews -->
|
||||
<script src="https://static.elfsight.com/platform/platform.js" async></script>
|
||||
<div class="elfsight-app-2b69875e-3d29-4d62-bd00-e8f5c51b40d3" data-elfsight-app-lazy></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CSS za Reviews sekcijo -->
|
||||
<style>
|
||||
.reviews-section {
|
||||
padding: 4rem 0;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.reviews-container {
|
||||
margin-top: 2rem;
|
||||
max-width: 1100px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.reviews-section {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,533 @@
|
|||
<?php
|
||||
/**
|
||||
* Grilc Tours functions and definitions
|
||||
*/
|
||||
|
||||
// Registracija potrebnih WordPress funkcionalnosti
|
||||
function grilctours_setup() {
|
||||
// Add default posts and comments RSS feed links to head.
|
||||
add_theme_support('automatic-feed-links');
|
||||
|
||||
// Let WordPress manage the document title.
|
||||
add_theme_support('title-tag');
|
||||
|
||||
// Enable support for Post Thumbnails on posts and pages.
|
||||
add_theme_support('post-thumbnails');
|
||||
|
||||
// Add support for custom logo
|
||||
add_theme_support('custom-logo');
|
||||
|
||||
// Registriraj navigacijske menije
|
||||
register_nav_menus(array(
|
||||
'primary' => esc_html__('Primary Menu', 'grilctours'),
|
||||
'footer' => esc_html__('Footer Menu', 'grilctours'),
|
||||
));
|
||||
}
|
||||
add_action('after_setup_theme', 'grilctours_setup');
|
||||
|
||||
// Registracija stilov in skript
|
||||
function grilctours_scripts() {
|
||||
// Glavni CSS
|
||||
wp_enqueue_style('grilctours-style', get_stylesheet_uri());
|
||||
|
||||
// Google Fonts
|
||||
wp_enqueue_style('google-fonts', 'https://fonts.googleapis.com/css2?family=Inter+Tight:wght@500&display=swap');
|
||||
|
||||
// Font Awesome
|
||||
wp_enqueue_style('font-awesome', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css');
|
||||
|
||||
wp_enqueue_script('grilctours-script', get_template_directory_uri() . '/js/script.js', array(), '1.0.0', true);
|
||||
}
|
||||
add_action('wp_enqueue_scripts', 'grilctours_scripts');
|
||||
|
||||
// Dodaj podporo za media uploader v admin
|
||||
function grilctours_admin_scripts($hook) {
|
||||
global $post_type;
|
||||
|
||||
// Naloži skripte samo na edit in add new straneh za individual_tour
|
||||
if (($hook == 'post.php' || $hook == 'post-new.php') && $post_type == 'individual_tour') {
|
||||
wp_enqueue_media();
|
||||
wp_enqueue_script('grilctours-admin-script', get_template_directory_uri() . '/admin-scripts.js', array('jquery'), '1.0.0', true);
|
||||
}
|
||||
}
|
||||
add_action('admin_enqueue_scripts', 'grilctours_admin_scripts');
|
||||
|
||||
// Registracija Custom Post Type za Experience Journey
|
||||
function register_experience_journey_post_type() {
|
||||
$labels = array(
|
||||
'name' => 'Experience Journeys',
|
||||
'singular_name' => 'Experience Journey',
|
||||
'menu_name' => 'Experience Journeys',
|
||||
'add_new' => 'Add New Journey',
|
||||
'add_new_item' => 'Add New Journey',
|
||||
'edit_item' => 'Edit Journey',
|
||||
'new_item' => 'New Journey',
|
||||
'view_item' => 'View Journey',
|
||||
'search_items' => 'Search Journeys',
|
||||
'not_found' => 'No journeys found',
|
||||
'not_found_in_trash' => 'No journeys found in Trash'
|
||||
);
|
||||
|
||||
$args = array(
|
||||
'labels' => $labels,
|
||||
'public' => true,
|
||||
'has_archive' => true,
|
||||
'menu_icon' => 'dashicons-palmtree',
|
||||
'supports' => array('title', 'editor', 'thumbnail', 'excerpt'),
|
||||
'rewrite' => array('slug' => 'journeys'),
|
||||
'show_in_rest' => true,
|
||||
'menu_position' => 5
|
||||
);
|
||||
|
||||
register_post_type('experience_journey', $args);
|
||||
}
|
||||
add_action('init', 'register_experience_journey_post_type');
|
||||
|
||||
// Registracija Custom Post Type za Individual Tour
|
||||
function register_individual_tour_post_type() {
|
||||
$labels = array(
|
||||
'name' => 'Individual Tours',
|
||||
'singular_name' => 'Individual Tour',
|
||||
'menu_name' => 'Individual Tours',
|
||||
'add_new' => 'Add New Tour',
|
||||
'add_new_item' => 'Add New Tour',
|
||||
'edit_item' => 'Edit Tour',
|
||||
'new_item' => 'New Tour',
|
||||
'view_item' => 'View Tour',
|
||||
'search_items' => 'Search Tours',
|
||||
'not_found' => 'No tours found',
|
||||
'not_found_in_trash' => 'No tours found in Trash'
|
||||
);
|
||||
|
||||
$args = array(
|
||||
'labels' => $labels,
|
||||
'public' => true,
|
||||
'has_archive' => true,
|
||||
'menu_icon' => 'dashicons-location',
|
||||
'supports' => array('title', 'editor', 'thumbnail', 'excerpt'),
|
||||
'rewrite' => array('slug' => 'tours'),
|
||||
'show_in_rest' => true,
|
||||
'menu_position' => 6
|
||||
);
|
||||
|
||||
register_post_type('individual_tour', $args);
|
||||
}
|
||||
add_action('init', 'register_individual_tour_post_type');
|
||||
|
||||
// Vključi datoteko za meta polja tur
|
||||
require_once get_template_directory() . '/tour-meta-fields.php';
|
||||
|
||||
// Registracija meta boxov za Experience Journey
|
||||
function register_experience_journey_meta_boxes() {
|
||||
add_meta_box(
|
||||
'experience_journey_details',
|
||||
'Journey Details',
|
||||
'render_experience_journey_meta_box',
|
||||
'experience_journey',
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
}
|
||||
add_action('add_meta_boxes', 'register_experience_journey_meta_boxes');
|
||||
|
||||
// Render meta box za Experience Journey
|
||||
function render_experience_journey_meta_box($post) {
|
||||
$target_audience = get_post_meta($post->ID, '_target_audience', true);
|
||||
|
||||
wp_nonce_field('experience_journey_nonce', 'experience_journey_nonce');
|
||||
?>
|
||||
<div class="journey-meta-box">
|
||||
<p>
|
||||
<label for="target_audience"><strong>Target Audience</strong></label>
|
||||
<textarea id="target_audience" name="target_audience" class="widefat" rows="5"><?php echo esc_textarea($target_audience); ?></textarea>
|
||||
<span class="description">Describe who this journey is designed for.</span>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Shrani meta podatke za Experience Journey
|
||||
function save_experience_journey_meta($post_id) {
|
||||
if (!isset($_POST['experience_journey_nonce']) || !wp_verify_nonce($_POST['experience_journey_nonce'], 'experience_journey_nonce')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current_user_can('edit_post', $post_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['target_audience'])) {
|
||||
update_post_meta($post_id, '_target_audience', sanitize_textarea_field($_POST['target_audience']));
|
||||
}
|
||||
}
|
||||
add_action('save_post_experience_journey', 'save_experience_journey_meta');
|
||||
|
||||
// Registracija meta boxov za Individual Tour
|
||||
function register_individual_tour_meta_boxes() {
|
||||
add_meta_box(
|
||||
'individual_tour_details',
|
||||
'Tour Details',
|
||||
'render_individual_tour_meta_box',
|
||||
'individual_tour',
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
|
||||
add_meta_box(
|
||||
'individual_tour_journey',
|
||||
'Experience Journey',
|
||||
'render_individual_tour_journey_meta_box',
|
||||
'individual_tour',
|
||||
'side',
|
||||
'default'
|
||||
);
|
||||
}
|
||||
add_action('add_meta_boxes', 'register_individual_tour_meta_boxes');
|
||||
|
||||
// Render meta box za Individual Tour
|
||||
function render_individual_tour_meta_box($post) {
|
||||
$price = get_post_meta($post->ID, '_price', true);
|
||||
$duration = get_post_meta($post->ID, '_duration', true);
|
||||
$distance = get_post_meta($post->ID, '_distance', true);
|
||||
$fitness_level = get_post_meta($post->ID, '_fitness_level', true);
|
||||
$hero_image = get_post_meta($post->ID, '_hero_image', true);
|
||||
$highlights = get_post_meta($post->ID, '_highlights', true) ?: array('');
|
||||
$inclusions = get_post_meta($post->ID, '_inclusions', true) ?: array('');
|
||||
$optional_extras = get_post_meta($post->ID, '_optional_extras', true) ?: array('');
|
||||
$itinerary = get_post_meta($post->ID, '_itinerary', true) ?: array(array('title' => '', 'description' => '', 'image' => ''));
|
||||
|
||||
wp_nonce_field('individual_tour_nonce', 'individual_tour_nonce');
|
||||
?>
|
||||
<div class="tour-meta-box">
|
||||
<p>
|
||||
<label for="price"><strong>Price (€)</strong></label>
|
||||
<input type="number" id="price" name="price" value="<?php echo esc_attr($price); ?>" class="widefat" min="0" step="0.01">
|
||||
</p>
|
||||
<p>
|
||||
<label for="duration"><strong>Duration</strong></label>
|
||||
<input type="text" id="duration" name="duration" value="<?php echo esc_attr($duration); ?>" class="widefat" placeholder="e.g., 7 days / 6 nights">
|
||||
</p>
|
||||
<p>
|
||||
<label for="distance"><strong>Distance</strong></label>
|
||||
<input type="text" id="distance" name="distance" value="<?php echo esc_attr($distance); ?>" class="widefat" placeholder="e.g., 60 - 100 km/day">
|
||||
</p>
|
||||
<p>
|
||||
<label for="fitness_level"><strong>Fitness Level</strong></label>
|
||||
<select id="fitness_level" name="fitness_level" class="widefat">
|
||||
<option value="">Select fitness level</option>
|
||||
<option value="Beginner" <?php selected($fitness_level, 'Beginner'); ?>>Beginner</option>
|
||||
<option value="Intermediate" <?php selected($fitness_level, 'Intermediate'); ?>>Intermediate</option>
|
||||
<option value="Advanced" <?php selected($fitness_level, 'Advanced'); ?>>Advanced</option>
|
||||
<option value="Expert" <?php selected($fitness_level, 'Expert'); ?>>Expert</option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<div class="hero-image-field">
|
||||
<h4>Hero Image</h4>
|
||||
<p>This image will be displayed at the top of the tour page. If not set, the featured image will be used.</p>
|
||||
<div class="image-preview-wrapper">
|
||||
<?php if (!empty($hero_image)) : ?>
|
||||
<img src="<?php echo esc_url($hero_image); ?>" alt="Hero Image" class="image-preview" style="max-width: 300px; height: auto;">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<input type="text" name="hero_image" id="hero_image" value="<?php echo esc_attr($hero_image); ?>" class="widefat">
|
||||
<button type="button" class="button hero-image-upload">Upload/Select Image</button>
|
||||
<button type="button" class="button hero-image-remove" <?php echo empty($hero_image) ? 'style="display:none;"' : ''; ?>>Remove Image</button>
|
||||
</div>
|
||||
|
||||
<div class="repeatable-fields">
|
||||
<h4>Highlights</h4>
|
||||
<div id="highlights-container">
|
||||
<?php foreach ($highlights as $highlight) : ?>
|
||||
<div class="repeatable-field">
|
||||
<input type="text" name="highlights[]" value="<?php echo esc_attr($highlight); ?>" class="widefat">
|
||||
<button type="button" class="remove-field">Remove</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="add-field" data-container="highlights-container">Add Highlight</button>
|
||||
</div>
|
||||
|
||||
<div class="repeatable-fields">
|
||||
<h4>Inclusions</h4>
|
||||
<div id="inclusions-container">
|
||||
<?php foreach ($inclusions as $inclusion) : ?>
|
||||
<div class="repeatable-field">
|
||||
<input type="text" name="inclusions[]" value="<?php echo esc_attr($inclusion); ?>" class="widefat">
|
||||
<button type="button" class="remove-field">Remove</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="add-field" data-container="inclusions-container">Add Inclusion</button>
|
||||
</div>
|
||||
|
||||
<div class="repeatable-fields">
|
||||
<h4>Optional Extras</h4>
|
||||
<div id="optional-extras-container">
|
||||
<?php foreach ($optional_extras as $extra) : ?>
|
||||
<div class="repeatable-field">
|
||||
<input type="text" name="optional_extras[]" value="<?php echo esc_attr($extra); ?>" class="widefat">
|
||||
<button type="button" class="remove-field">Remove</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="add-field" data-container="optional-extras-container">Add Optional Extra</button>
|
||||
</div>
|
||||
|
||||
<div class="repeatable-fields">
|
||||
<h4>Itinerary</h4>
|
||||
<div id="itinerary-container">
|
||||
<?php foreach ($itinerary as $index => $day) : ?>
|
||||
<div class="repeatable-field itinerary-day">
|
||||
<input type="text" name="itinerary[<?php echo $index; ?>][title]" value="<?php echo esc_attr($day['title']); ?>" class="widefat" placeholder="Day Title">
|
||||
<textarea name="itinerary[<?php echo $index; ?>][description]" class="widefat" rows="3" placeholder="Day Description"><?php echo esc_textarea($day['description']); ?></textarea>
|
||||
|
||||
<div class="itinerary-image-field">
|
||||
<label><strong>Day Image</strong></label>
|
||||
<div class="image-preview-wrapper">
|
||||
<?php if (!empty($day['image'])) : ?>
|
||||
<img src="<?php echo esc_url($day['image']); ?>" alt="Day Image" class="image-preview" style="max-width: 200px; height: auto;">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<input type="text" name="itinerary[<?php echo $index; ?>][image]" value="<?php echo esc_attr($day['image'] ?? ''); ?>" class="widefat itinerary-image-url">
|
||||
<button type="button" class="button itinerary-image-upload">Upload/Select Image</button>
|
||||
<button type="button" class="button itinerary-image-remove" <?php echo empty($day['image']) ? 'style="display:none;"' : ''; ?>>Remove Image</button>
|
||||
</div>
|
||||
|
||||
<button type="button" class="remove-field">Remove Day</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="add-field" data-container="itinerary-container">Add Day</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.repeatable-fields {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
.repeatable-field {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
.remove-field {
|
||||
margin-top: 5px;
|
||||
color: #a00;
|
||||
}
|
||||
.itinerary-day {
|
||||
padding: 15px;
|
||||
}
|
||||
.itinerary-day textarea {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.hero-image-field, .itinerary-image-field {
|
||||
margin: 15px 0;
|
||||
padding: 10px;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.image-preview-wrapper {
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
jQuery(document).ready(function($) {
|
||||
$('.add-field').click(function() {
|
||||
var container = $('#' + $(this).data('container'));
|
||||
var field = container.children().first().clone();
|
||||
field.find('input, textarea').val('');
|
||||
field.find('.image-preview-wrapper img').remove();
|
||||
field.find('.itinerary-image-remove').hide();
|
||||
|
||||
// Posodobi indekse za itinerary
|
||||
if (container.attr('id') === 'itinerary-container') {
|
||||
var newIndex = container.children().length;
|
||||
field.find('input, textarea').each(function() {
|
||||
var name = $(this).attr('name');
|
||||
if (name) {
|
||||
name = name.replace(/\[\d+\]/, '[' + newIndex + ']');
|
||||
$(this).attr('name', name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
container.append(field);
|
||||
});
|
||||
|
||||
$(document).on('click', '.remove-field', function() {
|
||||
var container = $(this).closest('.repeatable-fields').find('.repeatable-field');
|
||||
if (container.length > 1) {
|
||||
$(this).closest('.repeatable-field').remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Media uploader za hero sliko
|
||||
$('.hero-image-upload').click(function(e) {
|
||||
e.preventDefault();
|
||||
var button = $(this);
|
||||
var imageField = $('#hero_image');
|
||||
var previewWrapper = button.siblings('.image-preview-wrapper');
|
||||
var removeButton = button.siblings('.hero-image-remove');
|
||||
|
||||
var frame = wp.media({
|
||||
title: 'Select or Upload Hero Image',
|
||||
button: {
|
||||
text: 'Use this image'
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
frame.on('select', function() {
|
||||
var attachment = frame.state().get('selection').first().toJSON();
|
||||
imageField.val(attachment.url);
|
||||
|
||||
// Posodobi preview
|
||||
previewWrapper.html('<img src="' + attachment.url + '" alt="Hero Image" class="image-preview" style="max-width: 300px; height: auto;">');
|
||||
removeButton.show();
|
||||
});
|
||||
|
||||
frame.open();
|
||||
});
|
||||
|
||||
// Odstrani hero sliko
|
||||
$('.hero-image-remove').click(function() {
|
||||
$('#hero_image').val('');
|
||||
$(this).siblings('.image-preview-wrapper').empty();
|
||||
$(this).hide();
|
||||
});
|
||||
|
||||
// Media uploader za itinerary slike
|
||||
$(document).on('click', '.itinerary-image-upload', function(e) {
|
||||
e.preventDefault();
|
||||
var button = $(this);
|
||||
var imageField = button.siblings('.itinerary-image-url');
|
||||
var previewWrapper = button.siblings('.image-preview-wrapper');
|
||||
var removeButton = button.siblings('.itinerary-image-remove');
|
||||
|
||||
var frame = wp.media({
|
||||
title: 'Select or Upload Day Image',
|
||||
button: {
|
||||
text: 'Use this image'
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
frame.on('select', function() {
|
||||
var attachment = frame.state().get('selection').first().toJSON();
|
||||
imageField.val(attachment.url);
|
||||
|
||||
// Posodobi preview
|
||||
previewWrapper.html('<img src="' + attachment.url + '" alt="Day Image" class="image-preview" style="max-width: 200px; height: auto;">');
|
||||
removeButton.show();
|
||||
});
|
||||
|
||||
frame.open();
|
||||
});
|
||||
|
||||
// Odstrani itinerary sliko
|
||||
$(document).on('click', '.itinerary-image-remove', function() {
|
||||
$(this).siblings('.itinerary-image-url').val('');
|
||||
$(this).siblings('.image-preview-wrapper').empty();
|
||||
$(this).hide();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Render meta box za povezavo Individual Tour z Experience Journey
|
||||
function render_individual_tour_journey_meta_box($post) {
|
||||
$current_journey = get_post_meta($post->ID, '_experience_journey', true);
|
||||
|
||||
$journeys = get_posts(array(
|
||||
'post_type' => 'experience_journey',
|
||||
'posts_per_page' => -1,
|
||||
'orderby' => 'title',
|
||||
'order' => 'ASC'
|
||||
));
|
||||
|
||||
if (empty($journeys)) {
|
||||
echo '<p>No experience journeys found. Please create one first.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<select name="experience_journey" class="widefat">';
|
||||
echo '<option value="">Select Experience Journey</option>';
|
||||
|
||||
foreach ($journeys as $journey) {
|
||||
printf(
|
||||
'<option value="%s" %s>%s</option>',
|
||||
esc_attr($journey->ID),
|
||||
selected($current_journey, $journey->ID, false),
|
||||
esc_html($journey->post_title)
|
||||
);
|
||||
}
|
||||
|
||||
echo '</select>';
|
||||
}
|
||||
|
||||
// Shrani meta podatke za Individual Tour
|
||||
function save_individual_tour_meta($post_id) {
|
||||
if (!isset($_POST['individual_tour_nonce']) || !wp_verify_nonce($_POST['individual_tour_nonce'], 'individual_tour_nonce')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current_user_can('edit_post', $post_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shrani osnovne podatke
|
||||
$fields = array(
|
||||
'price' => 'sanitize_text_field',
|
||||
'duration' => 'sanitize_text_field',
|
||||
'distance' => 'sanitize_text_field',
|
||||
'fitness_level' => 'sanitize_text_field',
|
||||
'hero_image' => 'esc_url_raw',
|
||||
'experience_journey' => 'absint'
|
||||
);
|
||||
|
||||
foreach ($fields as $field => $sanitize_callback) {
|
||||
if (isset($_POST[$field])) {
|
||||
update_post_meta($post_id, '_' . $field, $sanitize_callback($_POST[$field]));
|
||||
}
|
||||
}
|
||||
|
||||
// Shrani array podatke
|
||||
$array_fields = array('highlights', 'inclusions', 'optional_extras');
|
||||
foreach ($array_fields as $field) {
|
||||
if (isset($_POST[$field]) && is_array($_POST[$field])) {
|
||||
$sanitized = array_map('sanitize_text_field', array_filter($_POST[$field]));
|
||||
update_post_meta($post_id, '_' . $field, $sanitized);
|
||||
}
|
||||
}
|
||||
|
||||
// Shrani itinerary
|
||||
if (isset($_POST['itinerary']) && is_array($_POST['itinerary'])) {
|
||||
$itinerary = array();
|
||||
foreach ($_POST['itinerary'] as $day) {
|
||||
if (!empty($day['title']) || !empty($day['description'])) {
|
||||
$itinerary[] = array(
|
||||
'title' => sanitize_text_field($day['title']),
|
||||
'description' => sanitize_textarea_field($day['description']),
|
||||
'image' => isset($day['image']) ? esc_url_raw($day['image']) : ''
|
||||
);
|
||||
}
|
||||
}
|
||||
update_post_meta($post_id, '_itinerary', $itinerary);
|
||||
}
|
||||
}
|
||||
add_action('save_post_individual_tour', 'save_individual_tour_meta');
|
||||
|
|
@ -0,0 +1,533 @@
|
|||
<?php
|
||||
/**
|
||||
* Grilc Tours functions and definitions
|
||||
*/
|
||||
|
||||
function grilctours_setup() {
|
||||
// Add default posts and comments RSS feed links to head.
|
||||
add_theme_support('automatic-feed-links');
|
||||
|
||||
// Let WordPress manage the document title.
|
||||
add_theme_support('title-tag');
|
||||
|
||||
// Enable support for Post Thumbnails on posts and pages.
|
||||
add_theme_support('post-thumbnails');
|
||||
|
||||
// Add support for custom logo
|
||||
add_theme_support('custom-logo');
|
||||
|
||||
// Register navigation menus
|
||||
register_nav_menus(array(
|
||||
'primary' => esc_html__('Primary Menu', 'grilctours'),
|
||||
'footer' => esc_html__('Footer Menu', 'grilctours'),
|
||||
));
|
||||
}
|
||||
add_action('after_setup_theme', 'grilctours_setup');
|
||||
|
||||
// Enqueue scripts and styles
|
||||
function grilctours_scripts() {
|
||||
wp_enqueue_style('grilctours-style', get_stylesheet_uri());
|
||||
|
||||
// Dodaj Font Awesome
|
||||
wp_enqueue_style('font-awesome', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css');
|
||||
|
||||
wp_enqueue_script('grilctours-script', get_template_directory_uri() . '/js/script.js', array(), '1.0.0', true);
|
||||
}
|
||||
add_action('wp_enqueue_scripts', 'grilctours_scripts');
|
||||
|
||||
// Dodaj podporo za media uploader v admin
|
||||
function grilctours_admin_scripts() {
|
||||
global $post_type;
|
||||
if ('individual_tour' == $post_type) {
|
||||
wp_enqueue_media();
|
||||
}
|
||||
}
|
||||
add_action('admin_enqueue_scripts', 'grilctours_admin_scripts');
|
||||
|
||||
// Registracija Experience Journey post type
|
||||
function register_experience_journey_post_type() {
|
||||
$labels = array(
|
||||
'name' => 'Experience Journeys',
|
||||
'singular_name' => 'Experience Journey',
|
||||
'menu_name' => 'Experience Journeys',
|
||||
'add_new' => 'Add New',
|
||||
'add_new_item' => 'Add New Experience Journey',
|
||||
'edit_item' => 'Edit Experience Journey',
|
||||
'new_item' => 'New Experience Journey',
|
||||
'view_item' => 'View Experience Journey',
|
||||
'search_items' => 'Search Experience Journeys',
|
||||
'not_found' => 'No experience journeys found',
|
||||
'not_found_in_trash' => 'No experience journeys found in Trash',
|
||||
);
|
||||
|
||||
$args = array(
|
||||
'labels' => $labels,
|
||||
'public' => true,
|
||||
'publicly_queryable' => true,
|
||||
'show_ui' => true,
|
||||
'show_in_menu' => true,
|
||||
'query_var' => true,
|
||||
'rewrite' => array('slug' => 'journeys'),
|
||||
'capability_type' => 'post',
|
||||
'has_archive' => true,
|
||||
'hierarchical' => false,
|
||||
'menu_position' => null,
|
||||
'supports' => array('title', 'editor', 'thumbnail'),
|
||||
'menu_icon' => 'dashicons-groups',
|
||||
);
|
||||
|
||||
register_post_type('experience_journey', $args);
|
||||
}
|
||||
add_action('init', 'register_experience_journey_post_type');
|
||||
|
||||
// Registracija Individual Tour post type
|
||||
function register_individual_tour_post_type() {
|
||||
$labels = array(
|
||||
'name' => 'Individual Tours',
|
||||
'singular_name' => 'Individual Tour',
|
||||
'menu_name' => 'Individual Tours',
|
||||
'add_new' => 'Add New',
|
||||
'add_new_item' => 'Add New Individual Tour',
|
||||
'edit_item' => 'Edit Individual Tour',
|
||||
'new_item' => 'New Individual Tour',
|
||||
'view_item' => 'View Individual Tour',
|
||||
'search_items' => 'Search Individual Tours',
|
||||
'not_found' => 'No individual tours found',
|
||||
'not_found_in_trash' => 'No individual tours found in Trash',
|
||||
);
|
||||
|
||||
$args = array(
|
||||
'labels' => $labels,
|
||||
'public' => true,
|
||||
'publicly_queryable' => true,
|
||||
'show_ui' => true,
|
||||
'show_in_menu' => true,
|
||||
'query_var' => true,
|
||||
'rewrite' => array('slug' => 'tours'),
|
||||
'capability_type' => 'post',
|
||||
'has_archive' => true,
|
||||
'hierarchical' => false,
|
||||
'menu_position' => null,
|
||||
'supports' => array('title', 'editor', 'thumbnail'),
|
||||
'menu_icon' => 'dashicons-location-alt',
|
||||
);
|
||||
|
||||
register_post_type('individual_tour', $args);
|
||||
}
|
||||
add_action('init', 'register_individual_tour_post_type');
|
||||
|
||||
// Registracija meta boxov za Experience Journey
|
||||
function register_experience_journey_meta_boxes() {
|
||||
add_meta_box(
|
||||
'experience_journey_details',
|
||||
'Journey Details',
|
||||
'render_experience_journey_meta_box',
|
||||
'experience_journey',
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
}
|
||||
add_action('add_meta_boxes', 'register_experience_journey_meta_boxes');
|
||||
|
||||
// Render meta box za Experience Journey
|
||||
function render_experience_journey_meta_box($post) {
|
||||
$target_audience = get_post_meta($post->ID, '_target_audience', true);
|
||||
|
||||
wp_nonce_field('experience_journey_nonce', 'experience_journey_nonce');
|
||||
?>
|
||||
<div class="journey-meta-box">
|
||||
<p>
|
||||
<label for="target_audience"><strong>Target Audience</strong></label>
|
||||
<textarea id="target_audience" name="target_audience" class="widefat" rows="5"><?php echo esc_textarea($target_audience); ?></textarea>
|
||||
<span class="description">Describe who this journey is designed for.</span>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Registracija meta boxov za Individual Tour
|
||||
function register_individual_tour_meta_boxes() {
|
||||
add_meta_box(
|
||||
'individual_tour_details',
|
||||
'Tour Details',
|
||||
'render_individual_tour_meta_box',
|
||||
'individual_tour',
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
|
||||
add_meta_box(
|
||||
'individual_tour_journey',
|
||||
'Experience Journey',
|
||||
'render_individual_tour_journey_meta_box',
|
||||
'individual_tour',
|
||||
'side',
|
||||
'default'
|
||||
);
|
||||
}
|
||||
add_action('add_meta_boxes', 'register_individual_tour_meta_boxes');
|
||||
|
||||
// Render meta box za Individual Tour
|
||||
function render_individual_tour_meta_box($post) {
|
||||
$price = get_post_meta($post->ID, '_price', true);
|
||||
$duration = get_post_meta($post->ID, '_duration', true);
|
||||
$distance = get_post_meta($post->ID, '_distance', true);
|
||||
$fitness_level = get_post_meta($post->ID, '_fitness_level', true);
|
||||
$hero_image = get_post_meta($post->ID, '_hero_image', true);
|
||||
$highlights = get_post_meta($post->ID, '_highlights', true) ?: array('');
|
||||
$inclusions = get_post_meta($post->ID, '_inclusions', true) ?: array('');
|
||||
$optional_extras = get_post_meta($post->ID, '_optional_extras', true) ?: array('');
|
||||
$itinerary = get_post_meta($post->ID, '_itinerary', true) ?: array(array('title' => '', 'description' => '', 'image' => ''));
|
||||
|
||||
wp_nonce_field('individual_tour_nonce', 'individual_tour_nonce');
|
||||
?>
|
||||
<div class="tour-meta-box">
|
||||
<p>
|
||||
<label for="price"><strong>Price (€)</strong></label>
|
||||
<input type="number" id="price" name="price" value="<?php echo esc_attr($price); ?>" class="widefat" min="0" step="0.01">
|
||||
</p>
|
||||
<p>
|
||||
<label for="duration"><strong>Duration</strong></label>
|
||||
<input type="text" id="duration" name="duration" value="<?php echo esc_attr($duration); ?>" class="widefat" placeholder="e.g., 7 days / 6 nights">
|
||||
</p>
|
||||
<p>
|
||||
<label for="distance"><strong>Distance</strong></label>
|
||||
<input type="text" id="distance" name="distance" value="<?php echo esc_attr($distance); ?>" class="widefat" placeholder="e.g., 60 - 100 km/day">
|
||||
</p>
|
||||
<p>
|
||||
<label for="fitness_level"><strong>Fitness Level</strong></label>
|
||||
<select id="fitness_level" name="fitness_level" class="widefat">
|
||||
<option value="">Select fitness level</option>
|
||||
<option value="Beginner" <?php selected($fitness_level, 'Beginner'); ?>>Beginner</option>
|
||||
<option value="Intermediate" <?php selected($fitness_level, 'Intermediate'); ?>>Intermediate</option>
|
||||
<option value="Advanced" <?php selected($fitness_level, 'Advanced'); ?>>Advanced</option>
|
||||
<option value="Expert" <?php selected($fitness_level, 'Expert'); ?>>Expert</option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<div class="hero-image-field">
|
||||
<h4>Hero Image</h4>
|
||||
<p>This image will be displayed at the top of the tour page. If not set, the featured image will be used.</p>
|
||||
<div class="image-preview-wrapper">
|
||||
<?php if (!empty($hero_image)) : ?>
|
||||
<img src="<?php echo esc_url($hero_image); ?>" alt="Hero Image" class="image-preview" style="max-width: 300px; height: auto;">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<input type="text" name="hero_image" id="hero_image" value="<?php echo esc_attr($hero_image); ?>" class="widefat">
|
||||
<button type="button" class="button hero-image-upload">Upload/Select Image</button>
|
||||
<button type="button" class="button hero-image-remove" <?php echo empty($hero_image) ? 'style="display:none;"' : ''; ?>>Remove Image</button>
|
||||
</div>
|
||||
|
||||
<div class="repeatable-fields">
|
||||
<h4>Highlights</h4>
|
||||
<div id="highlights-container">
|
||||
<?php foreach ($highlights as $highlight) : ?>
|
||||
<div class="repeatable-field">
|
||||
<input type="text" name="highlights[]" value="<?php echo esc_attr($highlight); ?>" class="widefat">
|
||||
<button type="button" class="remove-field">Remove</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="add-field" data-container="highlights-container">Add Highlight</button>
|
||||
</div>
|
||||
|
||||
<div class="repeatable-fields">
|
||||
<h4>Inclusions</h4>
|
||||
<div id="inclusions-container">
|
||||
<?php foreach ($inclusions as $inclusion) : ?>
|
||||
<div class="repeatable-field">
|
||||
<input type="text" name="inclusions[]" value="<?php echo esc_attr($inclusion); ?>" class="widefat">
|
||||
<button type="button" class="remove-field">Remove</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="add-field" data-container="inclusions-container">Add Inclusion</button>
|
||||
</div>
|
||||
|
||||
<div class="repeatable-fields">
|
||||
<h4>Optional Extras</h4>
|
||||
<div id="optional-extras-container">
|
||||
<?php foreach ($optional_extras as $extra) : ?>
|
||||
<div class="repeatable-field">
|
||||
<input type="text" name="optional_extras[]" value="<?php echo esc_attr($extra); ?>" class="widefat">
|
||||
<button type="button" class="remove-field">Remove</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="add-field" data-container="optional-extras-container">Add Optional Extra</button>
|
||||
</div>
|
||||
|
||||
<div class="repeatable-fields">
|
||||
<h4>Itinerary</h4>
|
||||
<div id="itinerary-container">
|
||||
<?php foreach ($itinerary as $index => $day) : ?>
|
||||
<div class="repeatable-field itinerary-day">
|
||||
<input type="text" name="itinerary[<?php echo $index; ?>][title]" value="<?php echo esc_attr($day['title']); ?>" class="widefat" placeholder="Day Title">
|
||||
<textarea name="itinerary[<?php echo $index; ?>][description]" class="widefat" rows="3" placeholder="Day Description"><?php echo esc_textarea($day['description']); ?></textarea>
|
||||
|
||||
<div class="itinerary-image-field">
|
||||
<label><strong>Day Image</strong></label>
|
||||
<div class="image-preview-wrapper">
|
||||
<?php if (!empty($day['image'])) : ?>
|
||||
<img src="<?php echo esc_url($day['image']); ?>" alt="Day Image" class="image-preview" style="max-width: 200px; height: auto;">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<input type="text" name="itinerary[<?php echo $index; ?>][image]" value="<?php echo esc_attr($day['image'] ?? ''); ?>" class="widefat itinerary-image-url">
|
||||
<button type="button" class="button itinerary-image-upload">Upload/Select Image</button>
|
||||
<button type="button" class="button itinerary-image-remove" <?php echo empty($day['image']) ? 'style="display:none;"' : ''; ?>>Remove Image</button>
|
||||
</div>
|
||||
|
||||
<button type="button" class="remove-field">Remove Day</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="add-field" data-container="itinerary-container">Add Day</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.repeatable-fields {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
.repeatable-field {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
.remove-field {
|
||||
margin-top: 5px;
|
||||
color: #a00;
|
||||
}
|
||||
.itinerary-day {
|
||||
padding: 15px;
|
||||
}
|
||||
.itinerary-day textarea {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.hero-image-field, .itinerary-image-field {
|
||||
margin: 15px 0;
|
||||
padding: 10px;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.image-preview-wrapper {
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
jQuery(document).ready(function($) {
|
||||
// Obstoječa koda za ponavljajoča se polja
|
||||
$('.add-field').click(function() {
|
||||
var container = $('#' + $(this).data('container'));
|
||||
var field = container.children().first().clone();
|
||||
field.find('input, textarea').val('');
|
||||
field.find('.image-preview-wrapper img').remove();
|
||||
field.find('.itinerary-image-remove').hide();
|
||||
|
||||
// Posodobi indekse za itinerary
|
||||
if (container.attr('id') === 'itinerary-container') {
|
||||
var newIndex = container.children().length;
|
||||
field.find('input, textarea').each(function() {
|
||||
var name = $(this).attr('name');
|
||||
if (name) {
|
||||
name = name.replace(/\[\d+\]/, '[' + newIndex + ']');
|
||||
$(this).attr('name', name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
container.append(field);
|
||||
});
|
||||
|
||||
$(document).on('click', '.remove-field', function() {
|
||||
var container = $(this).closest('.repeatable-fields').find('.repeatable-field');
|
||||
if (container.length > 1) {
|
||||
$(this).closest('.repeatable-field').remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Media uploader za hero sliko
|
||||
$('.hero-image-upload').click(function(e) {
|
||||
e.preventDefault();
|
||||
var button = $(this);
|
||||
var imageField = $('#hero_image');
|
||||
var previewWrapper = button.siblings('.image-preview-wrapper');
|
||||
var removeButton = button.siblings('.hero-image-remove');
|
||||
|
||||
var frame = wp.media({
|
||||
title: 'Select or Upload Hero Image',
|
||||
button: {
|
||||
text: 'Use this image'
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
frame.on('select', function() {
|
||||
var attachment = frame.state().get('selection').first().toJSON();
|
||||
imageField.val(attachment.url);
|
||||
|
||||
// Posodobi preview
|
||||
previewWrapper.html('<img src="' + attachment.url + '" alt="Hero Image" class="image-preview" style="max-width: 300px; height: auto;">');
|
||||
removeButton.show();
|
||||
});
|
||||
|
||||
frame.open();
|
||||
});
|
||||
|
||||
// Odstrani hero sliko
|
||||
$('.hero-image-remove').click(function() {
|
||||
$('#hero_image').val('');
|
||||
$(this).siblings('.image-preview-wrapper').empty();
|
||||
$(this).hide();
|
||||
});
|
||||
|
||||
// Media uploader za itinerary slike
|
||||
$(document).on('click', '.itinerary-image-upload', function(e) {
|
||||
e.preventDefault();
|
||||
var button = $(this);
|
||||
var imageField = button.siblings('.itinerary-image-url');
|
||||
var previewWrapper = button.siblings('.image-preview-wrapper');
|
||||
var removeButton = button.siblings('.itinerary-image-remove');
|
||||
|
||||
var frame = wp.media({
|
||||
title: 'Select or Upload Day Image',
|
||||
button: {
|
||||
text: 'Use this image'
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
frame.on('select', function() {
|
||||
var attachment = frame.state().get('selection').first().toJSON();
|
||||
imageField.val(attachment.url);
|
||||
|
||||
// Posodobi preview
|
||||
previewWrapper.html('<img src="' + attachment.url + '" alt="Day Image" class="image-preview" style="max-width: 200px; height: auto;">');
|
||||
removeButton.show();
|
||||
});
|
||||
|
||||
frame.open();
|
||||
});
|
||||
|
||||
// Odstrani itinerary sliko
|
||||
$(document).on('click', '.itinerary-image-remove', function() {
|
||||
$(this).siblings('.itinerary-image-url').val('');
|
||||
$(this).siblings('.image-preview-wrapper').empty();
|
||||
$(this).hide();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Render meta box za povezavo Individual Tour z Experience Journey
|
||||
function render_individual_tour_journey_meta_box($post) {
|
||||
$current_journey = get_post_meta($post->ID, '_experience_journey', true);
|
||||
|
||||
$journeys = get_posts(array(
|
||||
'post_type' => 'experience_journey',
|
||||
'posts_per_page' => -1,
|
||||
'orderby' => 'title',
|
||||
'order' => 'ASC'
|
||||
));
|
||||
|
||||
if (empty($journeys)) {
|
||||
echo '<p>No experience journeys found. Please create one first.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<select name="experience_journey" class="widefat">';
|
||||
echo '<option value="">Select Experience Journey</option>';
|
||||
|
||||
foreach ($journeys as $journey) {
|
||||
printf(
|
||||
'<option value="%s" %s>%s</option>',
|
||||
esc_attr($journey->ID),
|
||||
selected($current_journey, $journey->ID, false),
|
||||
esc_html($journey->post_title)
|
||||
);
|
||||
}
|
||||
|
||||
echo '</select>';
|
||||
}
|
||||
|
||||
// Shrani meta podatke za Experience Journey
|
||||
function save_experience_journey_meta($post_id) {
|
||||
if (!isset($_POST['experience_journey_nonce']) || !wp_verify_nonce($_POST['experience_journey_nonce'], 'experience_journey_nonce')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current_user_can('edit_post', $post_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['target_audience'])) {
|
||||
update_post_meta($post_id, '_target_audience', sanitize_textarea_field($_POST['target_audience']));
|
||||
}
|
||||
}
|
||||
add_action('save_post_experience_journey', 'save_experience_journey_meta');
|
||||
|
||||
// Shrani meta podatke za Individual Tour
|
||||
function save_individual_tour_meta($post_id) {
|
||||
if (!isset($_POST['individual_tour_nonce']) || !wp_verify_nonce($_POST['individual_tour_nonce'], 'individual_tour_nonce')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current_user_can('edit_post', $post_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shrani osnovne podatke
|
||||
$fields = array(
|
||||
'price' => 'sanitize_text_field',
|
||||
'duration' => 'sanitize_text_field',
|
||||
'distance' => 'sanitize_text_field',
|
||||
'fitness_level' => 'sanitize_text_field',
|
||||
'hero_image' => 'esc_url_raw',
|
||||
'experience_journey' => 'absint'
|
||||
);
|
||||
|
||||
foreach ($fields as $field => $sanitize_callback) {
|
||||
if (isset($_POST[$field])) {
|
||||
update_post_meta($post_id, '_' . $field, $sanitize_callback($_POST[$field]));
|
||||
}
|
||||
}
|
||||
|
||||
// Shrani array podatke
|
||||
$array_fields = array('highlights', 'inclusions', 'optional_extras');
|
||||
foreach ($array_fields as $field) {
|
||||
if (isset($_POST[$field]) && is_array($_POST[$field])) {
|
||||
$sanitized = array_map('sanitize_text_field', array_filter($_POST[$field]));
|
||||
update_post_meta($post_id, '_' . $field, $sanitized);
|
||||
}
|
||||
}
|
||||
|
||||
// Shrani itinerary
|
||||
if (isset($_POST['itinerary']) && is_array($_POST['itinerary'])) {
|
||||
$itinerary = array();
|
||||
foreach ($_POST['itinerary'] as $day) {
|
||||
if (!empty($day['title']) || !empty($day['description'])) {
|
||||
$itinerary[] = array(
|
||||
'title' => sanitize_text_field($day['title']),
|
||||
'description' => sanitize_textarea_field($day['description']),
|
||||
'image' => isset($day['image']) ? esc_url_raw($day['image']) : ''
|
||||
);
|
||||
}
|
||||
}
|
||||
update_post_meta($post_id, '_itinerary', $itinerary);
|
||||
}
|
||||
}
|
||||
add_action('save_post_individual_tour', 'save_individual_tour_meta');
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<meta charset="<?php bloginfo('charset'); ?>">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php wp_title('|', true, 'right'); ?><?php bloginfo('name'); ?></title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/accounting.js/0.4.1/accounting.min.js"></script>
|
||||
<?php wp_head(); ?>
|
||||
</head>
|
||||
<body <?php body_class(); ?>>
|
||||
<?php wp_body_open(); ?>
|
||||
|
||||
<!-- Header -->
|
||||
<header class="main-header">
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
<div class="logo">
|
||||
<a href="<?php echo esc_url(home_url('/')); ?>">
|
||||
<img src="<?php echo esc_url(get_theme_mod('header_logo', get_theme_file_uri('images/logo.png'))); ?>" alt="<?php bloginfo('name'); ?>">
|
||||
</a>
|
||||
</div>
|
||||
<button class="mobile-menu-toggle" id="menuToggle">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
<nav class="main-nav">
|
||||
<?php
|
||||
if (has_nav_menu('primary')) {
|
||||
wp_nav_menu(array(
|
||||
'theme_location' => 'primary',
|
||||
'menu_class' => 'menu',
|
||||
'container' => false,
|
||||
'fallback_cb' => false
|
||||
));
|
||||
} else {
|
||||
// Fallback menu, če meni ni določen v skrbniškem vmesniku
|
||||
echo '<ul class="menu">';
|
||||
echo '<li><a href="' . esc_url(home_url('/')) . '" ' . (is_front_page() ? 'class="active"' : '') . '>Home</a></li>';
|
||||
echo '<li><a href="' . esc_url(home_url('/contact')) . '" ' . (is_page('contact') ? 'class="active"' : '') . '>Contact us</a></li>';
|
||||
echo '<li><a href="' . esc_url(home_url('/blog')) . '" ' . (is_page('blog') ? 'class="active"' : '') . '>Blog</a></li>';
|
||||
echo '</ul>';
|
||||
}
|
||||
?>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="main-content">
|
||||
<a href="<?php echo esc_url(home_url('/inquiry/')); ?>" class="floating-inquiry-btn">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
Custom Journey Inquiry
|
||||
</a>
|
||||
</div>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 664 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 627 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 808 KiB |
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
// Vključi WordPress
|
||||
require_once('../../../../wp-load.php');
|
||||
|
||||
// Preberi JSON datoteko
|
||||
$json_file = '../../../Spletna/tours.json';
|
||||
$json_data = file_get_contents($json_file);
|
||||
$tours = json_decode($json_data, true);
|
||||
|
||||
if (!is_array($tours)) {
|
||||
die('Napaka pri branju JSON datoteke');
|
||||
}
|
||||
|
||||
// Uvozi vsako turo
|
||||
foreach ($tours as $tour) {
|
||||
// Preveri, če tura že obstaja
|
||||
$existing_tour = get_page_by_title($tour['title'], OBJECT, 'tour');
|
||||
|
||||
if ($existing_tour) {
|
||||
continue; // Preskoči, če tura že obstaja
|
||||
}
|
||||
|
||||
// Pripravi podatke za novo turo
|
||||
$post_data = array(
|
||||
'post_title' => $tour['title'],
|
||||
'post_content' => $tour['description'] ?? '',
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'tour'
|
||||
);
|
||||
|
||||
// Vstavi novo turo
|
||||
$post_id = wp_insert_post($post_data);
|
||||
|
||||
if ($post_id) {
|
||||
// Dodaj meta podatke
|
||||
update_post_meta($post_id, '_tour_duration', $tour['duration'] ?? '');
|
||||
update_post_meta($post_id, '_tour_distance', $tour['distance'] ?? '');
|
||||
update_post_meta($post_id, '_tour_category', $tour['category'] ?? '');
|
||||
|
||||
// Dodaj highlights in inclusions, če obstajajo
|
||||
if (isset($tour['highlights'])) {
|
||||
update_post_meta($post_id, '_tour_highlights', $tour['highlights']);
|
||||
}
|
||||
if (isset($tour['inclusions'])) {
|
||||
update_post_meta($post_id, '_tour_inclusions', $tour['inclusions']);
|
||||
}
|
||||
|
||||
// Nastavi featured image, če obstaja
|
||||
if (isset($tour['image']) && !empty($tour['image'])) {
|
||||
$image_url = $tour['image'];
|
||||
$upload_dir = wp_upload_dir();
|
||||
|
||||
// Prenesi sliko
|
||||
$image_data = file_get_contents($image_url);
|
||||
$filename = basename($image_url);
|
||||
|
||||
if ($image_data !== false) {
|
||||
$file = $upload_dir['path'] . '/' . $filename;
|
||||
file_put_contents($file, $image_data);
|
||||
|
||||
$wp_filetype = wp_check_filetype($filename, null);
|
||||
$attachment = array(
|
||||
'post_mime_type' => $wp_filetype['type'],
|
||||
'post_title' => sanitize_file_name($filename),
|
||||
'post_content' => '',
|
||||
'post_status' => 'inherit'
|
||||
);
|
||||
|
||||
$attach_id = wp_insert_attachment($attachment, $file, $post_id);
|
||||
require_once(ABSPATH . 'wp-admin/includes/image.php');
|
||||
|
||||
$attach_data = wp_generate_attachment_metadata($attach_id, $file);
|
||||
wp_update_attachment_metadata($attach_id, $attach_data);
|
||||
set_post_thumbnail($post_id, $attach_id);
|
||||
}
|
||||
}
|
||||
|
||||
echo "Uvožena tura: " . $tour['title'] . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "Uvoz končan!\n";
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php get_header(); ?>
|
||||
|
||||
<main class="site-main">
|
||||
<div class="container">
|
||||
<?php
|
||||
if (have_posts()) :
|
||||
while (have_posts()) : the_post();
|
||||
get_template_part('template-parts/content', get_post_type());
|
||||
endwhile;
|
||||
|
||||
the_posts_navigation();
|
||||
else :
|
||||
get_template_part('template-parts/content', 'none');
|
||||
endif;
|
||||
?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
(function($) {
|
||||
'use strict';
|
||||
|
||||
// Posodobi predogled, ko se spremeni vrstni red Experience Journeyev
|
||||
wp.customize('experience_journey_order', function(setting) {
|
||||
setting.bind(function(newValue) {
|
||||
wp.customize.previewer.refresh();
|
||||
});
|
||||
});
|
||||
|
||||
// Posodobi predogled, ko se spremeni vrstni red Individual Tours v katerem koli Journey
|
||||
$('option[id^="individual_tour_order_"]').each(function() {
|
||||
var settingId = $(this).attr('id');
|
||||
wp.customize(settingId, function(setting) {
|
||||
setting.bind(function(newValue) {
|
||||
wp.customize.previewer.refresh();
|
||||
});
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
(function($) {
|
||||
'use strict';
|
||||
|
||||
wp.customize.bind('ready', function() {
|
||||
$('.sortable-posts-list').sortable({
|
||||
update: function(event, ui) {
|
||||
var $list = $(this);
|
||||
var postIds = [];
|
||||
$list.find('li').each(function() {
|
||||
postIds.push($(this).data('post-id'));
|
||||
});
|
||||
var $input = $list.closest('.customize-control').find('input[type="hidden"]');
|
||||
$input.val(postIds.join(',')).trigger('change');
|
||||
}
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const menuToggle = document.getElementById('menuToggle');
|
||||
const mainNav = document.querySelector('.main-nav');
|
||||
const body = document.body;
|
||||
|
||||
// Ustvari overlay element
|
||||
let overlay = document.querySelector('.menu-overlay');
|
||||
if (!overlay) {
|
||||
overlay = document.createElement('div');
|
||||
overlay.className = 'menu-overlay';
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
if (menuToggle && mainNav) {
|
||||
menuToggle.addEventListener('click', function() {
|
||||
mainNav.classList.toggle('active');
|
||||
overlay.classList.toggle('active');
|
||||
body.classList.toggle('menu-open');
|
||||
menuToggle.classList.toggle('active');
|
||||
|
||||
// Dodaj aria-expanded za dostopnost
|
||||
const isExpanded = mainNav.classList.contains('active');
|
||||
menuToggle.setAttribute('aria-expanded', isExpanded);
|
||||
|
||||
// Eksplicitno nastavi display stil
|
||||
mainNav.style.display = isExpanded ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Zapri meni ob kliku na overlay
|
||||
overlay.addEventListener('click', function() {
|
||||
mainNav.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
body.classList.remove('menu-open');
|
||||
menuToggle.classList.remove('active');
|
||||
menuToggle.setAttribute('aria-expanded', 'false');
|
||||
mainNav.style.display = 'none';
|
||||
});
|
||||
|
||||
// Zapri meni ob kliku na povezavo v meniju
|
||||
const menuLinks = mainNav.querySelectorAll('a');
|
||||
menuLinks.forEach(link => {
|
||||
link.addEventListener('click', function() {
|
||||
mainNav.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
body.classList.remove('menu-open');
|
||||
menuToggle.classList.remove('active');
|
||||
menuToggle.setAttribute('aria-expanded', 'false');
|
||||
mainNav.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Zapri meni ob resize-u okna
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth > 768) {
|
||||
mainNav.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
body.classList.remove('menu-open');
|
||||
menuToggle.classList.remove('active');
|
||||
menuToggle.setAttribute('aria-expanded', 'false');
|
||||
mainNav.style.removeProperty('display');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (!localStorage.getItem('popupShown')) {
|
||||
setTimeout(function() {
|
||||
document.getElementById('email-popup').style.display = 'flex';
|
||||
}, 2000); // Show popup after 2 seconds
|
||||
}
|
||||
|
||||
// Dodaj poslušalca za promocijsko kodo v popupu
|
||||
const popupPromoInput = document.getElementById('popup-promo-code');
|
||||
const popupApplyButton = document.getElementById('popup-apply-promo');
|
||||
const popupPromoMessage = document.getElementById('popup-promo-message');
|
||||
let promoCodeApplied = false;
|
||||
|
||||
if (popupPromoInput && popupApplyButton) {
|
||||
popupApplyButton.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const promoCode = popupPromoInput.value.trim();
|
||||
|
||||
if (promoCode === 'HOLIDAYS25' && !promoCodeApplied) {
|
||||
// Pridobi trenutni znesek
|
||||
const totalElement = document.getElementById('popupTotalPrice');
|
||||
if (totalElement) {
|
||||
const currentAmount = parseFloat(totalElement.textContent);
|
||||
const newAmount = currentAmount - 100;
|
||||
|
||||
// Posodobi prikaz
|
||||
totalElement.textContent = newAmount.toFixed(2);
|
||||
|
||||
// Posodobi tudi zneske za delno plačilo
|
||||
const fullPaymentAmount = document.getElementById('full-payment-amount');
|
||||
const depositAmount = document.getElementById('deposit-amount');
|
||||
if (fullPaymentAmount) {
|
||||
fullPaymentAmount.textContent = newAmount.toFixed(2);
|
||||
}
|
||||
if (depositAmount) {
|
||||
depositAmount.textContent = (newAmount * 0.3).toFixed(2);
|
||||
}
|
||||
|
||||
// Dodaj sporočilo o uspehu
|
||||
popupPromoMessage.innerHTML = '<p class="promo-success">Promotional code successfully applied! We deducted €100.</p>';
|
||||
|
||||
// Onemogoči nadaljnje vnose
|
||||
popupPromoInput.disabled = true;
|
||||
popupApplyButton.disabled = true;
|
||||
promoCodeApplied = true;
|
||||
}
|
||||
} else {
|
||||
// Prikaži napako
|
||||
popupPromoMessage.innerHTML = '<p class="promo-error">Neveljavna promocijska koda.</p>';
|
||||
|
||||
// Odstrani sporočilo o napaki po 3 sekundah
|
||||
setTimeout(() => {
|
||||
popupPromoMessage.innerHTML = '';
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Pridobi podatke o vodiču
|
||||
const guideImage = document.getElementById('guide-image-data')?.getAttribute('data-guide-image');
|
||||
const guideName = document.getElementById('guide-image-data')?.getAttribute('data-guide-name');
|
||||
const guideTitle = document.getElementById('guide-image-data')?.getAttribute('data-guide-title');
|
||||
|
||||
// Posodobi HTML v obrazcu
|
||||
if (guideImage && guideName) {
|
||||
const guideContainer = document.querySelector('.guide-info-container');
|
||||
if (guideContainer) {
|
||||
guideContainer.innerHTML = `
|
||||
<div class="guide-info">
|
||||
<div class="guide-left">
|
||||
<div class="guide-image-container">
|
||||
<img src="${guideImage}" alt="${guideName}" class="guide-image">
|
||||
<h4 class="guide-name">${guideName}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="guide-contact">
|
||||
<a href="mailto:info@europewonder.com" class="contact-item">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<span>info@europewonder.com</span>
|
||||
</a>
|
||||
<a href="tel:+38671548893" class="contact-item">
|
||||
<i class="fas fa-phone"></i>
|
||||
<span>+386 (0) 71 548 893</span>
|
||||
</a>
|
||||
<a href="https://wa.me/38671548893" class="contact-item" target="_blank">
|
||||
<i class="fab fa-whatsapp"></i>
|
||||
<span>WhatsApp chat</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function closePopup() {
|
||||
document.getElementById('email-popup').style.display = 'none';
|
||||
localStorage.setItem('popupShown', 'true');
|
||||
}
|
||||
|
||||
async function submitEmail(event) {
|
||||
event.preventDefault();
|
||||
const email = document.getElementById('popup-email').value;
|
||||
|
||||
try {
|
||||
const response = await fetch('/wp-admin/admin-ajax.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `action=save_popup_email&email=${encodeURIComponent(email)}`
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
document.getElementById('success-message').style.display = 'block';
|
||||
document.getElementById('email-form').style.display = 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// ---------- MOBILNI MENI ----------
|
||||
const mainNav = document.querySelector('.main-nav');
|
||||
const body = document.body;
|
||||
|
||||
// Ustvari overlay element
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'menu-overlay';
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
if (menuToggle && mainNav) {
|
||||
menuToggle.addEventListener('click', function() {
|
||||
menuToggle.classList.toggle('active');
|
||||
mainNav.classList.toggle('active');
|
||||
overlay.classList.toggle('active');
|
||||
body.classList.toggle('menu-open');
|
||||
});
|
||||
|
||||
// Zapri meni ob kliku na overlay
|
||||
overlay.addEventListener('click', function() {
|
||||
menuToggle.classList.remove('active');
|
||||
mainNav.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
body.classList.remove('menu-open');
|
||||
});
|
||||
|
||||
// Zapri meni ob kliku na povezavo
|
||||
mainNav.querySelectorAll('a').forEach(link => {
|
||||
link.addEventListener('click', function() {
|
||||
menuToggle.classList.remove('active');
|
||||
mainNav.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
body.classList.remove('menu-open');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Zapri meni ob resize-u okna
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth > 768) {
|
||||
menuToggle.classList.remove('active');
|
||||
mainNav.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
body.classList.remove('menu-open');
|
||||
}
|
||||
});
|
||||
|
||||
// ---------- PLAVAJOČI GUMB ZA POVPRAŠEVANJE ----------
|
||||
// Ustvarimo nov HTML element za plavajoči gumb
|
||||
const floatingButton = document.createElement('a');
|
||||
floatingButton.className = 'floating-inquiry-btn';
|
||||
floatingButton.href = '/inquiry/';
|
||||
floatingButton.innerHTML = '<i class="fas fa-comment-alt"></i> Custom Journey Inquiry';
|
||||
|
||||
// Dodamo gumb v body
|
||||
document.body.appendChild(floatingButton);
|
||||
|
||||
// Če obstaja popup element na strani, bomo uporabili ta obstoječi element
|
||||
let inquiryPopup = document.getElementById('inquiry-popup');
|
||||
|
||||
// Če popup ne obstaja na trenutni strani, ga ustvarimo
|
||||
if (!inquiryPopup) {
|
||||
// Ustvarimo nov popup
|
||||
inquiryPopup = document.createElement('div');
|
||||
inquiryPopup.id = 'inquiry-popup';
|
||||
inquiryPopup.className = 'inquiry-popup';
|
||||
|
||||
// Vsebina popupa
|
||||
inquiryPopup.innerHTML = `
|
||||
<div class="inquiry-popup-content">
|
||||
<span class="inquiry-close">×</span>
|
||||
<h3>Custom Journey Inquiry</h3>
|
||||
<form id="custom-inquiry-form" class="inquiry-form">
|
||||
<input type="hidden" name="action" value="custom_inquiry_submit">
|
||||
<input type="hidden" name="inquiry_security" id="inquiry_security" value="">
|
||||
<div class="form-group">
|
||||
<label for="name">Your Name</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group half">
|
||||
<label for="travel-date">Planned Travel Date</label>
|
||||
<input type="date" id="travel-date" name="travel_date">
|
||||
</div>
|
||||
<div class="form-group half">
|
||||
<label for="travelers">Number of Travelers</label>
|
||||
<input type="number" id="travelers" name="travelers" min="1" value="2">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination">Preferred Destination(s)</label>
|
||||
<input type="text" id="destination" name="destination" placeholder="e.g. Slovenia, Croatia, Hungary...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="message">Your Travel Wishes</label>
|
||||
<textarea id="message" name="message" rows="4" placeholder="Tell us what you're looking for in your custom journey..."></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn-submit">Submit Inquiry</button>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Dodamo style, če ga še ni
|
||||
if (!document.getElementById('inquiry-popup-styles')) {
|
||||
const popupStyles = document.createElement('style');
|
||||
popupStyles.id = 'inquiry-popup-styles';
|
||||
popupStyles.textContent = `
|
||||
/* Popup Styles */
|
||||
.inquiry-popup {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.inquiry-popup.active {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.inquiry-popup-content {
|
||||
background-color: white;
|
||||
margin: 5% auto;
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(-20px);
|
||||
transition: transform 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inquiry-popup.active .inquiry-popup-content {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.inquiry-close {
|
||||
position: absolute;
|
||||
top: 1.2rem;
|
||||
right: 1.5rem;
|
||||
font-size: 2rem;
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.inquiry-close:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.inquiry-popup h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--dark);
|
||||
font-size: 1.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inquiry-form .form-group {
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.inquiry-form .half {
|
||||
width: 49%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.inquiry-form .form-group:nth-child(3) {
|
||||
margin-right: 2%;
|
||||
}
|
||||
|
||||
.inquiry-form label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #555;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.inquiry-form input,
|
||||
.inquiry-form textarea {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.inquiry-form input:focus,
|
||||
.inquiry-form textarea:focus {
|
||||
border-color: var(--accent);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.inquiry-form .btn-submit {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.3s ease;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.inquiry-form .btn-submit:hover {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
body.popup-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.inquiry-popup-content {
|
||||
padding: 1.5rem;
|
||||
margin: 10% auto;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.inquiry-form .half {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.inquiry-form .form-group:nth-child(3) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(popupStyles);
|
||||
}
|
||||
|
||||
// Dodamo popup v body
|
||||
document.body.appendChild(inquiryPopup);
|
||||
|
||||
// Pridobimo varnostni nonce preko AJAX klica
|
||||
fetch(inquiry_ajax_object.ajax_url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'action=get_inquiry_nonce'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('inquiry_security').value = data.data;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching nonce:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Poskrbimo, da je popup začetno skrit
|
||||
inquiryPopup.classList.remove('active');
|
||||
document.body.classList.remove('popup-open');
|
||||
|
||||
// Dodamo "click" poslušalec za odpiranje popupa
|
||||
floatingButton.addEventListener('click', function() {
|
||||
inquiryPopup.classList.add('active');
|
||||
document.body.classList.add('popup-open');
|
||||
});
|
||||
|
||||
// Pridobimo referenco na elemente v popupu
|
||||
const inquiryClose = inquiryPopup.querySelector('.inquiry-close');
|
||||
const inquiryForm = inquiryPopup.querySelector('#custom-inquiry-form');
|
||||
|
||||
// Zapri popup (X gumb)
|
||||
inquiryClose.addEventListener('click', function() {
|
||||
inquiryPopup.classList.remove('active');
|
||||
document.body.classList.remove('popup-open');
|
||||
});
|
||||
|
||||
// Zapri popup (klik izven obrazca)
|
||||
inquiryPopup.addEventListener('click', function(e) {
|
||||
if (e.target === inquiryPopup) {
|
||||
inquiryPopup.classList.remove('active');
|
||||
document.body.classList.remove('popup-open');
|
||||
}
|
||||
});
|
||||
|
||||
// Zapri popup (tipka ESC)
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && inquiryPopup.classList.contains('active')) {
|
||||
inquiryPopup.classList.remove('active');
|
||||
document.body.classList.remove('popup-open');
|
||||
}
|
||||
});
|
||||
|
||||
// Pošlji obrazec
|
||||
inquiryForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Prikaži indikator nalaganja
|
||||
const submitBtn = inquiryForm.querySelector('.btn-submit');
|
||||
const originalBtnText = submitBtn.textContent;
|
||||
submitBtn.textContent = 'Sending...';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
// Pridobi podatke iz obrazca
|
||||
const formData = new FormData(inquiryForm);
|
||||
const nonce = document.getElementById('inquiry_security').value;
|
||||
formData.append('security', nonce);
|
||||
|
||||
console.log('Sending form data to AJAX endpoint...');
|
||||
|
||||
// Pošlji AJAX zahtevek
|
||||
fetch(inquiry_ajax_object.ajax_url, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Response status:', response.status);
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Response data:', data);
|
||||
|
||||
// Ponastavi gumb
|
||||
submitBtn.textContent = originalBtnText;
|
||||
submitBtn.disabled = false;
|
||||
|
||||
if (data.success) {
|
||||
// Uspešno poslano
|
||||
alert(data.data);
|
||||
inquiryForm.reset();
|
||||
inquiryPopup.classList.remove('active');
|
||||
document.body.classList.remove('popup-open');
|
||||
} else {
|
||||
// Napaka
|
||||
console.error('Form submission error:', data);
|
||||
alert(data.data || 'There was an error sending your inquiry. Please try again.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Ponastavi gumb
|
||||
submitBtn.textContent = originalBtnText;
|
||||
submitBtn.disabled = false;
|
||||
|
||||
console.error('Error during form submission:', error);
|
||||
alert('There was an error sending your inquiry: ' + error.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
/**
|
||||
* Customizer za sortiranje Experience Journeyev
|
||||
*/
|
||||
|
||||
// Dodaj Customizer section in control
|
||||
function grilctours_customize_journey_order($wp_customize) {
|
||||
// Add section
|
||||
$wp_customize->add_section('experience_journey_order_section', array(
|
||||
'title' => __('Experience Journey Order', 'grilctours'),
|
||||
'priority' => 200,
|
||||
));
|
||||
|
||||
// Add setting
|
||||
$wp_customize->add_setting('experience_journey_order', array(
|
||||
'default' => '',
|
||||
'sanitize_callback' => 'sanitize_experience_journey_order',
|
||||
'transport' => 'refresh',
|
||||
));
|
||||
|
||||
// Add control
|
||||
$wp_customize->add_control(new WP_Customize_Sortable_Posts_Control($wp_customize, 'experience_journey_order', array(
|
||||
'section' => 'experience_journey_order_section',
|
||||
'label' => __('Drag & Drop to Reorder', 'grilctours'),
|
||||
'description' => __('Arrange the order of Experience Journeys on the homepage.', 'grilctours'),
|
||||
)));
|
||||
}
|
||||
add_action('customize_register', 'grilctours_customize_journey_order');
|
||||
|
||||
// Sanitizacijska funkcija za vrstni red
|
||||
function sanitize_experience_journey_order($input) {
|
||||
$order = explode(',', $input);
|
||||
$sanitized = array();
|
||||
foreach ($order as $post_id) {
|
||||
$post_id = absint($post_id);
|
||||
if ($post_id && get_post_status($post_id)) {
|
||||
$sanitized[] = $post_id;
|
||||
}
|
||||
}
|
||||
return implode(',', $sanitized);
|
||||
}
|
||||
|
||||
// Prilagodljiv control class
|
||||
class WP_Customize_Sortable_Posts_Control extends WP_Customize_Control {
|
||||
public $type = 'sortable_posts';
|
||||
public $post_type = 'experience_journey';
|
||||
|
||||
public function render_content() {
|
||||
$saved_order = (array) $this->value();
|
||||
$all_posts = get_posts(array(
|
||||
'post_type' => $this->post_type,
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'publish',
|
||||
'fields' => 'ids',
|
||||
));
|
||||
|
||||
// Split into saved and new posts
|
||||
$ordered_posts = array();
|
||||
$new_posts = array();
|
||||
|
||||
// Keep valid saved posts
|
||||
foreach ($saved_order as $post_id) {
|
||||
if (in_array($post_id, $all_posts)) {
|
||||
$ordered_posts[] = $post_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new posts not in saved order
|
||||
foreach ($all_posts as $post_id) {
|
||||
if (!in_array($post_id, $ordered_posts)) {
|
||||
$new_posts[] = $post_id;
|
||||
}
|
||||
}
|
||||
|
||||
$ordered_posts = array_merge($ordered_posts, $new_posts);
|
||||
?>
|
||||
<label>
|
||||
<span class="customize-control-title"><?php echo esc_html($this->label); ?></span>
|
||||
<span class="description customize-control-description"><?php echo esc_html($this->description); ?></span>
|
||||
</label>
|
||||
<ul class="sortable-posts-list">
|
||||
<?php foreach ($ordered_posts as $post_id) :
|
||||
$post = get_post($post_id);
|
||||
if (!$post) continue;
|
||||
?>
|
||||
<li data-post-id="<?php echo esc_attr($post_id); ?>">
|
||||
<span class="dashicons dashicons-menu"></span>
|
||||
<?php echo esc_html($post->post_title); ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<input type="hidden" <?php $this->link(); ?> value="<?php echo esc_attr(implode(',', $ordered_posts)); ?>" />
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue scripts
|
||||
function grilctours_customize_controls_enqueue_scripts() {
|
||||
wp_enqueue_script('grilctours-customizer-sortable', get_template_directory_uri() . '/js/customizer-sortable.js', array('jquery', 'jquery-ui-sortable', 'customize-controls'), '', true);
|
||||
wp_enqueue_style('grilctours-customizer-sortable', get_template_directory_uri() . '/css/customizer-sortable.css');
|
||||
}
|
||||
add_action('customize_controls_enqueue_scripts', 'grilctours_customize_controls_enqueue_scripts');
|
||||
|
||||
// Preview script
|
||||
function grilctours_customize_preview_js() {
|
||||
wp_enqueue_script('grilctours-customizer-preview', get_template_directory_uri() . '/js/customizer-preview.js', array('customize-preview'), '', true);
|
||||
}
|
||||
add_action('customize_preview_init', 'grilctours_customize_preview_js');
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
<?php
|
||||
/**
|
||||
* Template Name: About Page
|
||||
*/
|
||||
|
||||
get_header();
|
||||
?>
|
||||
|
||||
<!-- Hero section -->
|
||||
<section class="page-hero">
|
||||
<div class="hero-content">
|
||||
<h1><?php the_title(); ?></h1>
|
||||
<p><?php echo esc_html(get_theme_mod('about_subtitle', 'Get to know our passionate team')); ?></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Team section -->
|
||||
<section class="team-section">
|
||||
<div class="container">
|
||||
<div class="team-grid">
|
||||
<!-- Nejc -->
|
||||
<div class="team-member">
|
||||
<div class="member-image">
|
||||
<img src="<?php echo esc_url(get_theme_mod('team_nejc_image', get_template_directory_uri() . '/images/nejc.webp')); ?>"
|
||||
alt="<?php echo esc_attr(get_theme_mod('team_nejc_name', 'Nejc Dovžan Kukič')); ?>">
|
||||
</div>
|
||||
<div class="member-info">
|
||||
<h2 class="member-name"><?php echo esc_html(get_theme_mod('team_nejc_name', 'Nejc Dovžan Kukič')); ?></h2>
|
||||
<h3 class="member-position"><?php echo esc_html(get_theme_mod('team_nejc_position', 'CEO')); ?></h3>
|
||||
<div class="member-bio">
|
||||
<p><?php echo wp_kses_post(get_theme_mod('team_nejc_bio', 'A visionary leader with a passion for innovation and growth. Nejc drives the company\'s strategy with precision and purpose — when not in the office, you\'ll find him exploring mountain trails.')); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Matic -->
|
||||
<div class="team-member">
|
||||
<div class="member-image">
|
||||
<img src="<?php echo esc_url(get_theme_mod('team_matic_image', get_template_directory_uri() . '/images/matic.webp')); ?>"
|
||||
alt="<?php echo esc_attr(get_theme_mod('team_matic_name', 'Matic Snoj')); ?>">
|
||||
</div>
|
||||
<div class="member-info">
|
||||
<h2 class="member-name"><?php echo esc_html(get_theme_mod('team_matic_name', 'Matic Snoj')); ?></h2>
|
||||
<h3 class="member-position"><?php echo esc_html(get_theme_mod('team_matic_position', 'COO')); ?></h3>
|
||||
<div class="member-bio">
|
||||
<p><?php echo wp_kses_post(get_theme_mod('team_matic_bio', 'As the backbone of daily operations, Matic ensures everything runs smoothly behind the scenes. He\'s careful and detail-oriented and brings the same dedication to cycling in his free time.')); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Luka -->
|
||||
<div class="team-member">
|
||||
<div class="member-image">
|
||||
<img src="<?php echo esc_url(get_theme_mod('team_luka_image', get_template_directory_uri() . '/images/luka.webp')); ?>"
|
||||
alt="<?php echo esc_attr(get_theme_mod('team_luka_name', 'Luka Dovžan Kukič')); ?>">
|
||||
</div>
|
||||
<div class="member-info">
|
||||
<h2 class="member-name"><?php echo esc_html(get_theme_mod('team_luka_name', 'Luka Dovžan Kukič')); ?></h2>
|
||||
<h3 class="member-position"><?php echo esc_html(get_theme_mod('team_luka_position', 'CFO')); ?></h3>
|
||||
<div class="member-bio">
|
||||
<p><?php echo wp_kses_post(get_theme_mod('team_luka_bio', 'Luka manages the company\'s financial strategy with focus and discipline. Reliable and analytical, he brings the same energy to both numbers and his love for cycling.')); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Jernej -->
|
||||
<div class="team-member">
|
||||
<div class="member-image">
|
||||
<img src="<?php echo esc_url(get_theme_mod('team_jernej_image', get_template_directory_uri() . '/images/jernej.webp')); ?>"
|
||||
alt="<?php echo esc_attr(get_theme_mod('team_jernej_name', 'Jernej Antloga')); ?>">
|
||||
</div>
|
||||
<div class="member-info">
|
||||
<h2 class="member-name"><?php echo esc_html(get_theme_mod('team_jernej_name', 'Jernej Antloga')); ?></h2>
|
||||
<h3 class="member-position"><?php echo esc_html(get_theme_mod('team_jernej_position', 'Travel specialist')); ?></h3>
|
||||
<div class="member-bio">
|
||||
<p><?php echo wp_kses_post(get_theme_mod('team_jernej_bio', 'Friendly and knowledgeable, Jernej designs personalized trips with energy and enthusiasm. A history enthusiast, he brings stories to life through travel.')); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kevin -->
|
||||
<div class="team-member">
|
||||
<div class="member-image">
|
||||
<img src="<?php echo esc_url(get_theme_mod('team_kevin_image', get_template_directory_uri() . '/images/Kevin.webp')); ?>"
|
||||
alt="<?php echo esc_attr(get_theme_mod('team_kevin_name', 'Kevin Krajnc')); ?>">
|
||||
</div>
|
||||
<div class="member-info">
|
||||
<h2 class="member-name"><?php echo esc_html(get_theme_mod('team_kevin_name', 'Kevin Krajnc')); ?></h2>
|
||||
<h3 class="member-position"><?php echo esc_html(get_theme_mod('team_kevin_position', 'Travel specialist')); ?></h3>
|
||||
<div class="member-bio">
|
||||
<p><?php echo wp_kses_post(get_theme_mod('team_kevin_bio', 'Creative and detail-oriented, Kevin helps clients plan seamless, custom journeys. He\'s a road trip specialist known for his helpful nature and great communication skills.')); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* About Page Styles */
|
||||
.page-hero {
|
||||
height: 40vh;
|
||||
background: linear-gradient(to top,
|
||||
rgba(0, 0, 0, 0.7) 0%,
|
||||
rgba(0, 0, 0, 0.4) 30%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
), var(--accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.page-hero .hero-content {
|
||||
padding: 2rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-hero h1 {
|
||||
font-size: 3.5rem;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.page-hero p {
|
||||
font-size: 1.3rem;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.team-section {
|
||||
padding: 80px 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.team-grid {
|
||||
display: grid;
|
||||
gap: 60px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.team-member {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.team-member:nth-child(even) {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.member-image {
|
||||
flex: 0 0 300px;
|
||||
}
|
||||
|
||||
.member-image img {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
object-fit: cover;
|
||||
border-radius: 10px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.member-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
font-size: 32px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.member-position {
|
||||
font-size: 20px;
|
||||
color: #666;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.member-bio {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-hero h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.page-hero p {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.team-member,
|
||||
.team-member:nth-child(even) {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.member-image {
|
||||
flex: 0 0 auto;
|
||||
max-width: 250px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
<?php
|
||||
/**
|
||||
* Template Name: Blog
|
||||
*/
|
||||
|
||||
get_header(); ?>
|
||||
|
||||
<!-- Hero section -->
|
||||
<section class="page-hero">
|
||||
<div class="hero-content">
|
||||
<h1><?php the_title(); ?></h1>
|
||||
<p>Discover travel stories, tips, and insights</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Blog section -->
|
||||
<section class="blog">
|
||||
<div class="container">
|
||||
<div class="blog-grid">
|
||||
<?php
|
||||
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
|
||||
$args = array(
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 9,
|
||||
'paged' => $paged
|
||||
);
|
||||
|
||||
$blog_query = new WP_Query($args);
|
||||
|
||||
if ($blog_query->have_posts()) :
|
||||
while ($blog_query->have_posts()) : $blog_query->the_post();
|
||||
$image = get_the_post_thumbnail_url(get_the_ID(), 'large');
|
||||
if (!$image) {
|
||||
$image = get_theme_file_uri('images/placeholder.jpg');
|
||||
}
|
||||
?>
|
||||
<article class="blog-card">
|
||||
<div class="blog-image">
|
||||
<img src="<?php echo esc_url($image); ?>"
|
||||
alt="<?php echo esc_attr(get_the_title()); ?>"
|
||||
loading="lazy">
|
||||
</div>
|
||||
<div class="blog-content">
|
||||
<div class="blog-meta">
|
||||
<span class="date">
|
||||
<i class="far fa-calendar"></i>
|
||||
<?php echo get_the_date('M j, Y'); ?>
|
||||
</span>
|
||||
<?php
|
||||
$categories = get_the_category();
|
||||
if ($categories) :
|
||||
?>
|
||||
<span class="category">
|
||||
<i class="far fa-folder"></i>
|
||||
<?php echo esc_html($categories[0]->name); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<h2><?php the_title(); ?></h2>
|
||||
<p><?php echo wp_trim_words(get_the_excerpt(), 20); ?></p>
|
||||
<a href="<?php the_permalink(); ?>" class="btn-read">Read More →</a>
|
||||
</div>
|
||||
</article>
|
||||
<?php
|
||||
endwhile;
|
||||
|
||||
// Pagination
|
||||
echo '<div class="pagination">';
|
||||
echo paginate_links(array(
|
||||
'total' => $blog_query->max_num_pages,
|
||||
'current' => $paged,
|
||||
'prev_text' => '←',
|
||||
'next_text' => '→'
|
||||
));
|
||||
echo '</div>';
|
||||
|
||||
wp_reset_postdata();
|
||||
else :
|
||||
?>
|
||||
<div class="no-posts">
|
||||
<p>No blog posts found.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* Blog Page Styles */
|
||||
.blog {
|
||||
padding: 4rem 0;
|
||||
}
|
||||
|
||||
.blog-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.blog-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.blog-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.blog-image {
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.blog-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.blog-card:hover .blog-image img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.blog-content {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.blog-meta {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.blog-meta span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.blog-meta i {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.blog-card h2 {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--dark);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.blog-card p {
|
||||
color: #666;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-read {
|
||||
display: inline-block;
|
||||
padding: 0.8rem 1.5rem;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s ease;
|
||||
margin-top: auto;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.btn-read:hover {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.pagination .page-numbers {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
color: var(--dark);
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.pagination .page-numbers.current {
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.pagination .page-numbers:hover:not(.current) {
|
||||
background-color: #f5f5f5;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.no-posts {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.blog-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.blog-card h2 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.blog-image {
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
/**
|
||||
* Template Name: Contact Page
|
||||
* Template Post Type: page
|
||||
*/
|
||||
get_header();
|
||||
?>
|
||||
|
||||
<!-- Hero section -->
|
||||
<section class="page-hero">
|
||||
<div class="hero-content">
|
||||
<h1><?php the_title(); ?></h1>
|
||||
<p>Let's plan your next adventure together</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact section -->
|
||||
<section class="contact">
|
||||
<div class="container">
|
||||
<div class="contact-grid">
|
||||
<div class="contact-info">
|
||||
<h2>Get in Touch</h2>
|
||||
<div class="info-item">
|
||||
<i class="fas fa-map-marker-alt"></i>
|
||||
<p>123 Travel Street, Adventure City</p>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<i class="fas fa-phone"></i>
|
||||
<p>+386 41 444 290</p>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<p>info@grilctours.com</p>
|
||||
</div>
|
||||
<div class="social-links">
|
||||
<a href="#"><i class="fab fa-facebook"></i></a>
|
||||
<a href="#"><i class="fab fa-instagram"></i></a>
|
||||
<a href="#"><i class="fab fa-twitter"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contact-form">
|
||||
<?php echo do_shortcode('[contact-form-7 id="contact-form" title="Contact Form"]'); ?>
|
||||
|
||||
<!-- Fallback form if Contact Form 7 is not installed -->
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="subject">Subject</label>
|
||||
<input type="text" id="subject" name="subject" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="message">Message</label>
|
||||
<textarea id="message" name="message" rows="5" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn-submit">Send Message</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
<?php
|
||||
/**
|
||||
* Template Name: Contact Page
|
||||
*/
|
||||
|
||||
get_header(); ?>
|
||||
|
||||
<!-- Hero section -->
|
||||
<section class="page-hero">
|
||||
<div class="hero-content">
|
||||
<h1><?php the_title(); ?></h1>
|
||||
<p>Let's plan your next adventure together</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact section -->
|
||||
<section class="contact">
|
||||
<div class="container">
|
||||
<div class="contact-grid">
|
||||
<div class="contact-info">
|
||||
<h2>Get in Touch</h2>
|
||||
<div class="info-item">
|
||||
<i class="fas fa-map-marker-alt"></i>
|
||||
<p>Ob Farjevcu 50, 1000 Ljubljana, Slovenia</p>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<i class="fas fa-phone"></i>
|
||||
<p>+386 (0) 31 332 823</p>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<p>info@europewonder.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contact-form">
|
||||
<form id="contact-form">
|
||||
<?php wp_nonce_field('contact_form_nonce', 'contact_security'); ?>
|
||||
<input type="hidden" name="action" value="contact_form_submit">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="subject">Subject</label>
|
||||
<input type="text" id="subject" name="subject" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="message">Message</label>
|
||||
<textarea id="message" name="message" rows="5" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn-submit">Send Message</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* Contact Page Styles */
|
||||
.page-hero {
|
||||
height: 40vh;
|
||||
background: linear-gradient(to top,
|
||||
rgba(0, 0, 0, 0.7) 0%,
|
||||
rgba(0, 0, 0, 0.4) 30%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
), var(--accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.page-hero .hero-content {
|
||||
padding: 2rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-hero h1 {
|
||||
font-size: 3.5rem;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.page-hero p {
|
||||
font-size: 1.3rem;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.contact {
|
||||
padding: 4rem 0;
|
||||
}
|
||||
|
||||
.contact-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 4rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.contact-info h2 {
|
||||
color: var(--dark);
|
||||
font-size: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.info-item i {
|
||||
color: var(--accent);
|
||||
font-size: 1.5rem;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info-item p {
|
||||
color: #444;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.contact-form {
|
||||
background: white;
|
||||
padding: 2.5rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus {
|
||||
border-color: var(--accent);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(0, 121, 140, 0.1);
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 1rem 2rem;
|
||||
font-size: 1.1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-submit:hover {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.contact-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.page-hero h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.page-hero p {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.contact-info h2 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.contact-form {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.getElementById('contact-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Prikaži indikator nalaganja
|
||||
const submitBtn = this.querySelector('.btn-submit');
|
||||
const originalBtnText = submitBtn.textContent;
|
||||
submitBtn.textContent = 'Sending...';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
// Pridobi podatke iz obrazca
|
||||
const formData = new FormData(this);
|
||||
formData.append('security', document.querySelector('#contact_security').value);
|
||||
|
||||
console.log('Sending contact form data to AJAX endpoint...');
|
||||
|
||||
// Pošlji AJAX zahtevek
|
||||
fetch(<?php echo json_encode(admin_url('admin-ajax.php')); ?>, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Response status:', response.status);
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Response data:', data);
|
||||
|
||||
// Ponastavi gumb
|
||||
submitBtn.textContent = originalBtnText;
|
||||
submitBtn.disabled = false;
|
||||
|
||||
if (data.success) {
|
||||
// Uspešno poslano
|
||||
alert(data.data);
|
||||
this.reset();
|
||||
} else {
|
||||
// Napaka
|
||||
console.error('Form submission error:', data);
|
||||
alert(data.data || 'There was an error sending your message. Please try again.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Ponastavi gumb
|
||||
submitBtn.textContent = originalBtnText;
|
||||
submitBtn.disabled = false;
|
||||
|
||||
console.error('Error during form submission:', error);
|
||||
alert('There was an error sending your message: ' + error.message);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
/**
|
||||
* Template Name: Terms of Service
|
||||
*/
|
||||
|
||||
get_header(); ?>
|
||||
|
||||
<!-- Hero section -->
|
||||
<section class="page-hero">
|
||||
<div class="hero-content">
|
||||
<h1><?php the_title(); ?></h1>
|
||||
<p>Please read these terms carefully before using our services</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Terms Content -->
|
||||
<section class="terms-content">
|
||||
<div class="container">
|
||||
<div class="content-wrapper">
|
||||
<?php while (have_posts()) : the_post(); ?>
|
||||
<?php the_content(); ?>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* Terms of Service Page Styles */
|
||||
.page-hero {
|
||||
height: 40vh;
|
||||
background: linear-gradient(to top,
|
||||
rgba(0, 0, 0, 0.7) 0%,
|
||||
rgba(0, 0, 0, 0.4) 30%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
), var(--accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.page-hero .hero-content {
|
||||
padding: 2rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-hero h1 {
|
||||
font-size: 3.5rem;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.page-hero p {
|
||||
font-size: 1.3rem;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.terms-content {
|
||||
padding: 2rem 0 4rem;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.content-wrapper h2 {
|
||||
color: var(--dark);
|
||||
margin: 2rem 0 1rem;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.content-wrapper p {
|
||||
margin-bottom: 1.5rem;
|
||||
color: #444;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.content-wrapper ul {
|
||||
margin: 1rem 0 1.5rem 2rem;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.content-wrapper li {
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.content-wrapper strong {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-hero h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.page-hero p {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.content-wrapper h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
/**
|
||||
* Template Name: Thank You Page
|
||||
*/
|
||||
|
||||
get_header();
|
||||
|
||||
$is_inquiry = isset($_GET['inquiry']) && $_GET['inquiry'] === 'sent';
|
||||
?>
|
||||
|
||||
<!-- Hero section -->
|
||||
<section class="page-hero">
|
||||
<div class="hero-content">
|
||||
<h1><?php echo $is_inquiry ? 'Thank You for Your Inquiry!' : 'Thank You for Your Booking!'; ?></h1>
|
||||
<p><?php echo $is_inquiry ? 'Your inquiry has been sent successfully' : 'Your payment has been successfully processed'; ?></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Thank you content -->
|
||||
<section class="payment-success">
|
||||
<i class="fas <?php echo $is_inquiry ? 'fa-envelope' : 'fa-check-circle'; ?>"></i>
|
||||
<h2><?php echo $is_inquiry ? 'Inquiry Sent' : 'Booking Confirmed'; ?></h2>
|
||||
<?php if ($is_inquiry): ?>
|
||||
<p>We have received your inquiry and will get back to you as soon as possible with pricing and availability information. You will receive a confirmation email shortly.</p>
|
||||
<?php else: ?>
|
||||
<p>We have received your payment and your tour booking has been confirmed. You will receive a confirmation email shortly with all the details of your booking.</p>
|
||||
<?php endif; ?>
|
||||
<p>If you have any questions, please don't hesitate to contact us.</p>
|
||||
<a href="<?php echo esc_url(home_url('/')); ?>" class="btn-return">Return to Homepage</a>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* Thank You Page Styles */
|
||||
.page-hero {
|
||||
height: 40vh;
|
||||
background: linear-gradient(to top,
|
||||
rgba(0, 0, 0, 0.7) 0%,
|
||||
rgba(0, 0, 0, 0.4) 30%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
), var(--accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.page-hero .hero-content {
|
||||
padding: 2rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-hero h1 {
|
||||
font-size: 3.5rem;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.page-hero p {
|
||||
font-size: 1.3rem;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.payment-success {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.payment-success i {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.payment-success i.fa-check-circle {
|
||||
color: #10B981;
|
||||
}
|
||||
|
||||
.payment-success i.fa-envelope {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.payment-success h2 {
|
||||
color: #1F2937;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.payment-success p {
|
||||
color: #4B5563;
|
||||
margin-bottom: 2rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.btn-return {
|
||||
display: inline-block;
|
||||
padding: 1rem 2rem;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-return:hover {
|
||||
background-color: var(--accent-dark);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-hero h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.page-hero p {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.payment-success {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
/**
|
||||
* The template for displaying all pages
|
||||
*/
|
||||
get_header();
|
||||
?>
|
||||
|
||||
<!-- Page Hero section -->
|
||||
<section class="page-hero">
|
||||
<div class="hero-content">
|
||||
<h1><?php the_title(); ?></h1>
|
||||
<?php if (get_the_excerpt()) : ?>
|
||||
<p><?php echo get_the_excerpt(); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Page Content -->
|
||||
<section class="page-content">
|
||||
<div class="container">
|
||||
<?php
|
||||
while (have_posts()) :
|
||||
the_post();
|
||||
the_content();
|
||||
endwhile;
|
||||
?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
// Vključi WordPress
|
||||
require_once('../../../../wp-load.php');
|
||||
|
||||
// Ustvari glavni meni
|
||||
$menu_name = 'Primary Menu';
|
||||
$menu_exists = wp_get_nav_menu_object($menu_name);
|
||||
|
||||
if (!$menu_exists) {
|
||||
$menu_id = wp_create_nav_menu($menu_name);
|
||||
|
||||
// Dodaj menijske elemente
|
||||
wp_update_nav_menu_item($menu_id, 0, array(
|
||||
'menu-item-title' => 'Home',
|
||||
'menu-item-url' => home_url('/'),
|
||||
'menu-item-status' => 'publish',
|
||||
'menu-item-type' => 'custom',
|
||||
));
|
||||
|
||||
wp_update_nav_menu_item($menu_id, 0, array(
|
||||
'menu-item-title' => 'Contact us',
|
||||
'menu-item-url' => home_url('/contact'),
|
||||
'menu-item-status' => 'publish',
|
||||
'menu-item-type' => 'custom',
|
||||
));
|
||||
|
||||
wp_update_nav_menu_item($menu_id, 0, array(
|
||||
'menu-item-title' => 'Blog',
|
||||
'menu-item-url' => home_url('/blog'),
|
||||
'menu-item-status' => 'publish',
|
||||
'menu-item-type' => 'custom',
|
||||
));
|
||||
|
||||
// Dodeli meni lokaciji
|
||||
$locations = get_theme_mod('nav_menu_locations');
|
||||
$locations['primary'] = $menu_id;
|
||||
set_theme_mod('nav_menu_locations', $locations);
|
||||
}
|
||||
|
||||
// Nastavi kontaktne podatke
|
||||
set_theme_mod('contact_email', 'test@test.com');
|
||||
set_theme_mod('contact_phone', '+386 41 444 290');
|
||||
set_theme_mod('whatsapp', '38641444290');
|
||||
set_theme_mod('messenger', 'worlddiscovery');
|
||||
|
||||
// Nastavi družbena omrežja
|
||||
set_theme_mod('social_instagram', '#');
|
||||
set_theme_mod('social_facebook', '#');
|
||||
set_theme_mod('social_linkedin', '#');
|
||||
|
||||
// Nastavi opis v nogi
|
||||
set_theme_mod('footer_description', "We're a travel agency and tour operator for holidays, activities, and other unforgettable experiences in every corner of the globe, and for every sort of traveler.");
|
||||
|
||||
// Nastavi hero sekcijo
|
||||
set_theme_mod('hero_title', 'Grilc Tours');
|
||||
set_theme_mod('hero_subtitle', 'Your path to unforgettable cycling experiences');
|
||||
set_theme_mod('tours_section_title', 'Experience Journeys');
|
||||
|
||||
echo "Nastavitve teme so končane!\n";
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
<?php
|
||||
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
|
||||
header("Cache-Control: post-check=0, pre-check=0", false);
|
||||
header("Pragma: no-cache");
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<?php wp_head(); ?>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<style>
|
||||
.individual-tours {
|
||||
padding: 4rem 0;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.individual-tours .container {
|
||||
max-width: 100%;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.individual-tours h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
color: #333;
|
||||
font-size: 2.5rem;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.tours-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 350px);
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 1800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
width: 350px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
height: 420px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tour-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.tour-card .tour-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tour-card .tour-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.tour-card:hover .tour-image img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.tour-card .tour-info {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tour-card h3 {
|
||||
margin: 0 0 1rem;
|
||||
color: #333;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tour-card p {
|
||||
color: #666;
|
||||
margin-bottom: 1rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tour-card .tour-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.tour-card .price {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.tour-card .duration {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tour-card .btn-view {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 0.8rem;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s ease;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.tour-card .btn-view:hover {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
.no-tours {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
margin: 1rem 0;
|
||||
border-radius: 8px;
|
||||
font-size: 1.1rem;
|
||||
background-color: #f8f9fa;
|
||||
color: #6c757d;
|
||||
border: 1px solid #dee2e6;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.individual-tours h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.tours-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: 450px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body <?php body_class(); ?>>
|
||||
|
||||
<?php get_header(); ?>
|
||||
|
||||
<!-- Individual Tours -->
|
||||
<section class="individual-tours">
|
||||
<div class="container">
|
||||
<h2><?php
|
||||
$page_title = get_post_meta(get_the_ID(), '_page_title', true);
|
||||
echo esc_html($page_title ? $page_title : get_the_title());
|
||||
?></h2>
|
||||
<div class="tours-grid">
|
||||
<?php
|
||||
$journey_id = get_the_ID();
|
||||
|
||||
// Pridobi shranjen vrstni red
|
||||
$saved_order = get_option('individual_tour_order_' . $journey_id, '');
|
||||
$order_array = !empty($saved_order) ? explode(',', $saved_order) : array();
|
||||
|
||||
// Definiraj query argumente
|
||||
$args = array(
|
||||
'post_type' => 'individual_tour',
|
||||
'posts_per_page' => -1,
|
||||
'meta_key' => '_experience_journey',
|
||||
'meta_value' => $journey_id,
|
||||
'meta_compare' => '='
|
||||
);
|
||||
|
||||
// Če imamo shranjen vrstni red, uporabi post__in in orderby
|
||||
if (!empty($order_array)) {
|
||||
$args['post__in'] = $order_array;
|
||||
$args['orderby'] = 'post__in';
|
||||
}
|
||||
|
||||
$tours = new WP_Query($args);
|
||||
|
||||
// Debug v WordPress log
|
||||
error_log('=== EXPERIENCE JOURNEY DEBUG ===');
|
||||
error_log('Journey ID: ' . $journey_id);
|
||||
error_log('Found posts: ' . $tours->found_posts);
|
||||
error_log('Post count: ' . $tours->post_count);
|
||||
error_log('Max num pages: ' . $tours->max_num_pages);
|
||||
error_log('Query SQL: ' . $tours->request);
|
||||
error_log('Saved order array: ' . print_r($order_array, true));
|
||||
error_log('=== END DEBUG ===');
|
||||
|
||||
// Dodajmo tudi vizualni debug za administratorje
|
||||
if (current_user_can('administrator')) {
|
||||
echo '<div style="background: #f1f1f1; padding: 20px; margin: 20px; border: 1px solid #ddd;">';
|
||||
echo '<h3>Debug Info (visible only to admins):</h3>';
|
||||
echo '<p>Found posts: ' . $tours->found_posts . '</p>';
|
||||
echo '<p>Post count: ' . $tours->post_count . '</p>';
|
||||
echo '<p>Journey ID: ' . $journey_id . '</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
if ($tours->have_posts()) :
|
||||
while ($tours->have_posts()) : $tours->the_post();
|
||||
// Pridobi hero sliko
|
||||
$hero_image_id = get_post_meta(get_the_ID(), '_hero_image', true);
|
||||
$image = wp_get_attachment_image_url($hero_image_id, 'full');
|
||||
// Če hero slika ne obstaja, uporabi featured image
|
||||
if (!$image) {
|
||||
$image = get_the_post_thumbnail_url(get_the_ID(), 'large');
|
||||
}
|
||||
// Če ni nobene slike, uporabi placeholder
|
||||
if (!$image) {
|
||||
$image = get_theme_file_uri('images/placeholder.jpg');
|
||||
}
|
||||
$price = get_post_meta(get_the_ID(), '_tour_price', true);
|
||||
$duration = get_post_meta(get_the_ID(), '_tour_duration', true);
|
||||
?>
|
||||
<div class="tour-card">
|
||||
<div class="tour-image">
|
||||
<img src="<?php echo esc_url($image); ?>"
|
||||
alt="<?php echo esc_attr(get_the_title()); ?>"
|
||||
loading="lazy">
|
||||
</div>
|
||||
<div class="tour-info">
|
||||
<div>
|
||||
<h3><?php echo esc_html(get_the_title()); ?></h3>
|
||||
<p><?php echo esc_html(get_the_excerpt()); ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="tour-details">
|
||||
<?php if ($price) : ?>
|
||||
<span class="price">€<?php echo esc_html($price); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($duration) : ?>
|
||||
<span class="duration"><?php echo esc_html($duration); ?> days</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<a href="<?php echo esc_url(get_permalink()); ?>" class="btn-view">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
endwhile;
|
||||
wp_reset_postdata();
|
||||
else :
|
||||
?>
|
||||
<p class="no-tours">Currently no individual tours in this category.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,76 @@
|
|||
<?php get_header(); ?>
|
||||
|
||||
<article class="single-tour">
|
||||
<?php if (have_posts()) : while (have_posts()) : the_post(); ?>
|
||||
|
||||
<!-- Tour Hero -->
|
||||
<section class="tour-hero">
|
||||
<?php if (has_post_thumbnail()) : ?>
|
||||
<div class="tour-hero-image">
|
||||
<?php the_post_thumbnail('full'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="tour-hero-content">
|
||||
<div class="container">
|
||||
<div class="tour-category"><?php echo esc_html(get_post_meta(get_the_ID(), '_tour_category', true)); ?></div>
|
||||
<h1><?php the_title(); ?></h1>
|
||||
<div class="tour-meta">
|
||||
<div class="tour-duration">
|
||||
<i class="far fa-clock"></i>
|
||||
<?php echo esc_html(get_post_meta(get_the_ID(), '_tour_duration', true)); ?>
|
||||
</div>
|
||||
<div class="tour-distance">
|
||||
<i class="fas fa-route"></i>
|
||||
<?php echo esc_html(get_post_meta(get_the_ID(), '_tour_distance', true)); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Tour Content -->
|
||||
<section class="tour-content">
|
||||
<div class="container">
|
||||
<div class="tour-description">
|
||||
<?php the_content(); ?>
|
||||
</div>
|
||||
|
||||
<?php if (get_post_meta(get_the_ID(), '_tour_highlights', true)) : ?>
|
||||
<div class="tour-highlights">
|
||||
<h2>Highlights</h2>
|
||||
<ul>
|
||||
<?php
|
||||
$highlights = get_post_meta(get_the_ID(), '_tour_highlights', true);
|
||||
foreach ($highlights as $highlight) {
|
||||
echo '<li>' . esc_html($highlight) . '</li>';
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (get_post_meta(get_the_ID(), '_tour_inclusions', true)) : ?>
|
||||
<div class="tour-inclusions">
|
||||
<h2>What's Included</h2>
|
||||
<ul>
|
||||
<?php
|
||||
$inclusions = get_post_meta(get_the_ID(), '_tour_inclusions', true);
|
||||
foreach ($inclusions as $inclusion) {
|
||||
echo '<li>' . esc_html($inclusion) . '</li>';
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="tour-cta">
|
||||
<a href="#" class="btn-book">Book This Tour</a>
|
||||
<a href="#" class="btn-contact">Contact Us</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php endwhile; endif; ?>
|
||||
</article>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
|
@ -0,0 +1,386 @@
|
|||
<?php get_header(); ?>
|
||||
|
||||
<!-- Post Hero -->
|
||||
<section class="post-hero">
|
||||
<?php if (has_post_thumbnail()) : ?>
|
||||
<div class="post-hero-image">
|
||||
<?php the_post_thumbnail('full'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="post-hero-content">
|
||||
<div class="container">
|
||||
<div class="post-meta">
|
||||
<span class="date">
|
||||
<i class="far fa-calendar"></i>
|
||||
<?php echo get_the_date('M j, Y'); ?>
|
||||
</span>
|
||||
<?php
|
||||
$categories = get_the_category();
|
||||
if ($categories) :
|
||||
?>
|
||||
<span class="category">
|
||||
<i class="far fa-folder"></i>
|
||||
<?php echo esc_html($categories[0]->name); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<span class="author">
|
||||
<i class="far fa-user"></i>
|
||||
<?php the_author(); ?>
|
||||
</span>
|
||||
</div>
|
||||
<h1><?php the_title(); ?></h1>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Post Content -->
|
||||
<section class="post-content">
|
||||
<div class="container">
|
||||
<div class="content-wrapper">
|
||||
<?php
|
||||
while (have_posts()) :
|
||||
the_post();
|
||||
the_content();
|
||||
|
||||
// If comments are open or we have at least one comment, load up the comment template.
|
||||
if (comments_open() || get_comments_number()) :
|
||||
comments_template();
|
||||
endif;
|
||||
endwhile;
|
||||
?>
|
||||
</div>
|
||||
|
||||
<!-- Post Navigation -->
|
||||
<div class="post-navigation">
|
||||
<?php
|
||||
$prev_post = get_previous_post();
|
||||
$next_post = get_next_post();
|
||||
|
||||
if ($prev_post || $next_post) :
|
||||
?>
|
||||
<div class="nav-links">
|
||||
<?php if ($prev_post) : ?>
|
||||
<a href="<?php echo get_permalink($prev_post); ?>" class="nav-previous">
|
||||
<span class="nav-subtitle">Previous Post</span>
|
||||
<span class="nav-title"><?php echo get_the_title($prev_post); ?></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($next_post) : ?>
|
||||
<a href="<?php echo get_permalink($next_post); ?>" class="nav-next">
|
||||
<span class="nav-subtitle">Next Post</span>
|
||||
<span class="nav-title"><?php echo get_the_title($next_post); ?></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Related Posts -->
|
||||
<?php
|
||||
$categories = get_the_category();
|
||||
if ($categories) :
|
||||
$category_ids = array();
|
||||
foreach ($categories as $category) {
|
||||
$category_ids[] = $category->term_id;
|
||||
}
|
||||
|
||||
$related_query = new WP_Query(array(
|
||||
'category__in' => $category_ids,
|
||||
'post__not_in' => array(get_the_ID()),
|
||||
'posts_per_page' => 3,
|
||||
'orderby' => 'rand'
|
||||
));
|
||||
|
||||
if ($related_query->have_posts()) :
|
||||
?>
|
||||
<div class="related-posts">
|
||||
<h2>Related Posts</h2>
|
||||
<div class="related-grid">
|
||||
<?php
|
||||
while ($related_query->have_posts()) :
|
||||
$related_query->the_post();
|
||||
$image = get_the_post_thumbnail_url(get_the_ID(), 'medium_large');
|
||||
if (!$image) {
|
||||
$image = get_theme_file_uri('images/placeholder.jpg');
|
||||
}
|
||||
?>
|
||||
<article class="related-card">
|
||||
<div class="related-image">
|
||||
<img src="<?php echo esc_url($image); ?>"
|
||||
alt="<?php echo esc_attr(get_the_title()); ?>"
|
||||
loading="lazy">
|
||||
</div>
|
||||
<div class="related-content">
|
||||
<h3><?php the_title(); ?></h3>
|
||||
<a href="<?php the_permalink(); ?>" class="btn-read">Read More →</a>
|
||||
</div>
|
||||
</article>
|
||||
<?php
|
||||
endwhile;
|
||||
wp_reset_postdata();
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
endif;
|
||||
endif;
|
||||
?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* Single Post Styles */
|
||||
.post-hero {
|
||||
position: relative;
|
||||
height: 60vh;
|
||||
margin-bottom: 4rem;
|
||||
background-color: var(--dark);
|
||||
}
|
||||
|
||||
.post-hero-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.post-hero-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.post-hero-content {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding-bottom: 3rem;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
rgba(0, 0, 0, 0.7) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.post-meta {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.post-meta span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.post-meta i {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.post-hero-content h1 {
|
||||
color: white;
|
||||
font-size: 3rem;
|
||||
margin: 0;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.8;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.content-wrapper p {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.content-wrapper h2,
|
||||
.content-wrapper h3 {
|
||||
color: var(--dark);
|
||||
margin: 2rem 0 1rem;
|
||||
}
|
||||
|
||||
.content-wrapper img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.content-wrapper ul,
|
||||
.content-wrapper ol {
|
||||
margin: 1.5rem 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.content-wrapper li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.post-navigation {
|
||||
max-width: 800px;
|
||||
margin: 4rem auto;
|
||||
border-top: 1px solid #eee;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.nav-previous,
|
||||
.nav-next {
|
||||
flex: 1;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.nav-next {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.nav-subtitle {
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
color: var(--dark);
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-previous:hover .nav-title,
|
||||
.nav-next:hover .nav-title {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.related-posts {
|
||||
max-width: 1200px;
|
||||
margin: 4rem auto 0;
|
||||
}
|
||||
|
||||
.related-posts h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.related-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.related-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.related-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.related-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.related-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.related-card:hover .related-image img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.related-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.related-content h3 {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--dark);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-read {
|
||||
display: inline-block;
|
||||
padding: 0.8rem 1.5rem;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-read:hover {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.post-hero {
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.post-hero-content h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.post-meta {
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 0 1.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.nav-next {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.related-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
|
@ -0,0 +1,952 @@
|
|||
/*
|
||||
Theme Name: Europe Wonder
|
||||
Theme URI: https://europewonder.com
|
||||
Author: Europe Wonder
|
||||
Author URI: https://europewonder.com
|
||||
Description: Custom theme for Europe Wonder travel experiences
|
||||
Version: 1.0
|
||||
License: GNU General Public License v2 or later
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
Text Domain: grilctours
|
||||
*/
|
||||
|
||||
/* Reset in osnovni stili */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Spremenljivke za barve */
|
||||
:root {
|
||||
--primary: #edae49; /* Oranžna - glavna barva */
|
||||
--secondary: #d1495b; /* Rdeča - sekundarna barva */
|
||||
--accent: #00798c; /* Modro-zelena - poudarki */
|
||||
--accent-dark: #30638e; /* Temno modra - poudarki temni */
|
||||
--dark: #003d5b; /* Najtemnejša modra - temni elementi */
|
||||
}
|
||||
|
||||
/* Hero sekcija */
|
||||
.hero {
|
||||
height: 100vh;
|
||||
background: linear-gradient(to top,
|
||||
rgba(0, 0, 0, 0.7) 0%,
|
||||
rgba(0, 0, 0, 0.4) 30%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
), url('images/hero.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.5rem;
|
||||
color: white;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
/* Tours sekcija */
|
||||
.tours {
|
||||
padding: 4rem 0;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 2.5rem;
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tours-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.tour-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.tour-card .tour-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tour-card .tour-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.tour-card:hover .tour-image img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.tour-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.tour-content h3 {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.btn-more {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-more:hover {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
/* Video sekcija */
|
||||
.video-section {
|
||||
padding: 4rem 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.main-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
padding: 0.7rem 0;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.main-nav ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.main-nav a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.main-nav a:hover,
|
||||
.main-nav a.active {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.mobile-menu-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background-color: var(--dark);
|
||||
padding: 4rem 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4rem;
|
||||
}
|
||||
|
||||
.footer-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.footer-logo img {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.footer-description {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
line-height: 1.6;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.footer-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.footer-contact {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.footer-contact a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
font-size: 1.1rem;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-contact a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.footer-contact i {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.book-consultation {
|
||||
margin-top: 0.5rem;
|
||||
color: var(--primary) !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.book-consultation:hover {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.footer-links a {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-links a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.hero h1 {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.tours-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.footer-right {
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mobile-menu-toggle {
|
||||
display: block;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.8rem;
|
||||
color: var(--dark);
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.menu-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.menu-overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
z-index: 1000;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
display: none;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mobile-menu-toggle.active ~ .main-nav {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.main-nav ul {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.main-nav li {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 0.8rem 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.main-nav li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.main-nav a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
color: var(--dark);
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.main-nav a:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
body.menu-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* Page Hero */
|
||||
.page-hero {
|
||||
height: 60vh;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding-top: 60px;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.page-hero .hero-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.page-hero h1 {
|
||||
font-size: 3.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.page-hero p {
|
||||
font-size: 1.3rem;
|
||||
color: white;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Experience Description */
|
||||
.experience-description {
|
||||
padding: 2rem 0 4rem;
|
||||
}
|
||||
|
||||
.experience-description .container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Individual Tours */
|
||||
.individual-tours {
|
||||
padding: 4rem 0;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.individual-tours h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
color: #333;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.tours-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.tour-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.tour-card .tour-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tour-card .tour-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.tour-card:hover .tour-image img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.tour-card .tour-info {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.tour-card h3 {
|
||||
margin: 0 0 1rem;
|
||||
color: #333;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.tour-card p {
|
||||
color: #666;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tour-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tour-details .price {
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.btn-view {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
background-color: var(--dark);
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-view:hover {
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
.no-tours {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 1.1rem;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.individual-tours h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.tours-grid {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.tour-card h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tour Hero */
|
||||
.tour-hero {
|
||||
position: relative;
|
||||
height: 60vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tour-hero img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.tour-hero-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(0, 0, 0, 0.4) 0%,
|
||||
rgba(0, 0, 0, 0.6) 100%
|
||||
);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
color: white;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.tour-hero-content h1 {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1.5rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.tour-hero-meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.tour-price {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tour-duration i,
|
||||
.tour-location i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Tour Content */
|
||||
.tour-content-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 2rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.tour-info h2 {
|
||||
font-size: 1.8rem;
|
||||
color: #333;
|
||||
margin: 2rem 0 1rem;
|
||||
}
|
||||
|
||||
.tour-description {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.tour-description p {
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
/* Daily Program */
|
||||
.tour-daily-program {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.day-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.day-number {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.day-header h3 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.program-day {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.program-day p {
|
||||
margin-left: 3rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Included/Not Included Lists */
|
||||
.included-list,
|
||||
.not-included-list {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.included-item,
|
||||
.not-included-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.included-item i {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.not-included-item i {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* Booking Form */
|
||||
.booking-form {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||
position: sticky;
|
||||
top: 2rem;
|
||||
}
|
||||
|
||||
.booking-form h3 {
|
||||
font-size: 1.5rem;
|
||||
color: #333;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
border-color: var(--accent);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(0, 121, 140, 0.2);
|
||||
}
|
||||
|
||||
.total-price {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-book {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-book:hover {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tour-hero-content h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.tour-hero-meta {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.tour-content-wrapper {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.booking-form {
|
||||
position: static;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tour Extras Section */
|
||||
.tour-extras {
|
||||
margin: 3rem 0;
|
||||
padding: 2.5rem;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.05);
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.tour-extras h2 {
|
||||
color: var(--dark);
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
font-size: 1.8rem;
|
||||
position: relative;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tour-extras h2:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60px;
|
||||
height: 3px;
|
||||
background-color: var(--accent);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.extras-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.extra-item {
|
||||
background: white;
|
||||
padding: 1.2rem 1.5rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.2rem;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.extra-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.extra-item i {
|
||||
color: #9b59b6;
|
||||
font-size: 1.4rem;
|
||||
background: rgba(155, 89, 182, 0.1);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.extra-item span {
|
||||
color: #2c3e50;
|
||||
line-height: 1.6;
|
||||
font-size: 1.1rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tour-extras {
|
||||
padding: 1.5rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.extras-list {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.extra-item {
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.extra-item i {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
font-size: 1.2rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Floating Inquiry Button */
|
||||
.floating-inquiry-btn {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 50px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.floating-inquiry-btn:hover {
|
||||
background-color: var(--accent-dark);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.floating-inquiry-btn:visited {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.floating-inquiry-btn i {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.floating-inquiry-btn {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 16px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.floating-inquiry-btn i {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
/**
|
||||
* Template Name: Inquiry Page Template
|
||||
*
|
||||
* To je preprost template za prikaz Jotform obrazca.
|
||||
*/
|
||||
|
||||
get_header(); ?>
|
||||
|
||||
<div class="inquiry-page">
|
||||
<div class="container">
|
||||
<h1><?php the_title(); ?></h1>
|
||||
<p class="inquiry-subtitle">Tell us your wishes and we will create a custom tour for you</p>
|
||||
|
||||
<div class="jotform-container">
|
||||
<script type="text/javascript" src="https://form.jotform.com/jsform/251246404215346"></script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.inquiry-page {
|
||||
padding: 4rem 0;
|
||||
background-color: #f7f7f7;
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
.inquiry-page .container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.inquiry-page h1 {
|
||||
text-align: center;
|
||||
color: var(--dark);
|
||||
margin-bottom: 1rem;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.inquiry-subtitle {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.jotform-container {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.inquiry-page {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.inquiry-page h1 {
|
||||
font-size: 2rem;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.inquiry-subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.jotform-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
|
|
@ -0,0 +1,943 @@
|
|||
<?php
|
||||
/**
|
||||
* Additional fields for Individual Tour
|
||||
*/
|
||||
|
||||
// Registracija meta boxov za Individual Tour
|
||||
function register_individual_tour_meta_boxes() {
|
||||
add_meta_box(
|
||||
'individual_tour_details',
|
||||
'Tour Details',
|
||||
'render_individual_tour_meta_box',
|
||||
'individual_tour',
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
}
|
||||
add_action('add_meta_boxes', 'register_individual_tour_meta_boxes');
|
||||
|
||||
// Render meta box za Individual Tour
|
||||
function render_individual_tour_meta_box($post) {
|
||||
$price = get_post_meta($post->ID, '_tour_price', true);
|
||||
$duration = get_post_meta($post->ID, '_tour_duration', true);
|
||||
$location = get_post_meta($post->ID, '_tour_location', true);
|
||||
$experience_journey = get_post_meta($post->ID, '_experience_journey', true);
|
||||
$daily_program = get_post_meta($post->ID, '_daily_program', true);
|
||||
$included_items = get_post_meta($post->ID, '_included_items', true);
|
||||
$not_included_items = get_post_meta($post->ID, '_not_included_items', true);
|
||||
$available_extras = get_post_meta($post->ID, '_available_extras', true);
|
||||
$day_type_label = get_post_meta($post->ID, '_day_type_label', true);
|
||||
$hero_image = get_post_meta($post->ID, '_hero_image', true);
|
||||
$guide_image = get_post_meta($post->ID, '_guide_image', true);
|
||||
$guide_name = get_post_meta($post->ID, '_guide_name', true);
|
||||
$guide_title = get_post_meta($post->ID, '_guide_title', true);
|
||||
|
||||
if (!is_array($daily_program)) $daily_program = array();
|
||||
if (!is_array($included_items)) $included_items = array();
|
||||
if (!is_array($not_included_items)) $not_included_items = array();
|
||||
if (!is_array($available_extras)) $available_extras = array();
|
||||
if (empty($day_type_label)) $day_type_label = 'Day';
|
||||
|
||||
wp_nonce_field('individual_tour_nonce', 'individual_tour_nonce');
|
||||
?>
|
||||
<div class="tour-meta-box">
|
||||
<style>
|
||||
.tour-meta-box {
|
||||
max-width: 800px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
background: #fff;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="number"],
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.form-group input[type="text"]:focus,
|
||||
.form-group input[type="number"]:focus,
|
||||
.form-group select:focus,
|
||||
.form-group textarea:focus {
|
||||
border-color: #00798c;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(0,121,140,0.1);
|
||||
}
|
||||
.daily-program-list {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.program-day {
|
||||
background: #f9f9f9;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
.program-day input,
|
||||
.program-day textarea {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.items-list {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.item-row {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
.item-row input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.btn-add {
|
||||
background: #00798c;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
.btn-add:hover {
|
||||
background: #30638e;
|
||||
}
|
||||
.btn-remove {
|
||||
background: #dc3232;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
.btn-remove:hover {
|
||||
background: #b32d2e;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 1.2rem;
|
||||
color: #333;
|
||||
margin: 2rem 0 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid #00798c;
|
||||
}
|
||||
.guide-section {
|
||||
margin: 2rem 0;
|
||||
padding: 1.5rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
.guide-image-preview {
|
||||
max-width: 150px;
|
||||
height: 150px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
margin: 1rem 0;
|
||||
border: 3px solid #fff;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.guide-image-container {
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.guide-details {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.guide-details input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin: 5px 0 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Basic Tour Details -->
|
||||
<h3 class="section-title">Basic Information</h3>
|
||||
<div class="form-group">
|
||||
<label for="tour_price">Price (€)</label>
|
||||
<input type="number" id="tour_price" name="tour_price" step="0.01" value="<?php echo esc_attr($price); ?>" placeholder="Enter tour price">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="tour_duration">Duration (days)</label>
|
||||
<input type="number" id="tour_duration" name="tour_duration" value="<?php echo esc_attr($duration); ?>" placeholder="Enter number of days">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="tour_location">Location</label>
|
||||
<input type="text" id="tour_location" name="tour_location" value="<?php echo esc_attr($location); ?>" placeholder="Enter tour location">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="experience_journey">Experience Journey</label>
|
||||
<select id="experience_journey" name="experience_journey">
|
||||
<option value="">Select Experience Journey</option>
|
||||
<?php
|
||||
$journeys = get_posts(array(
|
||||
'post_type' => 'experience_journey',
|
||||
'posts_per_page' => -1,
|
||||
'orderby' => 'title',
|
||||
'order' => 'ASC'
|
||||
));
|
||||
foreach ($journeys as $journey) {
|
||||
printf(
|
||||
'<option value="%s" %s>%s</option>',
|
||||
esc_attr($journey->ID),
|
||||
selected($experience_journey, $journey->ID, false),
|
||||
esc_html($journey->post_title)
|
||||
);
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<p class="description">Select which Experience Journey this tour belongs to.</p>
|
||||
</div>
|
||||
|
||||
<!-- Hero Image -->
|
||||
<h3 class="section-title">Hero Image</h3>
|
||||
<div class="form-group">
|
||||
<div class="hero-image-upload">
|
||||
<?php
|
||||
$hero_image_url = wp_get_attachment_image_url($hero_image, 'full');
|
||||
?>
|
||||
<input type="hidden" name="hero_image" id="hero_image" value="<?php echo esc_attr($hero_image); ?>">
|
||||
<div class="hero-image-preview" style="margin-bottom: 1rem;">
|
||||
<?php if ($hero_image_url) : ?>
|
||||
<img src="<?php echo esc_url($hero_image_url); ?>"
|
||||
style="max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"
|
||||
class="hero-preview-img">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="hero-image-buttons">
|
||||
<button type="button" class="button upload-hero-image">
|
||||
<i class="dashicons dashicons-upload" style="margin-top: 3px;"></i>
|
||||
<?php echo $hero_image ? 'Change Hero Image' : 'Upload Hero Image'; ?>
|
||||
</button>
|
||||
<?php if ($hero_image) : ?>
|
||||
<button type="button" class="button remove-hero-image" style="margin-left: 10px;">
|
||||
<i class="dashicons dashicons-no" style="margin-top: 3px;"></i>
|
||||
Remove Image
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<p class="description" style="margin-top: 0.5rem;">
|
||||
Recommended size: 1920x1080px. This image will be displayed at the top of your tour page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tour Gallery -->
|
||||
<h3 class="section-title">Tour Gallery</h3>
|
||||
<div class="form-group">
|
||||
<div class="gallery-upload">
|
||||
<?php
|
||||
$gallery_images = get_post_meta($post->ID, '_tour_gallery', true);
|
||||
if (!is_array($gallery_images)) {
|
||||
$gallery_images = array();
|
||||
}
|
||||
?>
|
||||
<input type="hidden" name="tour_gallery" id="tour_gallery" value="<?php echo esc_attr(implode(',', $gallery_images)); ?>">
|
||||
<div class="gallery-preview" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 1rem; margin-bottom: 1rem;">
|
||||
<?php
|
||||
foreach ($gallery_images as $image_id) :
|
||||
$image_url = wp_get_attachment_image_url($image_id, 'medium');
|
||||
if ($image_url) :
|
||||
?>
|
||||
<div class="gallery-item" data-id="<?php echo esc_attr($image_id); ?>">
|
||||
<img src="<?php echo esc_url($image_url); ?>" style="width: 100%; height: 150px; object-fit: cover; border-radius: 4px;">
|
||||
<button type="button" class="remove-gallery-image" style="position: absolute; top: 5px; right: 5px; background: #dc3232; color: white; border: none; border-radius: 50%; width: 25px; height: 25px; cursor: pointer;">×</button>
|
||||
</div>
|
||||
<?php
|
||||
endif;
|
||||
endforeach;
|
||||
?>
|
||||
</div>
|
||||
<div class="gallery-buttons">
|
||||
<button type="button" class="button upload-gallery-images">
|
||||
<i class="dashicons dashicons-images-alt2" style="margin-top: 3px;"></i>
|
||||
Add Gallery Images
|
||||
</button>
|
||||
</div>
|
||||
<p class="description" style="margin-top: 0.5rem;">
|
||||
Add multiple images to showcase your tour. Recommended size: 1200x800px.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Daily Program -->
|
||||
<h3 class="section-title">Daily Program</h3>
|
||||
<div class="form-group">
|
||||
<label for="day_type_label">Day Type Label</label>
|
||||
<input type="text" id="day_type_label" name="day_type_label" value="<?php echo esc_attr($day_type_label); ?>" placeholder="Enter word to use instead of 'Day' (e.g. Activity, Dejavnost, etc.)">
|
||||
<p class="description">This label will be displayed before each day number (e.g. "<?php echo esc_html($day_type_label); ?> 1", "<?php echo esc_html($day_type_label); ?> 2", etc.)</p>
|
||||
|
||||
<div id="daily-program-list" class="daily-program-list">
|
||||
<?php foreach ($daily_program as $index => $day) : ?>
|
||||
<div class="program-day">
|
||||
<input type="text" name="daily_program[<?php echo $index; ?>][title]"
|
||||
placeholder="Day Title"
|
||||
value="<?php echo esc_attr($day['title']); ?>">
|
||||
<textarea name="daily_program[<?php echo $index; ?>][description]"
|
||||
rows="3"
|
||||
placeholder="Day Description"><?php echo esc_textarea($day['description']); ?></textarea>
|
||||
<button type="button" class="btn-remove" onclick="removeDay(this)">Remove Day</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="btn-add" onclick="addDay()">Add Day</button>
|
||||
</div>
|
||||
|
||||
<!-- Included Items -->
|
||||
<h3 class="section-title">Included in Price</h3>
|
||||
<div class="form-group">
|
||||
<div id="included-items-list" class="items-list">
|
||||
<?php foreach ($included_items as $index => $item) : ?>
|
||||
<div class="item-row">
|
||||
<input type="text" name="included_items[]" value="<?php echo esc_attr($item); ?>" placeholder="Enter included item">
|
||||
<button type="button" class="btn-remove" onclick="removeItem(this)">Remove</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="btn-add" onclick="addItem('included-items-list', 'included_items[]')">Add Item</button>
|
||||
</div>
|
||||
|
||||
<!-- Not Included Items -->
|
||||
<h3 class="section-title">Not Included in Price</h3>
|
||||
<div class="form-group">
|
||||
<div id="not-included-items-list" class="items-list">
|
||||
<?php foreach ($not_included_items as $index => $item) : ?>
|
||||
<div class="item-row">
|
||||
<input type="text" name="not_included_items[]" value="<?php echo esc_attr($item); ?>" placeholder="Enter not included item">
|
||||
<button type="button" class="btn-remove" onclick="removeItem(this)">Remove</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="btn-add" onclick="addItem('not-included-items-list', 'not_included_items[]')">Add Item</button>
|
||||
</div>
|
||||
|
||||
<!-- Available Extras -->
|
||||
<h3 class="section-title">Available Extras</h3>
|
||||
<div class="form-group">
|
||||
<div id="available-extras-list" class="items-list">
|
||||
<?php foreach ($available_extras as $index => $extra) : ?>
|
||||
<div class="item-row">
|
||||
<input type="text" name="available_extras[<?php echo $index; ?>][name]" value="<?php echo esc_attr($extra['name']); ?>" placeholder="Enter extra name" style="flex: 2;">
|
||||
<input type="number" name="available_extras[<?php echo $index; ?>][price]" value="<?php echo esc_attr($extra['price']); ?>" placeholder="Price" step="0.01" style="flex: 1;">
|
||||
<label style="margin-left: 10px; display: flex; align-items: center; white-space: nowrap;">
|
||||
<input type="checkbox" name="available_extras[<?php echo $index; ?>][per_day_per_person]" <?php checked(!empty($extra['per_day_per_person']), true); ?>>
|
||||
<span style="margin-left: 5px;">Per day per person</span>
|
||||
</label>
|
||||
<button type="button" class="btn-remove" onclick="removeExtra(this)">Remove</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="btn-add" onclick="addExtra()">Add Extra</button>
|
||||
<p class="description" style="margin-top: 10px;">
|
||||
Check "Per day per person" for extras where the price should be multiplied by both the number of days and participants (e.g. bike rental at €20/day/person for an 8-day tour with 3 participants would cost €480).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Guide Information -->
|
||||
<h3 class="section-title">Guide Information</h3>
|
||||
<div class="guide-section">
|
||||
<div class="guide-image-container">
|
||||
<div class="image-preview-wrapper">
|
||||
<?php if ($guide_image) : ?>
|
||||
<img src="<?php echo esc_url($guide_image); ?>" alt="Guide" class="guide-image-preview">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<input type="hidden" name="guide_image" id="guide_image" value="<?php echo esc_attr($guide_image); ?>">
|
||||
<button type="button" class="button guide-image-upload">Select Guide Image</button>
|
||||
<button type="button" class="button guide-image-remove" <?php echo empty($guide_image) ? 'style="display:none;"' : ''; ?>>Remove Image</button>
|
||||
</div>
|
||||
|
||||
<div class="guide-details">
|
||||
<label for="guide_name">Guide Name</label>
|
||||
<input type="text" id="guide_name" name="guide_name" value="<?php echo esc_attr($guide_name); ?>" placeholder="Enter guide's name">
|
||||
|
||||
<label for="guide_title">Guide Title/Role</label>
|
||||
<input type="text" id="guide_title" name="guide_title" value="<?php echo esc_attr($guide_title); ?>" placeholder="e.g. Travel Expert">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FAQ Section -->
|
||||
<h3 class="section-title">FAQ - Frequently Asked Questions</h3>
|
||||
<div class="form-group">
|
||||
<div id="faq-list" class="items-list">
|
||||
<?php
|
||||
$faqs = get_post_meta($post->ID, '_tour_faqs', true);
|
||||
if (!is_array($faqs)) $faqs = array();
|
||||
foreach ($faqs as $index => $faq) :
|
||||
?>
|
||||
<div class="faq-item" style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 15px;">
|
||||
<input type="text"
|
||||
name="tour_faqs[<?php echo $index; ?>][question]"
|
||||
value="<?php echo esc_attr($faq['question']); ?>"
|
||||
placeholder="Question"
|
||||
style="margin-bottom: 10px;">
|
||||
<textarea name="tour_faqs[<?php echo $index; ?>][answer]"
|
||||
rows="3"
|
||||
placeholder="Answer"
|
||||
style="width: 100%; margin-bottom: 10px;"><?php echo esc_textarea($faq['answer']); ?></textarea>
|
||||
<button type="button" class="btn-remove" onclick="removeFaq(this)">Remove Question</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="button" class="btn-add" onclick="addFaq()">Add New Question</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Experience Journey Dropdown
|
||||
const experienceJourney = document.getElementById('experience_journey');
|
||||
if (experienceJourney) {
|
||||
let isOpen = false;
|
||||
|
||||
experienceJourney.addEventListener('mousedown', function(e) {
|
||||
if (!isOpen) {
|
||||
isOpen = true;
|
||||
} else {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.blur();
|
||||
isOpen = false;
|
||||
}
|
||||
});
|
||||
|
||||
experienceJourney.addEventListener('change', function(e) {
|
||||
isOpen = false;
|
||||
this.blur();
|
||||
});
|
||||
|
||||
experienceJourney.addEventListener('blur', function(e) {
|
||||
isOpen = false;
|
||||
});
|
||||
|
||||
// Prepreči odpiranje ob kliku izven dropdown-a
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!experienceJourney.contains(e.target)) {
|
||||
isOpen = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hero Image Upload
|
||||
const heroImageUpload = document.querySelector('.upload-hero-image');
|
||||
const heroImageRemove = document.querySelector('.remove-hero-image');
|
||||
const heroImageInput = document.getElementById('hero_image');
|
||||
const heroPreviewContainer = document.querySelector('.hero-image-preview');
|
||||
|
||||
if (heroImageUpload) {
|
||||
heroImageUpload.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const frame = wp.media({
|
||||
title: 'Select or Upload Hero Image',
|
||||
button: {
|
||||
text: 'Use this image'
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
frame.on('select', function() {
|
||||
const attachment = frame.state().get('selection').first().toJSON();
|
||||
heroImageInput.value = attachment.id;
|
||||
|
||||
// Update preview
|
||||
heroPreviewContainer.innerHTML = `
|
||||
<img src="${attachment.url}"
|
||||
style="max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"
|
||||
class="hero-preview-img">
|
||||
`;
|
||||
|
||||
// Show remove button if it doesn't exist
|
||||
if (!document.querySelector('.remove-hero-image')) {
|
||||
const removeButton = document.createElement('button');
|
||||
removeButton.type = 'button';
|
||||
removeButton.className = 'button remove-hero-image';
|
||||
removeButton.style.marginLeft = '10px';
|
||||
removeButton.innerHTML = '<i class="dashicons dashicons-no" style="margin-top: 3px;"></i> Remove Image';
|
||||
document.querySelector('.hero-image-buttons').appendChild(removeButton);
|
||||
|
||||
// Add event listener to new remove button
|
||||
removeButton.addEventListener('click', removeHeroImage);
|
||||
}
|
||||
|
||||
// Update upload button text
|
||||
heroImageUpload.innerHTML = '<i class="dashicons dashicons-upload" style="margin-top: 3px;"></i> Change Hero Image';
|
||||
});
|
||||
|
||||
frame.open();
|
||||
});
|
||||
}
|
||||
|
||||
function removeHeroImage() {
|
||||
heroImageInput.value = '';
|
||||
heroPreviewContainer.innerHTML = '';
|
||||
this.remove();
|
||||
heroImageUpload.innerHTML = '<i class="dashicons dashicons-upload" style="margin-top: 3px;"></i> Upload Hero Image';
|
||||
}
|
||||
|
||||
if (heroImageRemove) {
|
||||
heroImageRemove.addEventListener('click', removeHeroImage);
|
||||
}
|
||||
|
||||
// Gallery Images Upload
|
||||
const galleryUpload = document.querySelector('.upload-gallery-images');
|
||||
const galleryInput = document.getElementById('tour_gallery');
|
||||
const galleryPreview = document.querySelector('.gallery-preview');
|
||||
|
||||
if (galleryUpload) {
|
||||
galleryUpload.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const frame = wp.media({
|
||||
title: 'Select Gallery Images',
|
||||
button: {
|
||||
text: 'Add to gallery'
|
||||
},
|
||||
multiple: true
|
||||
});
|
||||
|
||||
frame.on('select', function() {
|
||||
const attachments = frame.state().get('selection').toJSON();
|
||||
const currentImages = galleryInput.value ? galleryInput.value.split(',') : [];
|
||||
|
||||
attachments.forEach(attachment => {
|
||||
if (!currentImages.includes(attachment.id.toString())) {
|
||||
currentImages.push(attachment.id);
|
||||
|
||||
const galleryItem = document.createElement('div');
|
||||
galleryItem.className = 'gallery-item';
|
||||
galleryItem.dataset.id = attachment.id;
|
||||
galleryItem.style.position = 'relative';
|
||||
|
||||
galleryItem.innerHTML = `
|
||||
<img src="${attachment.url}" style="width: 100%; height: 150px; object-fit: cover; border-radius: 4px;">
|
||||
<button type="button" class="remove-gallery-image" style="position: absolute; top: 5px; right: 5px; background: #dc3232; color: white; border: none; border-radius: 50%; width: 25px; height: 25px; cursor: pointer;">×</button>
|
||||
`;
|
||||
|
||||
galleryPreview.appendChild(galleryItem);
|
||||
}
|
||||
});
|
||||
|
||||
galleryInput.value = currentImages.join(',');
|
||||
});
|
||||
|
||||
frame.open();
|
||||
});
|
||||
}
|
||||
|
||||
// Remove gallery image
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('remove-gallery-image')) {
|
||||
const galleryItem = e.target.closest('.gallery-item');
|
||||
const imageId = galleryItem.dataset.id;
|
||||
const currentImages = galleryInput.value.split(',');
|
||||
const updatedImages = currentImages.filter(id => id !== imageId);
|
||||
|
||||
galleryInput.value = updatedImages.join(',');
|
||||
galleryItem.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Guide Image Upload
|
||||
jQuery(document).ready(function($) {
|
||||
$('.guide-image-upload').click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var button = $(this);
|
||||
var imageField = $('#guide_image');
|
||||
var previewWrapper = $('.guide-image-container .image-preview-wrapper');
|
||||
var removeButton = $('.guide-image-remove');
|
||||
|
||||
var frame = wp.media({
|
||||
title: 'Select Guide Image',
|
||||
button: {
|
||||
text: 'Use this image'
|
||||
},
|
||||
library: {
|
||||
type: 'image'
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
frame.on('select', function() {
|
||||
var attachment = frame.state().get('selection').first().toJSON();
|
||||
imageField.val(attachment.url);
|
||||
previewWrapper.html('<img src="' + attachment.url + '" alt="Guide" class="guide-image-preview">');
|
||||
removeButton.show();
|
||||
});
|
||||
|
||||
frame.open();
|
||||
});
|
||||
|
||||
// Remove Guide Image
|
||||
$('.guide-image-remove').click(function() {
|
||||
$('#guide_image').val('');
|
||||
$('.guide-image-container .image-preview-wrapper').empty();
|
||||
$(this).hide();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function addDay() {
|
||||
const list = document.getElementById('daily-program-list');
|
||||
const index = list.children.length;
|
||||
const day = document.createElement('div');
|
||||
day.className = 'program-day';
|
||||
day.innerHTML = `
|
||||
<input type="text" name="daily_program[${index}][title]" placeholder="Day Title">
|
||||
<textarea name="daily_program[${index}][description]" rows="3" placeholder="Day Description"></textarea>
|
||||
<button type="button" class="btn-remove" onclick="removeDay(this)">Remove Day</button>
|
||||
`;
|
||||
list.appendChild(day);
|
||||
}
|
||||
|
||||
function removeDay(button) {
|
||||
button.parentElement.remove();
|
||||
reindexDays();
|
||||
}
|
||||
|
||||
function reindexDays() {
|
||||
const days = document.querySelectorAll('#daily-program-list .program-day');
|
||||
days.forEach((day, index) => {
|
||||
day.querySelector('input').name = `daily_program[${index}][title]`;
|
||||
day.querySelector('textarea').name = `daily_program[${index}][description]`;
|
||||
});
|
||||
}
|
||||
|
||||
function addItem(listId, inputName) {
|
||||
const list = document.getElementById(listId);
|
||||
const row = document.createElement('div');
|
||||
row.className = 'item-row';
|
||||
row.innerHTML = `
|
||||
<input type="text" name="${inputName}" placeholder="Enter item">
|
||||
<button type="button" class="btn-remove" onclick="removeItem(this)">Remove</button>
|
||||
`;
|
||||
list.appendChild(row);
|
||||
}
|
||||
|
||||
function removeItem(button) {
|
||||
button.parentElement.remove();
|
||||
}
|
||||
|
||||
function addExtra() {
|
||||
const list = document.getElementById('available-extras-list');
|
||||
const index = list.children.length;
|
||||
const row = document.createElement('div');
|
||||
row.className = 'item-row';
|
||||
row.innerHTML = `
|
||||
<input type="text" name="available_extras[${index}][name]" placeholder="Enter extra name" style="flex: 2;">
|
||||
<input type="number" name="available_extras[${index}][price]" placeholder="Price" step="0.01" style="flex: 1;">
|
||||
<label style="margin-left: 10px; display: flex; align-items: center; white-space: nowrap;">
|
||||
<input type="checkbox" name="available_extras[${index}][per_day_per_person]">
|
||||
<span style="margin-left: 5px;">Per day per person</span>
|
||||
</label>
|
||||
<button type="button" class="btn-remove" onclick="removeExtra(this)">Remove</button>
|
||||
`;
|
||||
list.appendChild(row);
|
||||
}
|
||||
|
||||
function removeExtra(button) {
|
||||
button.parentElement.remove();
|
||||
reindexExtras();
|
||||
}
|
||||
|
||||
function reindexExtras() {
|
||||
const extras = document.querySelectorAll('#available-extras-list .item-row');
|
||||
extras.forEach((extra, index) => {
|
||||
const inputs = extra.querySelectorAll('input');
|
||||
inputs[0].name = `available_extras[${index}][name]`;
|
||||
inputs[1].name = `available_extras[${index}][price]`;
|
||||
// Update the checkbox name as well if it exists
|
||||
if (inputs[2] && inputs[2].type === 'checkbox') {
|
||||
inputs[2].name = `available_extras[${index}][per_day_per_person]`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addFaq() {
|
||||
const list = document.getElementById('faq-list');
|
||||
const index = list.children.length;
|
||||
const faq = document.createElement('div');
|
||||
faq.className = 'faq-item';
|
||||
faq.style = 'background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 15px;';
|
||||
faq.innerHTML = `
|
||||
<input type="text"
|
||||
name="tour_faqs[${index}][question]"
|
||||
placeholder="Question"
|
||||
style="margin-bottom: 10px;">
|
||||
<textarea name="tour_faqs[${index}][answer]"
|
||||
rows="3"
|
||||
placeholder="Answer"
|
||||
style="width: 100%; margin-bottom: 10px;"></textarea>
|
||||
<button type="button" class="btn-remove" onclick="removeFaq(this)">Remove Question</button>
|
||||
`;
|
||||
list.appendChild(faq);
|
||||
}
|
||||
|
||||
function removeFaq(button) {
|
||||
button.parentElement.remove();
|
||||
reindexFaqs();
|
||||
}
|
||||
|
||||
function reindexFaqs() {
|
||||
const faqs = document.querySelectorAll('#faq-list .faq-item');
|
||||
faqs.forEach((faq, index) => {
|
||||
const inputs = faq.querySelectorAll('input, textarea');
|
||||
inputs[0].name = `tour_faqs[${index}][question]`;
|
||||
inputs[1].name = `tour_faqs[${index}][answer]`;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Shrani meta podatke za Individual Tour
|
||||
function save_individual_tour_meta($post_id) {
|
||||
if (!isset($_POST['individual_tour_nonce']) || !wp_verify_nonce($_POST['individual_tour_nonce'], 'individual_tour_nonce')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current_user_can('edit_post', $post_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shrani osnovne podatke
|
||||
$fields = array(
|
||||
'tour_price' => 'sanitize_text_field',
|
||||
'tour_duration' => 'sanitize_text_field',
|
||||
'tour_location' => 'sanitize_text_field',
|
||||
'experience_journey' => 'absint',
|
||||
'hero_image' => 'absint',
|
||||
'guide_image' => 'absint',
|
||||
'guide_name' => 'sanitize_text_field',
|
||||
'guide_title' => 'sanitize_text_field',
|
||||
'day_type_label' => 'sanitize_text_field'
|
||||
);
|
||||
|
||||
// Debug izpis za administratorje
|
||||
if (current_user_can('administrator')) {
|
||||
error_log('Saving tour meta data:');
|
||||
error_log('POST data: ' . print_r($_POST, true));
|
||||
}
|
||||
|
||||
foreach ($fields as $field => $sanitize_callback) {
|
||||
if (isset($_POST[$field])) {
|
||||
$value = call_user_func($sanitize_callback, $_POST[$field]);
|
||||
update_post_meta($post_id, '_' . $field, $value);
|
||||
|
||||
// Debug izpis za administratorje
|
||||
if (current_user_can('administrator')) {
|
||||
error_log("Saving field {$field} with value: {$value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shrani galerijo
|
||||
if (isset($_POST['tour_gallery'])) {
|
||||
$gallery_images = array_filter(explode(',', sanitize_text_field($_POST['tour_gallery'])));
|
||||
update_post_meta($post_id, '_tour_gallery', $gallery_images);
|
||||
}
|
||||
|
||||
// Shrani dnevni program
|
||||
if (isset($_POST['daily_program'])) {
|
||||
$daily_program = array();
|
||||
foreach ($_POST['daily_program'] as $index => $day) {
|
||||
if (!empty($day['title']) || !empty($day['description'])) {
|
||||
$daily_program[] = array(
|
||||
'title' => sanitize_text_field($day['title']),
|
||||
'description' => wp_kses_post($day['description'])
|
||||
);
|
||||
}
|
||||
}
|
||||
update_post_meta($post_id, '_daily_program', $daily_program);
|
||||
}
|
||||
|
||||
// Shrani vključene elemente
|
||||
if (isset($_POST['included_items'])) {
|
||||
$included_items = array_filter(array_map('sanitize_text_field', $_POST['included_items']));
|
||||
update_post_meta($post_id, '_included_items', $included_items);
|
||||
}
|
||||
|
||||
// Shrani nevključene elemente
|
||||
if (isset($_POST['not_included_items'])) {
|
||||
$not_included_items = array_filter(array_map('sanitize_text_field', $_POST['not_included_items']));
|
||||
update_post_meta($post_id, '_not_included_items', $not_included_items);
|
||||
}
|
||||
|
||||
// Shrani available extras
|
||||
if (isset($_POST['available_extras'])) {
|
||||
$available_extras = array();
|
||||
foreach ($_POST['available_extras'] as $extra) {
|
||||
if (!empty($extra['name'])) {
|
||||
$available_extras[] = array(
|
||||
'name' => sanitize_text_field($extra['name']),
|
||||
'price' => floatval($extra['price']),
|
||||
'per_day_per_person' => isset($extra['per_day_per_person']) ? true : false
|
||||
);
|
||||
}
|
||||
}
|
||||
update_post_meta($post_id, '_available_extras', $available_extras);
|
||||
}
|
||||
|
||||
// Shrani FAQ vprašanja in odgovore
|
||||
if (isset($_POST['tour_faqs'])) {
|
||||
$faqs = array();
|
||||
foreach ($_POST['tour_faqs'] as $faq) {
|
||||
if (!empty($faq['question']) || !empty($faq['answer'])) {
|
||||
$faqs[] = array(
|
||||
'question' => sanitize_text_field($faq['question']),
|
||||
'answer' => wp_kses_post($faq['answer'])
|
||||
);
|
||||
}
|
||||
}
|
||||
update_post_meta($post_id, '_tour_faqs', $faqs);
|
||||
}
|
||||
}
|
||||
add_action('save_post_individual_tour', 'save_individual_tour_meta');
|
||||
|
||||
// Dodaj podporo za naslovno sliko
|
||||
add_theme_support('post-thumbnails');
|
||||
|
||||
// Registriraj meta polja za REST API
|
||||
function register_tour_meta_fields() {
|
||||
// Registracija meta polj za Individual Tour
|
||||
register_post_meta('individual_tour', '_daily_program', array(
|
||||
'type' => 'array',
|
||||
'single' => true,
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'title' => array('type' => 'string'),
|
||||
'description' => array('type' => 'string')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
register_post_meta('individual_tour', '_included_items', array(
|
||||
'type' => 'array',
|
||||
'single' => true,
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'array',
|
||||
'items' => array('type' => 'string')
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
register_post_meta('individual_tour', '_not_included_items', array(
|
||||
'type' => 'array',
|
||||
'single' => true,
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'array',
|
||||
'items' => array('type' => 'string')
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
register_post_meta('individual_tour', '_available_extras', array(
|
||||
'type' => 'array',
|
||||
'single' => true,
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'title' => array('type' => 'string'),
|
||||
'price' => array('type' => 'number')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
// Registracija ostalih meta polj
|
||||
register_post_meta('individual_tour', '_tour_price', array(
|
||||
'type' => 'number',
|
||||
'single' => true,
|
||||
'show_in_rest' => true
|
||||
));
|
||||
|
||||
register_post_meta('individual_tour', '_tour_duration', array(
|
||||
'type' => 'number',
|
||||
'single' => true,
|
||||
'show_in_rest' => true
|
||||
));
|
||||
|
||||
register_post_meta('individual_tour', '_tour_location', array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'show_in_rest' => true
|
||||
));
|
||||
|
||||
register_post_meta('individual_tour', '_experience_journey', array(
|
||||
'type' => 'number',
|
||||
'single' => true,
|
||||
'show_in_rest' => true
|
||||
));
|
||||
|
||||
register_post_meta('individual_tour', '_hero_image', array(
|
||||
'type' => 'number',
|
||||
'single' => true,
|
||||
'show_in_rest' => true
|
||||
));
|
||||
|
||||
register_post_meta('individual_tour', '_tour_gallery', array(
|
||||
'type' => 'array',
|
||||
'single' => true,
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'array',
|
||||
'items' => array('type' => 'number')
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
register_post_meta('individual_tour', '_tour_faqs', array(
|
||||
'type' => 'array',
|
||||
'single' => true,
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'question' => array('type' => 'string'),
|
||||
'answer' => array('type' => 'string')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
}
|
||||
add_action('init', 'register_tour_meta_fields');
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit40aa654f2e66c20881ae0572fe987a10::getLoader();
|
||||
|
|
@ -0,0 +1,579 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see https://www.php-fig.org/psr/psr-0/
|
||||
* @see https://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
/** @var \Closure(string):void */
|
||||
private static $includeFile;
|
||||
|
||||
/** @var string|null */
|
||||
private $vendorDir;
|
||||
|
||||
// PSR-4
|
||||
/**
|
||||
* @var array<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
/**
|
||||
* List of PSR-0 prefixes
|
||||
*
|
||||
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
|
||||
*
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var string|null */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var array<string, self>
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param string|null $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string> Array of classname => path
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $classMap Class to filename map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 base directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($prepend) {
|
||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||
} else {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
|
||||
if (null !== $this->vendorDir) {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return true|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently registered loaders keyed by their corresponding vendor directories.
|
||||
*
|
||||
* @return array<string, self>
|
||||
*/
|
||||
public static function getRegisteredLoaders()
|
||||
{
|
||||
return self::$registeredLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $ext
|
||||
* @return string|false
|
||||
*/
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,378 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* This class is copied in every Composer installed project and available to all
|
||||
*
|
||||
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
|
||||
*
|
||||
* To require its presence, you can require `composer-runtime-api ^2.0`
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $installedIsLocalDir;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $canGetVendors;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static $installedByVendor = array();
|
||||
|
||||
/**
|
||||
* Returns a list of all package names which are present, either by being installed, replaced or provided
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackages()
|
||||
{
|
||||
$packages = array();
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
$packages[] = array_keys($installed['versions']);
|
||||
}
|
||||
|
||||
if (1 === \count($packages)) {
|
||||
return $packages[0];
|
||||
}
|
||||
|
||||
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all package names with a specific type e.g. 'library'
|
||||
*
|
||||
* @param string $type
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackagesByType($type)
|
||||
{
|
||||
$packagesByType = array();
|
||||
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
foreach ($installed['versions'] as $name => $package) {
|
||||
if (isset($package['type']) && $package['type'] === $type) {
|
||||
$packagesByType[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $packagesByType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package is installed
|
||||
*
|
||||
* This also returns true if the package name is provided or replaced by another package
|
||||
*
|
||||
* @param string $packageName
|
||||
* @param bool $includeDevRequirements
|
||||
* @return bool
|
||||
*/
|
||||
public static function isInstalled($packageName, $includeDevRequirements = true)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (isset($installed['versions'][$packageName])) {
|
||||
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package satisfies a version constraint
|
||||
*
|
||||
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
|
||||
*
|
||||
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
|
||||
*
|
||||
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
|
||||
* @param string $packageName
|
||||
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
|
||||
* @return bool
|
||||
*/
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints((string) $constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
|
||||
return $provided->matches($constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version constraint representing all the range(s) which are installed for a given package
|
||||
*
|
||||
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
|
||||
* whether a given version of a package is installed, and not just whether it exists
|
||||
*
|
||||
* @param string $packageName
|
||||
* @return string Version constraint usable with composer/semver
|
||||
*/
|
||||
public static function getVersionRanges($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ranges = array();
|
||||
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||
}
|
||||
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||
}
|
||||
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||
}
|
||||
|
||||
return implode(' || ', $ranges);
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getPrettyVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
|
||||
*/
|
||||
public static function getReference($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['reference'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
|
||||
*/
|
||||
public static function getInstallPath($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
|
||||
*/
|
||||
public static function getRootPackage()
|
||||
{
|
||||
$installed = self::getInstalled();
|
||||
|
||||
return $installed[0]['root'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw installed.php data for custom implementations
|
||||
*
|
||||
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
|
||||
* @return array[]
|
||||
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||
*/
|
||||
public static function getRawData()
|
||||
{
|
||||
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
self::$installed = include __DIR__ . '/installed.php';
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw data of all installed.php which are currently loaded for custom implementations
|
||||
*
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
public static function getAllRawData()
|
||||
{
|
||||
return self::getInstalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets you reload the static array from another file
|
||||
*
|
||||
* This is only useful for complex integrations in which a project needs to use
|
||||
* this class but then also needs to execute another project's autoloader in process,
|
||||
* and wants to ensure both projects have access to their version of installed.php.
|
||||
*
|
||||
* A typical case would be PHPUnit, where it would need to make sure it reads all
|
||||
* the data it needs from this class, then call reload() with
|
||||
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
|
||||
* the project in which it runs can then also use this class safely, without
|
||||
* interference between PHPUnit's dependencies and the project's dependencies.
|
||||
*
|
||||
* @param array[] $data A vendor/composer/installed.php data set
|
||||
* @return void
|
||||
*
|
||||
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
|
||||
*/
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
|
||||
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
|
||||
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
|
||||
// so we have to assume it does not, and that may result in duplicate data being returned when listing
|
||||
// all installed packages for example
|
||||
self::$installedIsLocalDir = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static function getInstalled()
|
||||
{
|
||||
if (null === self::$canGetVendors) {
|
||||
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||
}
|
||||
|
||||
$installed = array();
|
||||
$copiedLocalDir = false;
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
$selfDir = strtr(__DIR__, '\\', '/');
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
$vendorDir = strtr($vendorDir, '\\', '/');
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
self::$installedByVendor[$vendorDir] = $required;
|
||||
$installed[] = $required;
|
||||
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
|
||||
self::$installed = $required;
|
||||
self::$installedIsLocalDir = true;
|
||||
}
|
||||
}
|
||||
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require __DIR__ . '/installed.php';
|
||||
self::$installed = $required;
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array() && !$copiedLocalDir) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Stripe\\' => array($vendorDir . '/stripe/stripe-php/lib'),
|
||||
);
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit40aa654f2e66c20881ae0572fe987a10
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit40aa654f2e66c20881ae0572fe987a10', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit40aa654f2e66c20881ae0572fe987a10', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit40aa654f2e66c20881ae0572fe987a10::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit40aa654f2e66c20881ae0572fe987a10
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'S' =>
|
||||
array (
|
||||
'Stripe\\' => 7,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Stripe\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/stripe/stripe-php/lib',
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit40aa654f2e66c20881ae0572fe987a10::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit40aa654f2e66c20881ae0572fe987a10::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit40aa654f2e66c20881ae0572fe987a10::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "stripe/stripe-php",
|
||||
"version": "v16.6.0",
|
||||
"version_normalized": "16.6.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/stripe/stripe-php.git",
|
||||
"reference": "d6de0a536f00b5c5c74f36b8f4d0d93b035499ff"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/d6de0a536f00b5c5c74f36b8f4d0d93b035499ff",
|
||||
"reference": "d6de0a536f00b5c5c74f36b8f4d0d93b035499ff",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=5.6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "3.5.0",
|
||||
"phpstan/phpstan": "^1.2",
|
||||
"phpunit/phpunit": "^5.7 || ^9.0"
|
||||
},
|
||||
"time": "2025-02-24T22:35:29+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Stripe\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Stripe and contributors",
|
||||
"homepage": "https://github.com/stripe/stripe-php/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Stripe PHP Library",
|
||||
"homepage": "https://stripe.com/",
|
||||
"keywords": [
|
||||
"api",
|
||||
"payment processing",
|
||||
"stripe"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/stripe/stripe-php/issues",
|
||||
"source": "https://github.com/stripe/stripe-php/tree/v16.6.0"
|
||||
},
|
||||
"install-path": "../stripe/stripe-php"
|
||||
}
|
||||
],
|
||||
"dev": true,
|
||||
"dev-package-names": []
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => '__root__',
|
||||
'pretty_version' => '1.0.0+no-version-set',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => null,
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev' => true,
|
||||
),
|
||||
'versions' => array(
|
||||
'__root__' => array(
|
||||
'pretty_version' => '1.0.0+no-version-set',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => null,
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'stripe/stripe-php' => array(
|
||||
'pretty_version' => 'v16.6.0',
|
||||
'version' => '16.6.0.0',
|
||||
'reference' => 'd6de0a536f00b5c5c74f36b8f4d0d93b035499ff',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../stripe/stripe-php',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
// platform_check.php @generated by Composer
|
||||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 50600)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||
} elseif (!headers_sent()) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Ignore build files
|
||||
build/*
|
||||
|
||||
# Mac OS X dumps these all over the place.
|
||||
.DS_Store
|
||||
|
||||
# Ignore the SimpleTest library if it is installed to /test/.
|
||||
/test/simpletest/
|
||||
|
||||
# Ignore the /vendor/ directory for people using composer
|
||||
/vendor/
|
||||
|
||||
# If the vendor directory isn't being commited the composer.lock file should also be ignored
|
||||
composer.lock
|
||||
|
||||
# Ignore IDE's configuration files
|
||||
.idea
|
||||
|
||||
# Ignore PHP CS Fixer local config and cache
|
||||
.php_cs
|
||||
.php_cs.cache
|
||||
.php-cs-fixer.cache
|
||||
|
||||
# Ignore PHPStan local config
|
||||
.phpstan.neon
|
||||
|
||||
# Ignore phpDocumentor's local config and artifacts
|
||||
.phpdoc/*
|
||||
phpdoc.xml
|
||||
|
||||
# Ignore cached PHPUnit results.
|
||||
.phpunit.result.cache
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2019 Stripe, Inc. (https://stripe.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
|
@ -0,0 +1 @@
|
|||
v1505
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
# Stripe PHP bindings
|
||||
|
||||
[](https://github.com/stripe/stripe-php/actions?query=branch%3Amaster)
|
||||
[](https://packagist.org/packages/stripe/stripe-php)
|
||||
[](https://packagist.org/packages/stripe/stripe-php)
|
||||
[](https://packagist.org/packages/stripe/stripe-php)
|
||||
|
||||
The Stripe PHP library provides convenient access to the Stripe API from
|
||||
applications written in the PHP language. It includes a pre-defined set of
|
||||
classes for API resources that initialize themselves dynamically from API
|
||||
responses which makes it compatible with a wide range of versions of the Stripe
|
||||
API.
|
||||
|
||||
## Requirements
|
||||
|
||||
PHP 5.6.0 and later.
|
||||
|
||||
## Composer
|
||||
|
||||
You can install the bindings via [Composer](http://getcomposer.org/). Run the following command:
|
||||
|
||||
```bash
|
||||
composer require stripe/stripe-php
|
||||
```
|
||||
|
||||
To use the bindings, use Composer's [autoload](https://getcomposer.org/doc/01-basic-usage.md#autoloading):
|
||||
|
||||
```php
|
||||
require_once 'vendor/autoload.php';
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
|
||||
If you do not wish to use Composer, you can download the [latest release](https://github.com/stripe/stripe-php/releases). Then, to use the bindings, include the `init.php` file.
|
||||
|
||||
```php
|
||||
require_once '/path/to/stripe-php/init.php';
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
The bindings require the following extensions in order to work properly:
|
||||
|
||||
- [`curl`](https://secure.php.net/manual/en/book.curl.php), although you can use your own non-cURL client if you prefer
|
||||
- [`json`](https://secure.php.net/manual/en/book.json.php)
|
||||
- [`mbstring`](https://secure.php.net/manual/en/book.mbstring.php) (Multibyte String)
|
||||
|
||||
If you use Composer, these dependencies should be handled automatically. If you install manually, you'll want to make sure that these extensions are available.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Simple usage looks like:
|
||||
|
||||
```php
|
||||
$stripe = new \Stripe\StripeClient('sk_test_BQokikJOvBiI2HlWgH4olfQ2');
|
||||
$customer = $stripe->customers->create([
|
||||
'description' => 'example customer',
|
||||
'email' => 'email@example.com',
|
||||
'payment_method' => 'pm_card_visa',
|
||||
]);
|
||||
echo $customer;
|
||||
```
|
||||
|
||||
### Client/service patterns vs legacy patterns
|
||||
|
||||
You can continue to use the legacy integration patterns used prior to version [7.33.0](https://github.com/stripe/stripe-php/blob/master/CHANGELOG.md#7330---2020-05-14). Review the [migration guide](https://github.com/stripe/stripe-php/wiki/Migration-to-StripeClient-and-services-in-7.33.0) for the backwards-compatible client/services pattern changes.
|
||||
|
||||
## Documentation
|
||||
|
||||
See the [PHP API docs](https://stripe.com/docs/api/?lang=php#intro).
|
||||
|
||||
See [video demonstrations][youtube-playlist] covering how to use the library.
|
||||
|
||||
## Legacy Version Support
|
||||
|
||||
### PHP 5.4 & 5.5
|
||||
|
||||
If you are using PHP 5.4 or 5.5, you should consider upgrading your environment as those versions have been past end of life since September 2015 and July 2016 respectively.
|
||||
Otherwise, you can still use Stripe by downloading stripe-php v6.43.1 ([zip](https://github.com/stripe/stripe-php/archive/v6.43.1.zip), [tar.gz](https://github.com/stripe/stripe-php/archive/6.43.1.tar.gz)) from our [releases page](https://github.com/stripe/stripe-php/releases). This version will work but might not support recent features we added since the version was released and upgrading PHP is the best course of action.
|
||||
|
||||
### PHP 5.3
|
||||
|
||||
If you are using PHP 5.3, you should upgrade your environment as this version has been past end of life since August 2014.
|
||||
Otherwise, you can download v5.9.2 ([zip](https://github.com/stripe/stripe-php/archive/v5.9.2.zip), [tar.gz](https://github.com/stripe/stripe-php/archive/v5.9.2.tar.gz)) from our [releases page](https://github.com/stripe/stripe-php/releases). This version will continue to work with new versions of the Stripe API for all common uses.
|
||||
|
||||
## Custom Request Timeouts
|
||||
|
||||
> **Note**
|
||||
> We do not recommend decreasing the timeout for non-read-only calls (e.g. charge creation), since even if you locally timeout, the request on Stripe's side can still complete. If you are decreasing timeouts on these calls, make sure to use [idempotency tokens](https://stripe.com/docs/api/?lang=php#idempotent_requests) to avoid executing the same transaction twice as a result of timeout retry logic.
|
||||
|
||||
To modify request timeouts (connect or total, in seconds) you'll need to tell the API client to use a CurlClient other than its default. You'll set the timeouts in that CurlClient.
|
||||
|
||||
```php
|
||||
// set up your tweaked Curl client
|
||||
$curl = new \Stripe\HttpClient\CurlClient();
|
||||
$curl->setTimeout(10); // default is \Stripe\HttpClient\CurlClient::DEFAULT_TIMEOUT
|
||||
$curl->setConnectTimeout(5); // default is \Stripe\HttpClient\CurlClient::DEFAULT_CONNECT_TIMEOUT
|
||||
|
||||
echo $curl->getTimeout(); // 10
|
||||
echo $curl->getConnectTimeout(); // 5
|
||||
|
||||
// tell Stripe to use the tweaked client
|
||||
\Stripe\ApiRequestor::setHttpClient($curl);
|
||||
|
||||
// use the Stripe API client as you normally would
|
||||
```
|
||||
|
||||
## Custom cURL Options (e.g. proxies)
|
||||
|
||||
Need to set a proxy for your requests? Pass in the requisite `CURLOPT_*` array to the CurlClient constructor, using the same syntax as `curl_stopt_array()`. This will set the default cURL options for each HTTP request made by the SDK, though many more common options (e.g. timeouts; see above on how to set those) will be overridden by the client even if set here.
|
||||
|
||||
```php
|
||||
// set up your tweaked Curl client
|
||||
$curl = new \Stripe\HttpClient\CurlClient([CURLOPT_PROXY => 'proxy.local:80']);
|
||||
// tell Stripe to use the tweaked client
|
||||
\Stripe\ApiRequestor::setHttpClient($curl);
|
||||
```
|
||||
|
||||
Alternately, a callable can be passed to the CurlClient constructor that returns the above array based on request inputs. See `testDefaultOptions()` in `tests/CurlClientTest.php` for an example of this behavior. Note that the callable is called at the beginning of every API request, before the request is sent.
|
||||
|
||||
### Configuring a Logger
|
||||
|
||||
The library does minimal logging, but it can be configured
|
||||
with a [`PSR-3` compatible logger][psr3] so that messages
|
||||
end up there instead of `error_log`:
|
||||
|
||||
```php
|
||||
\Stripe\Stripe::setLogger($logger);
|
||||
```
|
||||
|
||||
### Accessing response data
|
||||
|
||||
You can access the data from the last API response on any object via `getLastResponse()`.
|
||||
|
||||
```php
|
||||
$customer = $stripe->customers->create([
|
||||
'description' => 'example customer',
|
||||
]);
|
||||
echo $customer->getLastResponse()->headers['Request-Id'];
|
||||
```
|
||||
|
||||
### SSL / TLS compatibility issues
|
||||
|
||||
Stripe's API now requires that [all connections use TLS 1.2](https://stripe.com/blog/upgrading-tls). Some systems (most notably some older CentOS and RHEL versions) are capable of using TLS 1.2 but will use TLS 1.0 or 1.1 by default. In this case, you'd get an `invalid_request_error` with the following error message: "Stripe no longer supports API requests made with TLS 1.0. Please initiate HTTPS connections with TLS 1.2 or later. You can learn more about this at [https://stripe.com/blog/upgrading-tls](https://stripe.com/blog/upgrading-tls).".
|
||||
|
||||
The recommended course of action is to [upgrade your cURL and OpenSSL packages](https://support.stripe.com/questions/how-do-i-upgrade-my-stripe-integration-from-tls-1-0-to-tls-1-2#php) so that TLS 1.2 is used by default, but if that is not possible, you might be able to solve the issue by setting the `CURLOPT_SSLVERSION` option to either `CURL_SSLVERSION_TLSv1` or `CURL_SSLVERSION_TLSv1_2`:
|
||||
|
||||
```php
|
||||
$curl = new \Stripe\HttpClient\CurlClient([CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1]);
|
||||
\Stripe\ApiRequestor::setHttpClient($curl);
|
||||
```
|
||||
|
||||
### Per-request Configuration
|
||||
|
||||
For apps that need to use multiple keys during the lifetime of a process, like
|
||||
one that uses [Stripe Connect][connect], it's also possible to set a
|
||||
per-request key and/or account:
|
||||
|
||||
```php
|
||||
$customers = $stripe->customers->all([],[
|
||||
'api_key' => 'sk_test_...',
|
||||
'stripe_account' => 'acct_...'
|
||||
]);
|
||||
|
||||
$stripe->customers->retrieve('cus_123456789', [], [
|
||||
'api_key' => 'sk_test_...',
|
||||
'stripe_account' => 'acct_...'
|
||||
]);
|
||||
```
|
||||
|
||||
### Configuring CA Bundles
|
||||
|
||||
By default, the library will use its own internal bundle of known CA
|
||||
certificates, but it's possible to configure your own:
|
||||
|
||||
```php
|
||||
\Stripe\Stripe::setCABundlePath("path/to/ca/bundle");
|
||||
```
|
||||
|
||||
### Configuring Automatic Retries
|
||||
|
||||
The library can be configured to automatically retry requests that fail due to
|
||||
an intermittent network problem:
|
||||
|
||||
```php
|
||||
\Stripe\Stripe::setMaxNetworkRetries(2);
|
||||
```
|
||||
|
||||
[Idempotency keys][idempotency-keys] are added to requests to guarantee that
|
||||
retries are safe.
|
||||
|
||||
### Telemetry
|
||||
|
||||
By default, the library sends telemetry to Stripe regarding request latency and feature usage. These
|
||||
numbers help Stripe improve the overall latency of its API for all users, and
|
||||
improve popular features.
|
||||
|
||||
You can disable this behavior if you prefer:
|
||||
|
||||
```php
|
||||
\Stripe\Stripe::setEnableTelemetry(false);
|
||||
```
|
||||
|
||||
### Beta SDKs
|
||||
|
||||
Stripe has features in the beta phase that can be accessed via the beta version of this package.
|
||||
We would love for you to try these and share feedback with us before these features reach the stable phase.
|
||||
Use the `composer require` command with an exact version specified to install the beta version of the stripe-php pacakge.
|
||||
|
||||
```bash
|
||||
composer require stripe/stripe-php:v9.2.0-beta.1
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> There can be breaking changes between beta versions. Therefore we recommend pinning the package version to a specific beta version in your composer.json file. This way you can install the same version each time without breaking changes unless you are intentionally looking for the latest beta version.
|
||||
|
||||
We highly recommend keeping an eye on when the beta feature you are interested in goes from beta to stable so that you can move from using a beta version of the SDK to the stable version.
|
||||
|
||||
If your beta feature requires a `Stripe-Version` header to be sent, set the `apiVersion` property of `config` object by using the function `addBetaVersion`:
|
||||
|
||||
```php
|
||||
Stripe::addBetaVersion("feature_beta", "v3");
|
||||
```
|
||||
|
||||
### Custom requests
|
||||
|
||||
If you would like to send a request to an undocumented API (for example you are in a private beta), or if you prefer to bypass the method definitions in the library and specify your request details directly, you can use the `rawRequest` method on the StripeClient.
|
||||
|
||||
```php
|
||||
$stripe = new \Stripe\StripeClient('sk_test_xyz');
|
||||
$response = $stripe->rawRequest('post', '/v1/beta_endpoint', [
|
||||
"caveat": "emptor"
|
||||
], [
|
||||
"stripe_version" => "2022-11_15",
|
||||
]);
|
||||
// $response->body is a string, you can call $stripe->deserialize to get a \Stripe\StripeObject.
|
||||
$obj = $stripe->deserialize($response->body);
|
||||
|
||||
// For GET requests, the params argument must be null, and you should write the query string explicitly.
|
||||
$get_response = $stripe->rawRequest('get', '/v1/beta_endpoint?caveat=emptor', null, [
|
||||
"stripe_version" => "2022-11_15",
|
||||
]);
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
New features and bug fixes are released on the latest major version of the Stripe PHP library. If you are on an older major version, we recommend that you upgrade to the latest in order to use the new features and bug fixes including those for security vulnerabilities. Older major versions of the package will continue to be available for use, but will not be receiving any updates.
|
||||
|
||||
## Development
|
||||
|
||||
[Contribution guidelines for this project](CONTRIBUTING.md)
|
||||
|
||||
We use [just](https://github.com/casey/just) for conveniently running development tasks. You can use them directly, or copy the commands out of the `justfile`. To our help docs, run `just`.
|
||||
|
||||
To get started, install [Composer][composer]. For example, on Mac OS:
|
||||
|
||||
```bash
|
||||
brew install composer
|
||||
```
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
just install
|
||||
# or: composer install
|
||||
```
|
||||
|
||||
The test suite depends on [stripe-mock], so make sure to fetch and run it from a
|
||||
background terminal ([stripe-mock's README][stripe-mock] also contains
|
||||
instructions for installing via Homebrew and other methods):
|
||||
|
||||
```bash
|
||||
go install github.com/stripe/stripe-mock@latest
|
||||
stripe-mock
|
||||
```
|
||||
|
||||
Install dependencies as mentioned above (which will resolve [PHPUnit](http://packagist.org/packages/phpunit/phpunit)), then you can run the test suite:
|
||||
|
||||
```bash
|
||||
just test
|
||||
# or: ./vendor/bin/phpunit
|
||||
```
|
||||
|
||||
Or to run an individual test file:
|
||||
|
||||
```bash
|
||||
just test tests/Stripe/UtilTest.php
|
||||
# or: ./vendor/bin/phpunit tests/Stripe/UtilTest.php
|
||||
```
|
||||
|
||||
Update bundled CA certificates from the [Mozilla cURL release][curl]:
|
||||
|
||||
```bash
|
||||
./update_certs.php
|
||||
```
|
||||
|
||||
The library uses [PHP CS Fixer][php-cs-fixer] for code formatting. Code must be formatted before PRs are submitted, otherwise CI will fail. Run the formatter with:
|
||||
|
||||
```bash
|
||||
just format
|
||||
# or: ./vendor/bin/php-cs-fixer fix -v .
|
||||
```
|
||||
|
||||
## Attention plugin developers
|
||||
|
||||
Are you writing a plugin that integrates Stripe and embeds our library? Then please use the `setAppInfo` function to identify your plugin. For example:
|
||||
|
||||
```php
|
||||
\Stripe\Stripe::setAppInfo("MyAwesomePlugin", "1.2.34", "https://myawesomeplugin.info");
|
||||
```
|
||||
|
||||
The method should be called once, before any request is sent to the API. The second and third parameters are optional.
|
||||
|
||||
### SSL / TLS configuration option
|
||||
|
||||
See the "SSL / TLS compatibility issues" paragraph above for full context. If you want to ensure that your plugin can be used on all systems, you should add a configuration option to let your users choose between different values for `CURLOPT_SSLVERSION`: none (default), `CURL_SSLVERSION_TLSv1` and `CURL_SSLVERSION_TLSv1_2`.
|
||||
|
||||
[composer]: https://getcomposer.org/
|
||||
[connect]: https://stripe.com/connect
|
||||
[curl]: http://curl.haxx.se/docs/caextract.html
|
||||
[idempotency-keys]: https://stripe.com/docs/api/?lang=php#idempotent_requests
|
||||
[php-cs-fixer]: https://github.com/FriendsOfPHP/PHP-CS-Fixer
|
||||
[psr3]: http://www.php-fig.org/psr/psr-3/
|
||||
[stripe-mock]: https://github.com/stripe/stripe-mock
|
||||
[youtube-playlist]: https://www.youtube.com/playlist?list=PLy1nL-pvL2M6cUbiHrfMkXxZ9j9SGBxFE
|
||||
|
|
@ -0,0 +1 @@
|
|||
16.6.0
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "stripe/stripe-php",
|
||||
"description": "Stripe PHP Library",
|
||||
"keywords": [
|
||||
"stripe",
|
||||
"payment processing",
|
||||
"api"
|
||||
],
|
||||
"homepage": "https://stripe.com/",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Stripe and contributors",
|
||||
"homepage": "https://github.com/stripe/stripe-php/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.6.0",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.7 || ^9.0",
|
||||
"friendsofphp/php-cs-fixer": "3.5.0",
|
||||
"phpstan/phpstan": "^1.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Stripe\\": "lib/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Stripe\\": [
|
||||
"tests/",
|
||||
"tests/Stripe/"
|
||||
]
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,396 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . '/lib/Util/ApiVersion.php';
|
||||
|
||||
// Stripe singleton
|
||||
require __DIR__ . '/lib/Stripe.php';
|
||||
|
||||
// Utilities
|
||||
require __DIR__ . '/lib/Util/CaseInsensitiveArray.php';
|
||||
require __DIR__ . '/lib/Util/LoggerInterface.php';
|
||||
require __DIR__ . '/lib/Util/DefaultLogger.php';
|
||||
require __DIR__ . '/lib/Util/RandomGenerator.php';
|
||||
require __DIR__ . '/lib/Util/RequestOptions.php';
|
||||
require __DIR__ . '/lib/Util/Set.php';
|
||||
require __DIR__ . '/lib/Util/Util.php';
|
||||
require __DIR__ . '/lib/Util/EventTypes.php';
|
||||
require __DIR__ . '/lib/Util/ObjectTypes.php';
|
||||
|
||||
// HttpClient
|
||||
require __DIR__ . '/lib/HttpClient/ClientInterface.php';
|
||||
require __DIR__ . '/lib/HttpClient/StreamingClientInterface.php';
|
||||
require __DIR__ . '/lib/HttpClient/CurlClient.php';
|
||||
|
||||
// Exceptions
|
||||
require __DIR__ . '/lib/Exception/ExceptionInterface.php';
|
||||
require __DIR__ . '/lib/Exception/ApiErrorException.php';
|
||||
require __DIR__ . '/lib/Exception/ApiConnectionException.php';
|
||||
require __DIR__ . '/lib/Exception/AuthenticationException.php';
|
||||
require __DIR__ . '/lib/Exception/BadMethodCallException.php';
|
||||
require __DIR__ . '/lib/Exception/CardException.php';
|
||||
require __DIR__ . '/lib/Exception/IdempotencyException.php';
|
||||
require __DIR__ . '/lib/Exception/InvalidArgumentException.php';
|
||||
require __DIR__ . '/lib/Exception/InvalidRequestException.php';
|
||||
require __DIR__ . '/lib/Exception/PermissionException.php';
|
||||
require __DIR__ . '/lib/Exception/RateLimitException.php';
|
||||
require __DIR__ . '/lib/Exception/SignatureVerificationException.php';
|
||||
require __DIR__ . '/lib/Exception/UnexpectedValueException.php';
|
||||
require __DIR__ . '/lib/Exception/UnknownApiErrorException.php';
|
||||
|
||||
// OAuth exceptions
|
||||
require __DIR__ . '/lib/Exception/OAuth/ExceptionInterface.php';
|
||||
require __DIR__ . '/lib/Exception/OAuth/OAuthErrorException.php';
|
||||
require __DIR__ . '/lib/Exception/OAuth/InvalidClientException.php';
|
||||
require __DIR__ . '/lib/Exception/OAuth/InvalidGrantException.php';
|
||||
require __DIR__ . '/lib/Exception/OAuth/InvalidRequestException.php';
|
||||
require __DIR__ . '/lib/Exception/OAuth/InvalidScopeException.php';
|
||||
require __DIR__ . '/lib/Exception/OAuth/UnknownOAuthErrorException.php';
|
||||
require __DIR__ . '/lib/Exception/OAuth/UnsupportedGrantTypeException.php';
|
||||
require __DIR__ . '/lib/Exception/OAuth/UnsupportedResponseTypeException.php';
|
||||
|
||||
// API operations
|
||||
require __DIR__ . '/lib/ApiOperations/All.php';
|
||||
require __DIR__ . '/lib/ApiOperations/Create.php';
|
||||
require __DIR__ . '/lib/ApiOperations/Delete.php';
|
||||
require __DIR__ . '/lib/ApiOperations/NestedResource.php';
|
||||
require __DIR__ . '/lib/ApiOperations/Request.php';
|
||||
require __DIR__ . '/lib/ApiOperations/Retrieve.php';
|
||||
require __DIR__ . '/lib/ApiOperations/Search.php';
|
||||
require __DIR__ . '/lib/ApiOperations/SingletonRetrieve.php';
|
||||
require __DIR__ . '/lib/ApiOperations/Update.php';
|
||||
|
||||
// Plumbing
|
||||
require __DIR__ . '/lib/ApiResponse.php';
|
||||
require __DIR__ . '/lib/RequestTelemetry.php';
|
||||
require __DIR__ . '/lib/StripeObject.php';
|
||||
require __DIR__ . '/lib/ApiRequestor.php';
|
||||
require __DIR__ . '/lib/ApiResource.php';
|
||||
require __DIR__ . '/lib/SingletonApiResource.php';
|
||||
require __DIR__ . '/lib/Service/ServiceNavigatorTrait.php';
|
||||
require __DIR__ . '/lib/Service/AbstractService.php';
|
||||
require __DIR__ . '/lib/Service/AbstractServiceFactory.php';
|
||||
require __DIR__ . '/lib/V2/Event.php';
|
||||
require __DIR__ . '/lib/ThinEvent.php';
|
||||
require __DIR__ . '/lib/Reason.php';
|
||||
require __DIR__ . '/lib/RelatedObject.php';
|
||||
require __DIR__ . '/lib/Collection.php';
|
||||
require __DIR__ . '/lib/V2/Collection.php';
|
||||
require __DIR__ . '/lib/SearchResult.php';
|
||||
require __DIR__ . '/lib/ErrorObject.php';
|
||||
require __DIR__ . '/lib/Issuing/CardDetails.php';
|
||||
|
||||
// StripeClient
|
||||
require __DIR__ . '/lib/BaseStripeClientInterface.php';
|
||||
require __DIR__ . '/lib/StripeClientInterface.php';
|
||||
require __DIR__ . '/lib/StripeStreamingClientInterface.php';
|
||||
require __DIR__ . '/lib/BaseStripeClient.php';
|
||||
require __DIR__ . '/lib/StripeClient.php';
|
||||
|
||||
// The beginning of the section generated from our OpenAPI spec
|
||||
require __DIR__ . '/lib/Account.php';
|
||||
require __DIR__ . '/lib/AccountLink.php';
|
||||
require __DIR__ . '/lib/AccountSession.php';
|
||||
require __DIR__ . '/lib/ApplePayDomain.php';
|
||||
require __DIR__ . '/lib/Application.php';
|
||||
require __DIR__ . '/lib/ApplicationFee.php';
|
||||
require __DIR__ . '/lib/ApplicationFeeRefund.php';
|
||||
require __DIR__ . '/lib/Apps/Secret.php';
|
||||
require __DIR__ . '/lib/Balance.php';
|
||||
require __DIR__ . '/lib/BalanceTransaction.php';
|
||||
require __DIR__ . '/lib/BankAccount.php';
|
||||
require __DIR__ . '/lib/Billing/Alert.php';
|
||||
require __DIR__ . '/lib/Billing/AlertTriggered.php';
|
||||
require __DIR__ . '/lib/Billing/CreditBalanceSummary.php';
|
||||
require __DIR__ . '/lib/Billing/CreditBalanceTransaction.php';
|
||||
require __DIR__ . '/lib/Billing/CreditGrant.php';
|
||||
require __DIR__ . '/lib/Billing/Meter.php';
|
||||
require __DIR__ . '/lib/Billing/MeterEvent.php';
|
||||
require __DIR__ . '/lib/Billing/MeterEventAdjustment.php';
|
||||
require __DIR__ . '/lib/Billing/MeterEventSummary.php';
|
||||
require __DIR__ . '/lib/BillingPortal/Configuration.php';
|
||||
require __DIR__ . '/lib/BillingPortal/Session.php';
|
||||
require __DIR__ . '/lib/Capability.php';
|
||||
require __DIR__ . '/lib/Card.php';
|
||||
require __DIR__ . '/lib/CashBalance.php';
|
||||
require __DIR__ . '/lib/Charge.php';
|
||||
require __DIR__ . '/lib/Checkout/Session.php';
|
||||
require __DIR__ . '/lib/Climate/Order.php';
|
||||
require __DIR__ . '/lib/Climate/Product.php';
|
||||
require __DIR__ . '/lib/Climate/Supplier.php';
|
||||
require __DIR__ . '/lib/ConfirmationToken.php';
|
||||
require __DIR__ . '/lib/ConnectCollectionTransfer.php';
|
||||
require __DIR__ . '/lib/CountrySpec.php';
|
||||
require __DIR__ . '/lib/Coupon.php';
|
||||
require __DIR__ . '/lib/CreditNote.php';
|
||||
require __DIR__ . '/lib/CreditNoteLineItem.php';
|
||||
require __DIR__ . '/lib/Customer.php';
|
||||
require __DIR__ . '/lib/CustomerBalanceTransaction.php';
|
||||
require __DIR__ . '/lib/CustomerCashBalanceTransaction.php';
|
||||
require __DIR__ . '/lib/CustomerSession.php';
|
||||
require __DIR__ . '/lib/Discount.php';
|
||||
require __DIR__ . '/lib/Dispute.php';
|
||||
require __DIR__ . '/lib/Entitlements/ActiveEntitlement.php';
|
||||
require __DIR__ . '/lib/Entitlements/ActiveEntitlementSummary.php';
|
||||
require __DIR__ . '/lib/Entitlements/Feature.php';
|
||||
require __DIR__ . '/lib/EphemeralKey.php';
|
||||
require __DIR__ . '/lib/Event.php';
|
||||
require __DIR__ . '/lib/EventData/V1BillingMeterErrorReportTriggeredEventData.php';
|
||||
require __DIR__ . '/lib/EventData/V1BillingMeterNoMeterFoundEventData.php';
|
||||
require __DIR__ . '/lib/Events/V1BillingMeterErrorReportTriggeredEvent.php';
|
||||
require __DIR__ . '/lib/Events/V1BillingMeterNoMeterFoundEvent.php';
|
||||
require __DIR__ . '/lib/Exception/TemporarySessionExpiredException.php';
|
||||
require __DIR__ . '/lib/ExchangeRate.php';
|
||||
require __DIR__ . '/lib/File.php';
|
||||
require __DIR__ . '/lib/FileLink.php';
|
||||
require __DIR__ . '/lib/FinancialConnections/Account.php';
|
||||
require __DIR__ . '/lib/FinancialConnections/AccountOwner.php';
|
||||
require __DIR__ . '/lib/FinancialConnections/AccountOwnership.php';
|
||||
require __DIR__ . '/lib/FinancialConnections/Session.php';
|
||||
require __DIR__ . '/lib/FinancialConnections/Transaction.php';
|
||||
require __DIR__ . '/lib/Forwarding/Request.php';
|
||||
require __DIR__ . '/lib/FundingInstructions.php';
|
||||
require __DIR__ . '/lib/Identity/VerificationReport.php';
|
||||
require __DIR__ . '/lib/Identity/VerificationSession.php';
|
||||
require __DIR__ . '/lib/Invoice.php';
|
||||
require __DIR__ . '/lib/InvoiceItem.php';
|
||||
require __DIR__ . '/lib/InvoiceLineItem.php';
|
||||
require __DIR__ . '/lib/InvoiceRenderingTemplate.php';
|
||||
require __DIR__ . '/lib/Issuing/Authorization.php';
|
||||
require __DIR__ . '/lib/Issuing/Card.php';
|
||||
require __DIR__ . '/lib/Issuing/Cardholder.php';
|
||||
require __DIR__ . '/lib/Issuing/Dispute.php';
|
||||
require __DIR__ . '/lib/Issuing/PersonalizationDesign.php';
|
||||
require __DIR__ . '/lib/Issuing/PhysicalBundle.php';
|
||||
require __DIR__ . '/lib/Issuing/Token.php';
|
||||
require __DIR__ . '/lib/Issuing/Transaction.php';
|
||||
require __DIR__ . '/lib/LineItem.php';
|
||||
require __DIR__ . '/lib/LoginLink.php';
|
||||
require __DIR__ . '/lib/Mandate.php';
|
||||
require __DIR__ . '/lib/PaymentIntent.php';
|
||||
require __DIR__ . '/lib/PaymentLink.php';
|
||||
require __DIR__ . '/lib/PaymentMethod.php';
|
||||
require __DIR__ . '/lib/PaymentMethodConfiguration.php';
|
||||
require __DIR__ . '/lib/PaymentMethodDomain.php';
|
||||
require __DIR__ . '/lib/Payout.php';
|
||||
require __DIR__ . '/lib/Person.php';
|
||||
require __DIR__ . '/lib/Plan.php';
|
||||
require __DIR__ . '/lib/Price.php';
|
||||
require __DIR__ . '/lib/Product.php';
|
||||
require __DIR__ . '/lib/ProductFeature.php';
|
||||
require __DIR__ . '/lib/PromotionCode.php';
|
||||
require __DIR__ . '/lib/Quote.php';
|
||||
require __DIR__ . '/lib/Radar/EarlyFraudWarning.php';
|
||||
require __DIR__ . '/lib/Radar/ValueList.php';
|
||||
require __DIR__ . '/lib/Radar/ValueListItem.php';
|
||||
require __DIR__ . '/lib/Refund.php';
|
||||
require __DIR__ . '/lib/Reporting/ReportRun.php';
|
||||
require __DIR__ . '/lib/Reporting/ReportType.php';
|
||||
require __DIR__ . '/lib/ReserveTransaction.php';
|
||||
require __DIR__ . '/lib/Review.php';
|
||||
require __DIR__ . '/lib/Service/AccountLinkService.php';
|
||||
require __DIR__ . '/lib/Service/AccountService.php';
|
||||
require __DIR__ . '/lib/Service/AccountSessionService.php';
|
||||
require __DIR__ . '/lib/Service/ApplePayDomainService.php';
|
||||
require __DIR__ . '/lib/Service/ApplicationFeeService.php';
|
||||
require __DIR__ . '/lib/Service/Apps/AppsServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/Apps/SecretService.php';
|
||||
require __DIR__ . '/lib/Service/BalanceService.php';
|
||||
require __DIR__ . '/lib/Service/BalanceTransactionService.php';
|
||||
require __DIR__ . '/lib/Service/Billing/AlertService.php';
|
||||
require __DIR__ . '/lib/Service/Billing/BillingServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/Billing/CreditBalanceSummaryService.php';
|
||||
require __DIR__ . '/lib/Service/Billing/CreditBalanceTransactionService.php';
|
||||
require __DIR__ . '/lib/Service/Billing/CreditGrantService.php';
|
||||
require __DIR__ . '/lib/Service/Billing/MeterEventAdjustmentService.php';
|
||||
require __DIR__ . '/lib/Service/Billing/MeterEventService.php';
|
||||
require __DIR__ . '/lib/Service/Billing/MeterService.php';
|
||||
require __DIR__ . '/lib/Service/BillingPortal/BillingPortalServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/BillingPortal/ConfigurationService.php';
|
||||
require __DIR__ . '/lib/Service/BillingPortal/SessionService.php';
|
||||
require __DIR__ . '/lib/Service/ChargeService.php';
|
||||
require __DIR__ . '/lib/Service/Checkout/CheckoutServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/Checkout/SessionService.php';
|
||||
require __DIR__ . '/lib/Service/Climate/ClimateServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/Climate/OrderService.php';
|
||||
require __DIR__ . '/lib/Service/Climate/ProductService.php';
|
||||
require __DIR__ . '/lib/Service/Climate/SupplierService.php';
|
||||
require __DIR__ . '/lib/Service/ConfirmationTokenService.php';
|
||||
require __DIR__ . '/lib/Service/CoreServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/CountrySpecService.php';
|
||||
require __DIR__ . '/lib/Service/CouponService.php';
|
||||
require __DIR__ . '/lib/Service/CreditNoteService.php';
|
||||
require __DIR__ . '/lib/Service/CustomerService.php';
|
||||
require __DIR__ . '/lib/Service/CustomerSessionService.php';
|
||||
require __DIR__ . '/lib/Service/DisputeService.php';
|
||||
require __DIR__ . '/lib/Service/Entitlements/ActiveEntitlementService.php';
|
||||
require __DIR__ . '/lib/Service/Entitlements/EntitlementsServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/Entitlements/FeatureService.php';
|
||||
require __DIR__ . '/lib/Service/EphemeralKeyService.php';
|
||||
require __DIR__ . '/lib/Service/EventService.php';
|
||||
require __DIR__ . '/lib/Service/ExchangeRateService.php';
|
||||
require __DIR__ . '/lib/Service/FileLinkService.php';
|
||||
require __DIR__ . '/lib/Service/FileService.php';
|
||||
require __DIR__ . '/lib/Service/FinancialConnections/AccountService.php';
|
||||
require __DIR__ . '/lib/Service/FinancialConnections/FinancialConnectionsServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/FinancialConnections/SessionService.php';
|
||||
require __DIR__ . '/lib/Service/FinancialConnections/TransactionService.php';
|
||||
require __DIR__ . '/lib/Service/Forwarding/ForwardingServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/Forwarding/RequestService.php';
|
||||
require __DIR__ . '/lib/Service/Identity/IdentityServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/Identity/VerificationReportService.php';
|
||||
require __DIR__ . '/lib/Service/Identity/VerificationSessionService.php';
|
||||
require __DIR__ . '/lib/Service/InvoiceItemService.php';
|
||||
require __DIR__ . '/lib/Service/InvoiceRenderingTemplateService.php';
|
||||
require __DIR__ . '/lib/Service/InvoiceService.php';
|
||||
require __DIR__ . '/lib/Service/Issuing/AuthorizationService.php';
|
||||
require __DIR__ . '/lib/Service/Issuing/CardService.php';
|
||||
require __DIR__ . '/lib/Service/Issuing/CardholderService.php';
|
||||
require __DIR__ . '/lib/Service/Issuing/DisputeService.php';
|
||||
require __DIR__ . '/lib/Service/Issuing/IssuingServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/Issuing/PersonalizationDesignService.php';
|
||||
require __DIR__ . '/lib/Service/Issuing/PhysicalBundleService.php';
|
||||
require __DIR__ . '/lib/Service/Issuing/TokenService.php';
|
||||
require __DIR__ . '/lib/Service/Issuing/TransactionService.php';
|
||||
require __DIR__ . '/lib/Service/MandateService.php';
|
||||
require __DIR__ . '/lib/Service/PaymentIntentService.php';
|
||||
require __DIR__ . '/lib/Service/PaymentLinkService.php';
|
||||
require __DIR__ . '/lib/Service/PaymentMethodConfigurationService.php';
|
||||
require __DIR__ . '/lib/Service/PaymentMethodDomainService.php';
|
||||
require __DIR__ . '/lib/Service/PaymentMethodService.php';
|
||||
require __DIR__ . '/lib/Service/PayoutService.php';
|
||||
require __DIR__ . '/lib/Service/PlanService.php';
|
||||
require __DIR__ . '/lib/Service/PriceService.php';
|
||||
require __DIR__ . '/lib/Service/ProductService.php';
|
||||
require __DIR__ . '/lib/Service/PromotionCodeService.php';
|
||||
require __DIR__ . '/lib/Service/QuoteService.php';
|
||||
require __DIR__ . '/lib/Service/Radar/EarlyFraudWarningService.php';
|
||||
require __DIR__ . '/lib/Service/Radar/RadarServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/Radar/ValueListItemService.php';
|
||||
require __DIR__ . '/lib/Service/Radar/ValueListService.php';
|
||||
require __DIR__ . '/lib/Service/RefundService.php';
|
||||
require __DIR__ . '/lib/Service/Reporting/ReportRunService.php';
|
||||
require __DIR__ . '/lib/Service/Reporting/ReportTypeService.php';
|
||||
require __DIR__ . '/lib/Service/Reporting/ReportingServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/ReviewService.php';
|
||||
require __DIR__ . '/lib/Service/SetupAttemptService.php';
|
||||
require __DIR__ . '/lib/Service/SetupIntentService.php';
|
||||
require __DIR__ . '/lib/Service/ShippingRateService.php';
|
||||
require __DIR__ . '/lib/Service/Sigma/ScheduledQueryRunService.php';
|
||||
require __DIR__ . '/lib/Service/Sigma/SigmaServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/SourceService.php';
|
||||
require __DIR__ . '/lib/Service/SubscriptionItemService.php';
|
||||
require __DIR__ . '/lib/Service/SubscriptionScheduleService.php';
|
||||
require __DIR__ . '/lib/Service/SubscriptionService.php';
|
||||
require __DIR__ . '/lib/Service/Tax/CalculationService.php';
|
||||
require __DIR__ . '/lib/Service/Tax/RegistrationService.php';
|
||||
require __DIR__ . '/lib/Service/Tax/SettingsService.php';
|
||||
require __DIR__ . '/lib/Service/Tax/TaxServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/Tax/TransactionService.php';
|
||||
require __DIR__ . '/lib/Service/TaxCodeService.php';
|
||||
require __DIR__ . '/lib/Service/TaxIdService.php';
|
||||
require __DIR__ . '/lib/Service/TaxRateService.php';
|
||||
require __DIR__ . '/lib/Service/Terminal/ConfigurationService.php';
|
||||
require __DIR__ . '/lib/Service/Terminal/ConnectionTokenService.php';
|
||||
require __DIR__ . '/lib/Service/Terminal/LocationService.php';
|
||||
require __DIR__ . '/lib/Service/Terminal/ReaderService.php';
|
||||
require __DIR__ . '/lib/Service/Terminal/TerminalServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/ConfirmationTokenService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/CustomerService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Issuing/AuthorizationService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Issuing/CardService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Issuing/IssuingServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Issuing/PersonalizationDesignService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Issuing/TransactionService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/RefundService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Terminal/ReaderService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Terminal/TerminalServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/TestClockService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/TestHelpersServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Treasury/InboundTransferService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Treasury/OutboundPaymentService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Treasury/OutboundTransferService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Treasury/ReceivedCreditService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Treasury/ReceivedDebitService.php';
|
||||
require __DIR__ . '/lib/Service/TestHelpers/Treasury/TreasuryServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/TokenService.php';
|
||||
require __DIR__ . '/lib/Service/TopupService.php';
|
||||
require __DIR__ . '/lib/Service/TransferService.php';
|
||||
require __DIR__ . '/lib/Service/Treasury/CreditReversalService.php';
|
||||
require __DIR__ . '/lib/Service/Treasury/DebitReversalService.php';
|
||||
require __DIR__ . '/lib/Service/Treasury/FinancialAccountService.php';
|
||||
require __DIR__ . '/lib/Service/Treasury/InboundTransferService.php';
|
||||
require __DIR__ . '/lib/Service/Treasury/OutboundPaymentService.php';
|
||||
require __DIR__ . '/lib/Service/Treasury/OutboundTransferService.php';
|
||||
require __DIR__ . '/lib/Service/Treasury/ReceivedCreditService.php';
|
||||
require __DIR__ . '/lib/Service/Treasury/ReceivedDebitService.php';
|
||||
require __DIR__ . '/lib/Service/Treasury/TransactionEntryService.php';
|
||||
require __DIR__ . '/lib/Service/Treasury/TransactionService.php';
|
||||
require __DIR__ . '/lib/Service/Treasury/TreasuryServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/V2/Billing/BillingServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/V2/Billing/MeterEventAdjustmentService.php';
|
||||
require __DIR__ . '/lib/Service/V2/Billing/MeterEventService.php';
|
||||
require __DIR__ . '/lib/Service/V2/Billing/MeterEventSessionService.php';
|
||||
require __DIR__ . '/lib/Service/V2/Billing/MeterEventStreamService.php';
|
||||
require __DIR__ . '/lib/Service/V2/Core/CoreServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/V2/Core/EventDestinationService.php';
|
||||
require __DIR__ . '/lib/Service/V2/Core/EventService.php';
|
||||
require __DIR__ . '/lib/Service/V2/V2ServiceFactory.php';
|
||||
require __DIR__ . '/lib/Service/WebhookEndpointService.php';
|
||||
require __DIR__ . '/lib/SetupAttempt.php';
|
||||
require __DIR__ . '/lib/SetupIntent.php';
|
||||
require __DIR__ . '/lib/ShippingRate.php';
|
||||
require __DIR__ . '/lib/Sigma/ScheduledQueryRun.php';
|
||||
require __DIR__ . '/lib/Source.php';
|
||||
require __DIR__ . '/lib/SourceMandateNotification.php';
|
||||
require __DIR__ . '/lib/SourceTransaction.php';
|
||||
require __DIR__ . '/lib/Subscription.php';
|
||||
require __DIR__ . '/lib/SubscriptionItem.php';
|
||||
require __DIR__ . '/lib/SubscriptionSchedule.php';
|
||||
require __DIR__ . '/lib/Tax/Calculation.php';
|
||||
require __DIR__ . '/lib/Tax/CalculationLineItem.php';
|
||||
require __DIR__ . '/lib/Tax/Registration.php';
|
||||
require __DIR__ . '/lib/Tax/Settings.php';
|
||||
require __DIR__ . '/lib/Tax/Transaction.php';
|
||||
require __DIR__ . '/lib/Tax/TransactionLineItem.php';
|
||||
require __DIR__ . '/lib/TaxCode.php';
|
||||
require __DIR__ . '/lib/TaxDeductedAtSource.php';
|
||||
require __DIR__ . '/lib/TaxId.php';
|
||||
require __DIR__ . '/lib/TaxRate.php';
|
||||
require __DIR__ . '/lib/Terminal/Configuration.php';
|
||||
require __DIR__ . '/lib/Terminal/ConnectionToken.php';
|
||||
require __DIR__ . '/lib/Terminal/Location.php';
|
||||
require __DIR__ . '/lib/Terminal/Reader.php';
|
||||
require __DIR__ . '/lib/TestHelpers/TestClock.php';
|
||||
require __DIR__ . '/lib/Token.php';
|
||||
require __DIR__ . '/lib/Topup.php';
|
||||
require __DIR__ . '/lib/Transfer.php';
|
||||
require __DIR__ . '/lib/TransferReversal.php';
|
||||
require __DIR__ . '/lib/Treasury/CreditReversal.php';
|
||||
require __DIR__ . '/lib/Treasury/DebitReversal.php';
|
||||
require __DIR__ . '/lib/Treasury/FinancialAccount.php';
|
||||
require __DIR__ . '/lib/Treasury/FinancialAccountFeatures.php';
|
||||
require __DIR__ . '/lib/Treasury/InboundTransfer.php';
|
||||
require __DIR__ . '/lib/Treasury/OutboundPayment.php';
|
||||
require __DIR__ . '/lib/Treasury/OutboundTransfer.php';
|
||||
require __DIR__ . '/lib/Treasury/ReceivedCredit.php';
|
||||
require __DIR__ . '/lib/Treasury/ReceivedDebit.php';
|
||||
require __DIR__ . '/lib/Treasury/Transaction.php';
|
||||
require __DIR__ . '/lib/Treasury/TransactionEntry.php';
|
||||
require __DIR__ . '/lib/UsageRecord.php';
|
||||
require __DIR__ . '/lib/UsageRecordSummary.php';
|
||||
require __DIR__ . '/lib/V2/Billing/MeterEvent.php';
|
||||
require __DIR__ . '/lib/V2/Billing/MeterEventAdjustment.php';
|
||||
require __DIR__ . '/lib/V2/Billing/MeterEventSession.php';
|
||||
require __DIR__ . '/lib/V2/EventDestination.php';
|
||||
require __DIR__ . '/lib/WebhookEndpoint.php';
|
||||
|
||||
// The end of the section generated from our OpenAPI spec
|
||||
|
||||
// OAuth
|
||||
require __DIR__ . '/lib/OAuth.php';
|
||||
require __DIR__ . '/lib/OAuthErrorObject.php';
|
||||
require __DIR__ . '/lib/Service/OAuthService.php';
|
||||
|
||||
// Webhooks
|
||||
require __DIR__ . '/lib/Webhook.php';
|
||||
require __DIR__ . '/lib/WebhookSignature.php';
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
set quiet
|
||||
|
||||
import? '../sdk-codegen/utils.just'
|
||||
|
||||
# make vendored executables callable directly
|
||||
export PATH := "vendor/bin:" + env_var('PATH')
|
||||
|
||||
_default:
|
||||
just --list --unsorted
|
||||
|
||||
# install vendored dependencies
|
||||
install *args:
|
||||
composer install {{ if is_dependency() == "true" {"--quiet"} else {""} }} {{ args }}
|
||||
|
||||
# ⭐ run full unit test suite; needs stripe-mock
|
||||
[no-exit-message]
|
||||
test *args: install
|
||||
phpunit {{ args }}
|
||||
|
||||
# run tests in CI; can use autoload mode (or not)
|
||||
[confirm("This will modify local files and is intended for use in CI; do you want to proceed?")]
|
||||
ci-test autoload:
|
||||
./build.php {{ autoload }}
|
||||
|
||||
# ⭐ format all files
|
||||
format *args: install
|
||||
PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -v --using-cache=no {{ args }}
|
||||
|
||||
# check formatting for, but don't modify, files
|
||||
format-check: (format "--dry-run")
|
||||
|
||||
# ⭐ statically analyze code
|
||||
lint *args:
|
||||
php -d memory_limit=512M vendor/bin/phpstan analyse lib tests {{args}}
|
||||
|
||||
# for backwards compatibility; ideally removed later
|
||||
[private]
|
||||
alias phpstan := lint
|
||||
|
||||
# called by tooling
|
||||
[private]
|
||||
update-version version:
|
||||
echo "{{ version }}" > VERSION
|
||||
perl -pi -e 's|VERSION = '\''[.\-\w\d]+'\''|VERSION = '\''{{ version }}'\''|' lib/Stripe.php
|
||||
|
||||
|
||||
PHPDOCUMENTOR_VERSION := "v3.0.0"
|
||||
# generates docs; currently broken? can unhide if working
|
||||
[private]
|
||||
phpdoc:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ ! -f vendor/bin/phpdoc ]; then
|
||||
curl -sfL https://github.com/phpDocumentor/phpDocumentor/releases/download/{{ PHPDOCUMENTOR_VERSION }}/phpDocumentor.phar -o vendor/bin/phpdoc
|
||||
chmod +x vendor/bin/phpdoc
|
||||
fi
|
||||
|
||||
phpdoc
|
||||
|
|
@ -0,0 +1,524 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* This is an object representing a Stripe account. You can retrieve it to see
|
||||
* properties on the account like its current requirements or if the account is
|
||||
* enabled to make live charges or receive payouts.
|
||||
*
|
||||
* For accounts where <a href="/api/accounts/object#account_object-controller-requirement_collection">controller.requirement_collection</a>
|
||||
* is <code>application</code>, which includes Custom accounts, the properties below are always
|
||||
* returned.
|
||||
*
|
||||
* For accounts where <a href="/api/accounts/object#account_object-controller-requirement_collection">controller.requirement_collection</a>
|
||||
* is <code>stripe</code>, which includes Standard and Express accounts, some properties are only returned
|
||||
* until you create an <a href="/api/account_links">Account Link</a> or <a href="/api/account_sessions">Account Session</a>
|
||||
* to start Connect Onboarding. Learn about the <a href="/connect/accounts">differences between accounts</a>.
|
||||
*
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property null|\Stripe\StripeObject $business_profile Business information about the account.
|
||||
* @property null|string $business_type The business type. After you create an <a href="/api/account_links">Account Link</a> or <a href="/api/account_sessions">Account Session</a>, this property is only returned for accounts where <a href="/api/accounts/object#account_object-controller-requirement_collection">controller.requirement_collection</a> is <code>application</code>, which includes Custom accounts.
|
||||
* @property null|\Stripe\StripeObject $capabilities
|
||||
* @property null|bool $charges_enabled Whether the account can process charges.
|
||||
* @property null|\Stripe\StripeObject $company
|
||||
* @property null|\Stripe\StripeObject $controller
|
||||
* @property null|string $country The account's country.
|
||||
* @property null|int $created Time at which the account was connected. Measured in seconds since the Unix epoch.
|
||||
* @property null|string $default_currency Three-letter ISO currency code representing the default currency for the account. This must be a currency that <a href="https://stripe.com/docs/payouts">Stripe supports in the account's country</a>.
|
||||
* @property null|bool $details_submitted Whether account details have been submitted. Accounts with Stripe Dashboard access, which includes Standard accounts, cannot receive payouts before this is true. Accounts where this is false should be directed to <a href="/connect/onboarding">an onboarding flow</a> to finish submitting account details.
|
||||
* @property null|string $email An email address associated with the account. It's not used for authentication and Stripe doesn't market to this field without explicit approval from the platform.
|
||||
* @property null|\Stripe\Collection<\Stripe\BankAccount|\Stripe\Card> $external_accounts External accounts (bank accounts and debit cards) currently attached to this account. External accounts are only returned for requests where <code>controller[is_controller]</code> is true.
|
||||
* @property null|\Stripe\StripeObject $future_requirements
|
||||
* @property null|\Stripe\StripeObject $groups The groups associated with the account.
|
||||
* @property null|\Stripe\Person $individual <p>This is an object representing a person associated with a Stripe account.</p><p>A platform cannot access a person for an account where <a href="/api/accounts/object#account_object-controller-requirement_collection">account.controller.requirement_collection</a> is <code>stripe</code>, which includes Standard and Express accounts, after creating an Account Link or Account Session to start Connect onboarding.</p><p>See the <a href="/connect/standard-accounts">Standard onboarding</a> or <a href="/connect/express-accounts">Express onboarding</a> documentation for information about prefilling information and account onboarding steps. Learn more about <a href="/connect/handling-api-verification#person-information">handling identity verification with the API</a>.</p>
|
||||
* @property null|\Stripe\StripeObject $metadata Set of <a href="https://stripe.com/docs/api/metadata">key-value pairs</a> that you can attach to an object. This can be useful for storing additional information about the object in a structured format.
|
||||
* @property null|bool $payouts_enabled Whether the funds in this account can be paid out.
|
||||
* @property null|\Stripe\StripeObject $requirements
|
||||
* @property null|\Stripe\StripeObject $settings Options for customizing how the account functions within Stripe.
|
||||
* @property null|\Stripe\StripeObject $tos_acceptance
|
||||
* @property null|string $type The Stripe account type. Can be <code>standard</code>, <code>express</code>, <code>custom</code>, or <code>none</code>.
|
||||
*/
|
||||
class Account extends ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'account';
|
||||
|
||||
use ApiOperations\NestedResource;
|
||||
use ApiOperations\Update;
|
||||
|
||||
const BUSINESS_TYPE_COMPANY = 'company';
|
||||
const BUSINESS_TYPE_GOVERNMENT_ENTITY = 'government_entity';
|
||||
const BUSINESS_TYPE_INDIVIDUAL = 'individual';
|
||||
const BUSINESS_TYPE_NON_PROFIT = 'non_profit';
|
||||
|
||||
const TYPE_CUSTOM = 'custom';
|
||||
const TYPE_EXPRESS = 'express';
|
||||
const TYPE_NONE = 'none';
|
||||
const TYPE_STANDARD = 'standard';
|
||||
|
||||
/**
|
||||
* With <a href="/docs/connect">Connect</a>, you can create Stripe accounts for
|
||||
* your users. To do this, you’ll first need to <a
|
||||
* href="https://dashboard.stripe.com/account/applications/settings">register your
|
||||
* platform</a>.
|
||||
*
|
||||
* If you’ve already collected information for your connected accounts, you <a
|
||||
* href="/docs/connect/best-practices#onboarding">can prefill that information</a>
|
||||
* when creating the account. Connect Onboarding won’t ask for the prefilled
|
||||
* information during account onboarding. You can prefill any information on the
|
||||
* account.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Account the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With <a href="/connect">Connect</a>, you can delete accounts you manage.
|
||||
*
|
||||
* Test-mode accounts can be deleted at any time.
|
||||
*
|
||||
* Live-mode accounts where Stripe is responsible for negative account balances
|
||||
* cannot be deleted, which includes Standard accounts. Live-mode accounts where
|
||||
* your platform is liable for negative account balances, which includes Custom and
|
||||
* Express accounts, can be deleted when all <a
|
||||
* href="/api/balance/balance_object">balances</a> are zero.
|
||||
*
|
||||
* If you want to delete your own account, use the <a
|
||||
* href="https://dashboard.stripe.com/settings/account">account information tab in
|
||||
* your account settings</a> instead.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Account the deleted resource
|
||||
*/
|
||||
public function delete($params = null, $opts = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
|
||||
$url = $this->instanceUrl();
|
||||
list($response, $opts) = $this->_request('delete', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of accounts connected to your platform via <a
|
||||
* href="/docs/connect">Connect</a>. If you’re not a platform, the list is empty.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\Account> of ApiResources
|
||||
*/
|
||||
public static function all($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl();
|
||||
|
||||
return static::_requestPage($url, \Stripe\Collection::class, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a <a href="/connect/accounts">connected account</a> by setting the
|
||||
* values of the parameters passed. Any parameters not provided are left unchanged.
|
||||
*
|
||||
* For accounts where <a
|
||||
* href="/api/accounts/object#account_object-controller-requirement_collection">controller.requirement_collection</a>
|
||||
* is <code>application</code>, which includes Custom accounts, you can update any
|
||||
* information on the account.
|
||||
*
|
||||
* For accounts where <a
|
||||
* href="/api/accounts/object#account_object-controller-requirement_collection">controller.requirement_collection</a>
|
||||
* is <code>stripe</code>, which includes Standard and Express accounts, you can
|
||||
* update all information until you create an <a href="/api/account_links">Account
|
||||
* Link</a> or <a href="/api/account_sessions">Account Session</a> to start Connect
|
||||
* onboarding, after which some properties can no longer be updated.
|
||||
*
|
||||
* To update your own account, use the <a
|
||||
* href="https://dashboard.stripe.com/settings/account">Dashboard</a>. Refer to our
|
||||
* <a href="/docs/connect/updating-accounts">Connect</a> documentation to learn
|
||||
* more about updating accounts.
|
||||
*
|
||||
* @param string $id the ID of the resource to update
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Account the updated resource
|
||||
*/
|
||||
public static function update($id, $params = null, $opts = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::resourceUrl($id);
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $opts);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
use ApiOperations\Retrieve {
|
||||
retrieve as protected _retrieve;
|
||||
}
|
||||
|
||||
public static function getSavedNestedResources()
|
||||
{
|
||||
static $savedNestedResources = null;
|
||||
if (null === $savedNestedResources) {
|
||||
$savedNestedResources = new Util\Set([
|
||||
'external_account',
|
||||
'bank_account',
|
||||
]);
|
||||
}
|
||||
|
||||
return $savedNestedResources;
|
||||
}
|
||||
|
||||
public function instanceUrl()
|
||||
{
|
||||
if (null === $this['id']) {
|
||||
return '/v1/account';
|
||||
}
|
||||
|
||||
return parent::instanceUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array|string $id the ID of the account to retrieve, or an
|
||||
* options array containing an `id` key
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Account
|
||||
*/
|
||||
public static function retrieve($id = null, $opts = null)
|
||||
{
|
||||
if (!$opts && \is_string($id) && 'sk_' === \substr($id, 0, 3)) {
|
||||
$opts = $id;
|
||||
$id = null;
|
||||
}
|
||||
|
||||
return self::_retrieve($id, $opts);
|
||||
}
|
||||
|
||||
public function serializeParameters($force = false)
|
||||
{
|
||||
$update = parent::serializeParameters($force);
|
||||
if (isset($this->_values['legal_entity'])) {
|
||||
$entity = $this['legal_entity'];
|
||||
if (isset($entity->_values['additional_owners'])) {
|
||||
$owners = $entity['additional_owners'];
|
||||
$entityUpdate = isset($update['legal_entity']) ? $update['legal_entity'] : [];
|
||||
$entityUpdate['additional_owners'] = $this->serializeAdditionalOwners($entity, $owners);
|
||||
$update['legal_entity'] = $entityUpdate;
|
||||
}
|
||||
}
|
||||
if (isset($this->_values['individual'])) {
|
||||
$individual = $this['individual'];
|
||||
if (($individual instanceof Person) && !isset($update['individual'])) {
|
||||
$update['individual'] = $individual->serializeParameters($force);
|
||||
}
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
private function serializeAdditionalOwners($legalEntity, $additionalOwners)
|
||||
{
|
||||
if (isset($legalEntity->_originalValues['additional_owners'])) {
|
||||
$originalValue = $legalEntity->_originalValues['additional_owners'];
|
||||
} else {
|
||||
$originalValue = [];
|
||||
}
|
||||
if (($originalValue) && (\count($originalValue) > \count($additionalOwners))) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'You cannot delete an item from an array, you must instead set a new array'
|
||||
);
|
||||
}
|
||||
|
||||
$updateArr = [];
|
||||
foreach ($additionalOwners as $i => $v) {
|
||||
$update = ($v instanceof StripeObject) ? $v->serializeParameters() : $v;
|
||||
|
||||
if ([] !== $update) {
|
||||
if (!$originalValue
|
||||
|| !\array_key_exists($i, $originalValue)
|
||||
|| ($update !== $legalEntity->serializeParamsValue($originalValue[$i], null, false, true))) {
|
||||
$updateArr[$i] = $update;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $updateArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $clientId
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\StripeObject object containing the response from the API
|
||||
*/
|
||||
public function deauthorize($clientId = null, $opts = null)
|
||||
{
|
||||
$params = [
|
||||
'client_id' => $clientId,
|
||||
'stripe_user_id' => $this->id,
|
||||
];
|
||||
|
||||
return OAuth::deauthorize($params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Account the rejected account
|
||||
*/
|
||||
public function reject($params = null, $opts = null)
|
||||
{
|
||||
$url = $this->instanceUrl() . '/reject';
|
||||
list($response, $opts) = $this->_request('post', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
const PATH_CAPABILITIES = '/capabilities';
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account on which to retrieve the capabilities
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\Capability> the list of capabilities
|
||||
*/
|
||||
public static function allCapabilities($id, $params = null, $opts = null)
|
||||
{
|
||||
return self::_allNestedResources($id, static::PATH_CAPABILITIES, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account to which the capability belongs
|
||||
* @param string $capabilityId the ID of the capability to retrieve
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Capability
|
||||
*/
|
||||
public static function retrieveCapability($id, $capabilityId, $params = null, $opts = null)
|
||||
{
|
||||
return self::_retrieveNestedResource($id, static::PATH_CAPABILITIES, $capabilityId, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account to which the capability belongs
|
||||
* @param string $capabilityId the ID of the capability to update
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Capability
|
||||
*/
|
||||
public static function updateCapability($id, $capabilityId, $params = null, $opts = null)
|
||||
{
|
||||
return self::_updateNestedResource($id, static::PATH_CAPABILITIES, $capabilityId, $params, $opts);
|
||||
}
|
||||
const PATH_EXTERNAL_ACCOUNTS = '/external_accounts';
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account on which to retrieve the external accounts
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\BankAccount|\Stripe\Card> the list of external accounts (BankAccount or Card)
|
||||
*/
|
||||
public static function allExternalAccounts($id, $params = null, $opts = null)
|
||||
{
|
||||
return self::_allNestedResources($id, static::PATH_EXTERNAL_ACCOUNTS, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account on which to create the external account
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\BankAccount|\Stripe\Card
|
||||
*/
|
||||
public static function createExternalAccount($id, $params = null, $opts = null)
|
||||
{
|
||||
return self::_createNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account to which the external account belongs
|
||||
* @param string $externalAccountId the ID of the external account to delete
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\BankAccount|\Stripe\Card
|
||||
*/
|
||||
public static function deleteExternalAccount($id, $externalAccountId, $params = null, $opts = null)
|
||||
{
|
||||
return self::_deleteNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $externalAccountId, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account to which the external account belongs
|
||||
* @param string $externalAccountId the ID of the external account to retrieve
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\BankAccount|\Stripe\Card
|
||||
*/
|
||||
public static function retrieveExternalAccount($id, $externalAccountId, $params = null, $opts = null)
|
||||
{
|
||||
return self::_retrieveNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $externalAccountId, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account to which the external account belongs
|
||||
* @param string $externalAccountId the ID of the external account to update
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\BankAccount|\Stripe\Card
|
||||
*/
|
||||
public static function updateExternalAccount($id, $externalAccountId, $params = null, $opts = null)
|
||||
{
|
||||
return self::_updateNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $externalAccountId, $params, $opts);
|
||||
}
|
||||
const PATH_LOGIN_LINKS = '/login_links';
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account on which to create the login link
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\LoginLink
|
||||
*/
|
||||
public static function createLoginLink($id, $params = null, $opts = null)
|
||||
{
|
||||
return self::_createNestedResource($id, static::PATH_LOGIN_LINKS, $params, $opts);
|
||||
}
|
||||
const PATH_PERSONS = '/persons';
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account on which to retrieve the persons
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\Person> the list of persons
|
||||
*/
|
||||
public static function allPersons($id, $params = null, $opts = null)
|
||||
{
|
||||
return self::_allNestedResources($id, static::PATH_PERSONS, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account on which to create the person
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Person
|
||||
*/
|
||||
public static function createPerson($id, $params = null, $opts = null)
|
||||
{
|
||||
return self::_createNestedResource($id, static::PATH_PERSONS, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account to which the person belongs
|
||||
* @param string $personId the ID of the person to delete
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Person
|
||||
*/
|
||||
public static function deletePerson($id, $personId, $params = null, $opts = null)
|
||||
{
|
||||
return self::_deleteNestedResource($id, static::PATH_PERSONS, $personId, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account to which the person belongs
|
||||
* @param string $personId the ID of the person to retrieve
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Person
|
||||
*/
|
||||
public static function retrievePerson($id, $personId, $params = null, $opts = null)
|
||||
{
|
||||
return self::_retrieveNestedResource($id, static::PATH_PERSONS, $personId, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the account to which the person belongs
|
||||
* @param string $personId the ID of the person to update
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Person
|
||||
*/
|
||||
public static function updatePerson($id, $personId, $params = null, $opts = null)
|
||||
{
|
||||
return self::_updateNestedResource($id, static::PATH_PERSONS, $personId, $params, $opts);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* Account Links are the means by which a Connect platform grants a connected account permission to access
|
||||
* Stripe-hosted applications, such as Connect Onboarding.
|
||||
*
|
||||
* Related guide: <a href="https://stripe.com/docs/connect/custom/hosted-onboarding">Connect Onboarding</a>
|
||||
*
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property int $expires_at The timestamp at which this account link will expire.
|
||||
* @property string $url The URL for the account link.
|
||||
*/
|
||||
class AccountLink extends ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'account_link';
|
||||
|
||||
/**
|
||||
* Creates an AccountLink object that includes a single-use Stripe URL that the
|
||||
* platform can redirect their user to in order to take them through the Connect
|
||||
* Onboarding flow.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\AccountLink the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* An AccountSession allows a Connect platform to grant access to a connected account in Connect embedded components.
|
||||
*
|
||||
* We recommend that you create an AccountSession each time you need to display an embedded component
|
||||
* to your user. Do not save AccountSessions to your database as they expire relatively
|
||||
* quickly, and cannot be used more than once.
|
||||
*
|
||||
* Related guide: <a href="https://stripe.com/docs/connect/get-started-connect-embedded-components">Connect embedded components</a>
|
||||
*
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property string $account The ID of the account the AccountSession was created for
|
||||
* @property string $client_secret <p>The client secret of this AccountSession. Used on the client to set up secure access to the given <code>account</code>.</p><p>The client secret can be used to provide access to <code>account</code> from your frontend. It should not be stored, logged, or exposed to anyone other than the connected account. Make sure that you have TLS enabled on any page that includes the client secret.</p><p>Refer to our docs to <a href="https://stripe.com/docs/connect/get-started-connect-embedded-components">setup Connect embedded components</a> and learn about how <code>client_secret</code> should be handled.</p>
|
||||
* @property \Stripe\StripeObject $components
|
||||
* @property int $expires_at The timestamp at which this AccountSession will expire.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
*/
|
||||
class AccountSession extends ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'account_session';
|
||||
|
||||
/**
|
||||
* Creates a AccountSession object that includes a single-use token that the
|
||||
* platform can use on their front-end to grant client-side API access.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\AccountSession the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe\ApiOperations;
|
||||
|
||||
/**
|
||||
* Trait for listable resources. Adds a `all()` static method to the class.
|
||||
*
|
||||
* This trait should only be applied to classes that derive from StripeObject.
|
||||
*/
|
||||
trait All
|
||||
{
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection of ApiResources
|
||||
*/
|
||||
public static function all($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl();
|
||||
|
||||
return static::_requestPage($url, \Stripe\Collection::class, $params, $opts);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe\ApiOperations;
|
||||
|
||||
/**
|
||||
* Trait for creatable resources. Adds a `create()` static method to the class.
|
||||
*
|
||||
* This trait should only be applied to classes that derive from StripeObject.
|
||||
*/
|
||||
trait Create
|
||||
{
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return static the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe\ApiOperations;
|
||||
|
||||
/**
|
||||
* Trait for deletable resources. Adds a `delete()` method to the class.
|
||||
*
|
||||
* This trait should only be applied to classes that derive from StripeObject.
|
||||
*/
|
||||
trait Delete
|
||||
{
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return static the deleted resource
|
||||
*/
|
||||
public function delete($params = null, $opts = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
|
||||
$url = $this->instanceUrl();
|
||||
list($response, $opts) = $this->_request('delete', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe\ApiOperations;
|
||||
|
||||
/**
|
||||
* Trait for resources that have nested resources.
|
||||
*
|
||||
* This trait should only be applied to classes that derive from StripeObject.
|
||||
*/
|
||||
trait NestedResource
|
||||
{
|
||||
/**
|
||||
* @param 'delete'|'get'|'post' $method
|
||||
* @param string $url
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @return \Stripe\StripeObject
|
||||
*/
|
||||
protected static function _nestedResourceOperation($method, $url, $params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
|
||||
list($response, $opts) = static::_staticRequest($method, $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string $nestedPath
|
||||
* @param null|string $nestedId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function _nestedResourceUrl($id, $nestedPath, $nestedId = null)
|
||||
{
|
||||
$url = static::resourceUrl($id) . $nestedPath;
|
||||
if (null !== $nestedId) {
|
||||
$url .= "/{$nestedId}";
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string $nestedPath
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\StripeObject
|
||||
*/
|
||||
protected static function _createNestedResource($id, $nestedPath, $params = null, $options = null)
|
||||
{
|
||||
$url = static::_nestedResourceUrl($id, $nestedPath);
|
||||
|
||||
return self::_nestedResourceOperation('post', $url, $params, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string $nestedPath
|
||||
* @param null|string $nestedId
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\StripeObject
|
||||
*/
|
||||
protected static function _retrieveNestedResource($id, $nestedPath, $nestedId, $params = null, $options = null)
|
||||
{
|
||||
$url = static::_nestedResourceUrl($id, $nestedPath, $nestedId);
|
||||
|
||||
return self::_nestedResourceOperation('get', $url, $params, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string $nestedPath
|
||||
* @param null|string $nestedId
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\StripeObject
|
||||
*/
|
||||
protected static function _updateNestedResource($id, $nestedPath, $nestedId, $params = null, $options = null)
|
||||
{
|
||||
$url = static::_nestedResourceUrl($id, $nestedPath, $nestedId);
|
||||
|
||||
return self::_nestedResourceOperation('post', $url, $params, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string $nestedPath
|
||||
* @param null|string $nestedId
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\StripeObject
|
||||
*/
|
||||
protected static function _deleteNestedResource($id, $nestedPath, $nestedId, $params = null, $options = null)
|
||||
{
|
||||
$url = static::_nestedResourceUrl($id, $nestedPath, $nestedId);
|
||||
|
||||
return self::_nestedResourceOperation('delete', $url, $params, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string $nestedPath
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\StripeObject
|
||||
*/
|
||||
protected static function _allNestedResources($id, $nestedPath, $params = null, $options = null)
|
||||
{
|
||||
$url = static::_nestedResourceUrl($id, $nestedPath);
|
||||
|
||||
return self::_nestedResourceOperation('get', $url, $params, $options);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe\ApiOperations;
|
||||
|
||||
/**
|
||||
* Trait for resources that need to make API requests.
|
||||
*
|
||||
* This trait should only be applied to classes that derive from StripeObject.
|
||||
*/
|
||||
trait Request
|
||||
{
|
||||
/**
|
||||
* @param null|array|mixed $params The list of parameters to validate
|
||||
*
|
||||
* @throws \Stripe\Exception\InvalidArgumentException if $params exists and is not an array
|
||||
*/
|
||||
protected static function _validateParams($params = null)
|
||||
{
|
||||
if ($params && !\is_array($params)) {
|
||||
$message = 'You must pass an array as the first argument to Stripe API '
|
||||
. 'method calls. (HINT: an example call to create a charge '
|
||||
. "would be: \"Stripe\\Charge::create(['amount' => 100, "
|
||||
. "'currency' => 'usd', 'source' => 'tok_1234'])\")";
|
||||
|
||||
throw new \Stripe\Exception\InvalidArgumentException($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'delete'|'get'|'post' $method HTTP method ('get', 'post', etc.)
|
||||
* @param string $url URL for the request
|
||||
* @param array $params list of parameters for the request
|
||||
* @param null|array|string $options
|
||||
* @param string[] $usage names of tracked behaviors associated with this request
|
||||
* @param 'v1'|'v2' $apiMode
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return array tuple containing (the JSON response, $options)
|
||||
*/
|
||||
protected function _request($method, $url, $params = [], $options = null, $usage = [], $apiMode = 'v1')
|
||||
{
|
||||
$opts = $this->_opts->merge($options);
|
||||
list($resp, $options) = static::_staticRequest($method, $url, $params, $opts, $usage, $apiMode);
|
||||
$this->setLastResponse($resp);
|
||||
|
||||
return [$resp->json, $options];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url URL for the request
|
||||
* @param class-string< \Stripe\SearchResult|\Stripe\Collection > $resultClass indicating what type of paginated result is returned
|
||||
* @param null|array $params list of parameters for the request
|
||||
* @param null|array|string $options
|
||||
* @param string[] $usage names of tracked behaviors associated with this request
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection|\Stripe\SearchResult
|
||||
*/
|
||||
protected static function _requestPage($url, $resultClass, $params = null, $options = null, $usage = [])
|
||||
{
|
||||
self::_validateParams($params);
|
||||
|
||||
list($response, $opts) = static::_staticRequest('get', $url, $params, $options, $usage);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
if (!($obj instanceof $resultClass)) {
|
||||
throw new \Stripe\Exception\UnexpectedValueException(
|
||||
'Expected type ' . $resultClass . ', got "' . \get_class($obj) . '" instead.'
|
||||
);
|
||||
}
|
||||
$obj->setLastResponse($response);
|
||||
$obj->setFilters($params);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'delete'|'get'|'post' $method HTTP method ('get', 'post', etc.)
|
||||
* @param string $url URL for the request
|
||||
* @param callable $readBodyChunk function that will receive chunks of data from a successful request body
|
||||
* @param array $params list of parameters for the request
|
||||
* @param null|array|string $options
|
||||
* @param string[] $usage names of tracked behaviors associated with this request
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*/
|
||||
protected function _requestStream($method, $url, $readBodyChunk, $params = [], $options = null, $usage = [])
|
||||
{
|
||||
$opts = $this->_opts->merge($options);
|
||||
static::_staticStreamingRequest($method, $url, $readBodyChunk, $params, $opts, $usage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'delete'|'get'|'post' $method HTTP method ('get', 'post', etc.)
|
||||
* @param string $url URL for the request
|
||||
* @param array $params list of parameters for the request
|
||||
* @param null|array|string $options
|
||||
* @param string[] $usage names of tracked behaviors associated with this request
|
||||
* @param 'v1'|'v2' $apiMode
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return array tuple containing (the JSON response, $options)
|
||||
*/
|
||||
protected static function _staticRequest($method, $url, $params, $options, $usage = [], $apiMode = 'v1')
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($options);
|
||||
$baseUrl = isset($opts->apiBase) ? $opts->apiBase : static::baseUrl();
|
||||
$requestor = new \Stripe\ApiRequestor($opts->apiKey, $baseUrl);
|
||||
list($response, $opts->apiKey) = $requestor->request($method, $url, $params, $opts->headers, $apiMode, $usage);
|
||||
$opts->discardNonPersistentHeaders();
|
||||
|
||||
return [$response, $opts];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'delete'|'get'|'post' $method HTTP method ('get', 'post', etc.)
|
||||
* @param string $url URL for the request
|
||||
* @param callable $readBodyChunk function that will receive chunks of data from a successful request body
|
||||
* @param array $params list of parameters for the request
|
||||
* @param null|array|string $options
|
||||
* @param string[] $usage names of tracked behaviors associated with this request
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*/
|
||||
protected static function _staticStreamingRequest($method, $url, $readBodyChunk, $params, $options, $usage = [])
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($options);
|
||||
$baseUrl = isset($opts->apiBase) ? $opts->apiBase : static::baseUrl();
|
||||
$requestor = new \Stripe\ApiRequestor($opts->apiKey, $baseUrl);
|
||||
$requestor->requestStream($method, $url, $readBodyChunk, $params, $opts->headers);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe\ApiOperations;
|
||||
|
||||
/**
|
||||
* Trait for retrievable resources. Adds a `retrieve()` static method to the
|
||||
* class.
|
||||
*
|
||||
* This trait should only be applied to classes that derive from StripeObject.
|
||||
*/
|
||||
trait Retrieve
|
||||
{
|
||||
/**
|
||||
* @param array|string $id the ID of the API resource to retrieve,
|
||||
* or an options array containing an `id` key
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function retrieve($id, $opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static($id, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe\ApiOperations;
|
||||
|
||||
/**
|
||||
* Trait for searchable resources.
|
||||
*
|
||||
* This trait should only be applied to classes that derive from StripeObject.
|
||||
*/
|
||||
trait Search
|
||||
{
|
||||
/**
|
||||
* @param string $searchUrl
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\SearchResult of ApiResources
|
||||
*/
|
||||
protected static function _searchResource($searchUrl, $params = null, $opts = null)
|
||||
{
|
||||
return static::_requestPage($searchUrl, \Stripe\SearchResult::class, $params, $opts);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe\ApiOperations;
|
||||
|
||||
/**
|
||||
* Trait for retrievable singleton resources. Adds a `retrieve()` static method to the
|
||||
* class.
|
||||
*
|
||||
* This trait should only be applied to classes that derive from SingletonApiResource.
|
||||
*/
|
||||
trait SingletonRetrieve
|
||||
{
|
||||
/**
|
||||
* @param null|array|string $opts the ID of the API resource to retrieve,
|
||||
* or an options array containing an `id` key
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function retrieve($opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static(null, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe\ApiOperations;
|
||||
|
||||
/**
|
||||
* Trait for updatable resources. Adds an `update()` static method and a
|
||||
* `save()` method to the class.
|
||||
*
|
||||
* This trait should only be applied to classes that derive from StripeObject.
|
||||
*/
|
||||
trait Update
|
||||
{
|
||||
/**
|
||||
* @param string $id the ID of the resource to update
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return static the updated resource
|
||||
*/
|
||||
public static function update($id, $params = null, $opts = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::resourceUrl($id);
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $opts);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return static the saved resource
|
||||
*
|
||||
* @deprecated The `save` method is deprecated and will be removed in a
|
||||
* future major version of the library. Use the static method `update`
|
||||
* on the resource instead.
|
||||
*/
|
||||
public function save($opts = null)
|
||||
{
|
||||
$params = $this->serializeParameters();
|
||||
if (\count($params) > 0) {
|
||||
$url = $this->instanceUrl();
|
||||
list($response, $opts) = $this->_request('post', $url, $params, $opts, ['save']);
|
||||
$this->refreshFrom($response, $opts);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,702 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* Class ApiRequestor.
|
||||
*/
|
||||
class ApiRequestor
|
||||
{
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
private $_apiKey;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $_apiBase;
|
||||
|
||||
/**
|
||||
* @var null|array
|
||||
*/
|
||||
private $_appInfo;
|
||||
|
||||
/**
|
||||
* @var HttpClient\ClientInterface
|
||||
*/
|
||||
private static $_httpClient;
|
||||
/**
|
||||
* @var HttpClient\StreamingClientInterface
|
||||
*/
|
||||
private static $_streamingHttpClient;
|
||||
|
||||
/**
|
||||
* @var RequestTelemetry
|
||||
*/
|
||||
private static $requestTelemetry;
|
||||
|
||||
private static $OPTIONS_KEYS = ['api_key', 'idempotency_key', 'stripe_account', 'stripe_context', 'stripe_version', 'api_base'];
|
||||
|
||||
/**
|
||||
* ApiRequestor constructor.
|
||||
*
|
||||
* @param null|string $apiKey
|
||||
* @param null|string $apiBase
|
||||
* @param null|array $appInfo
|
||||
*/
|
||||
public function __construct($apiKey = null, $apiBase = null, $appInfo = null)
|
||||
{
|
||||
$this->_apiKey = $apiKey;
|
||||
if (!$apiBase) {
|
||||
$apiBase = Stripe::$apiBase;
|
||||
}
|
||||
$this->_apiBase = $apiBase;
|
||||
$this->_appInfo = $appInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a telemetry json blob for use in 'X-Stripe-Client-Telemetry' headers.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param RequestTelemetry $requestTelemetry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _telemetryJson($requestTelemetry)
|
||||
{
|
||||
$payload = [
|
||||
'last_request_metrics' => [
|
||||
'request_id' => $requestTelemetry->requestId,
|
||||
'request_duration_ms' => $requestTelemetry->requestDuration,
|
||||
],
|
||||
];
|
||||
if (\count($requestTelemetry->usage) > 0) {
|
||||
$payload['last_request_metrics']['usage'] = $requestTelemetry->usage;
|
||||
}
|
||||
|
||||
$result = \json_encode($payload);
|
||||
if (false !== $result) {
|
||||
return $result;
|
||||
}
|
||||
Stripe::getLogger()->error('Serializing telemetry payload failed!');
|
||||
|
||||
return '{}';
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* @param ApiResource|array|bool|mixed $d
|
||||
*
|
||||
* @return ApiResource|array|mixed|string
|
||||
*/
|
||||
private static function _encodeObjects($d)
|
||||
{
|
||||
if ($d instanceof ApiResource) {
|
||||
return Util\Util::utf8($d->id);
|
||||
}
|
||||
if (true === $d) {
|
||||
return 'true';
|
||||
}
|
||||
if (false === $d) {
|
||||
return 'false';
|
||||
}
|
||||
if (\is_array($d)) {
|
||||
$res = [];
|
||||
foreach ($d as $k => $v) {
|
||||
$res[$k] = self::_encodeObjects($v);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return Util\Util::utf8($d);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'delete'|'get'|'post' $method
|
||||
* @param string $url
|
||||
* @param null|array $params
|
||||
* @param null|array $headers
|
||||
* @param 'v1'|'v2' $apiMode
|
||||
* @param string[] $usage
|
||||
*
|
||||
* @throws Exception\ApiErrorException
|
||||
*
|
||||
* @return array tuple containing (ApiReponse, API key)
|
||||
*/
|
||||
public function request($method, $url, $params = null, $headers = null, $apiMode = 'v1', $usage = [])
|
||||
{
|
||||
$params = $params ?: [];
|
||||
$headers = $headers ?: [];
|
||||
list($rbody, $rcode, $rheaders, $myApiKey) =
|
||||
$this->_requestRaw($method, $url, $params, $headers, $apiMode, $usage);
|
||||
$json = $this->_interpretResponse($rbody, $rcode, $rheaders, $apiMode);
|
||||
$resp = new ApiResponse($rbody, $rcode, $rheaders, $json);
|
||||
|
||||
return [$resp, $myApiKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'delete'|'get'|'post' $method
|
||||
* @param string $url
|
||||
* @param callable $readBodyChunkCallable
|
||||
* @param null|array $params
|
||||
* @param null|array $headers
|
||||
* @param 'v1'|'v2' $apiMode
|
||||
* @param string[] $usage
|
||||
*
|
||||
* @throws Exception\ApiErrorException
|
||||
*/
|
||||
public function requestStream($method, $url, $readBodyChunkCallable, $params = null, $headers = null, $apiMode = 'v1', $usage = [])
|
||||
{
|
||||
$params = $params ?: [];
|
||||
$headers = $headers ?: [];
|
||||
list($rbody, $rcode, $rheaders, $myApiKey) =
|
||||
$this->_requestRawStreaming($method, $url, $params, $headers, $apiMode, $usage, $readBodyChunkCallable);
|
||||
if ($rcode >= 300) {
|
||||
$this->_interpretResponse($rbody, $rcode, $rheaders, $apiMode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $rbody a JSON string
|
||||
* @param int $rcode
|
||||
* @param array $rheaders
|
||||
* @param array $resp
|
||||
* @param 'v1'|'v2' $apiMode
|
||||
*
|
||||
* @throws Exception\UnexpectedValueException
|
||||
* @throws Exception\ApiErrorException
|
||||
*/
|
||||
public function handleErrorResponse($rbody, $rcode, $rheaders, $resp, $apiMode)
|
||||
{
|
||||
if (!\is_array($resp) || !isset($resp['error'])) {
|
||||
$msg = "Invalid response object from API: {$rbody} "
|
||||
. "(HTTP response code was {$rcode})";
|
||||
|
||||
throw new Exception\UnexpectedValueException($msg);
|
||||
}
|
||||
|
||||
$errorData = $resp['error'];
|
||||
|
||||
$error = null;
|
||||
|
||||
if (\is_string($errorData)) {
|
||||
$error = self::_specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorData);
|
||||
}
|
||||
if (!$error) {
|
||||
$error = 'v1' === $apiMode ? self::_specificV1APIError($rbody, $rcode, $rheaders, $resp, $errorData) : self::_specificV2APIError($rbody, $rcode, $rheaders, $resp, $errorData);
|
||||
}
|
||||
|
||||
throw $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* @param string $rbody
|
||||
* @param int $rcode
|
||||
* @param array $rheaders
|
||||
* @param array $resp
|
||||
* @param array $errorData
|
||||
*
|
||||
* @return Exception\ApiErrorException
|
||||
*/
|
||||
private static function _specificV1APIError($rbody, $rcode, $rheaders, $resp, $errorData)
|
||||
{
|
||||
$msg = isset($errorData['message']) ? $errorData['message'] : null;
|
||||
$param = isset($errorData['param']) ? $errorData['param'] : null;
|
||||
$code = isset($errorData['code']) ? $errorData['code'] : null;
|
||||
$type = isset($errorData['type']) ? $errorData['type'] : null;
|
||||
$declineCode = isset($errorData['decline_code']) ? $errorData['decline_code'] : null;
|
||||
|
||||
switch ($rcode) {
|
||||
case 400:
|
||||
// 'rate_limit' code is deprecated, but left here for backwards compatibility
|
||||
// for API versions earlier than 2015-09-08
|
||||
if ('rate_limit' === $code) {
|
||||
return Exception\RateLimitException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param);
|
||||
}
|
||||
if ('idempotency_error' === $type) {
|
||||
return Exception\IdempotencyException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
|
||||
}
|
||||
|
||||
// fall through in generic 400 or 404, returns InvalidRequestException by default
|
||||
// no break
|
||||
case 404:
|
||||
return Exception\InvalidRequestException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param);
|
||||
|
||||
case 401:
|
||||
return Exception\AuthenticationException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
|
||||
|
||||
case 402:
|
||||
return Exception\CardException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $declineCode, $param);
|
||||
|
||||
case 403:
|
||||
return Exception\PermissionException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
|
||||
|
||||
case 429:
|
||||
return Exception\RateLimitException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param);
|
||||
|
||||
default:
|
||||
return Exception\UnknownApiErrorException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* @param string $rbody
|
||||
* @param int $rcode
|
||||
* @param array $rheaders
|
||||
* @param array $resp
|
||||
* @param array $errorData
|
||||
*
|
||||
* @return Exception\ApiErrorException
|
||||
*/
|
||||
private static function _specificV2APIError($rbody, $rcode, $rheaders, $resp, $errorData)
|
||||
{
|
||||
$msg = isset($errorData['message']) ? $errorData['message'] : null;
|
||||
$code = isset($errorData['code']) ? $errorData['code'] : null;
|
||||
$type = isset($errorData['type']) ? $errorData['type'] : null;
|
||||
|
||||
switch ($type) {
|
||||
case 'idempotency_error':
|
||||
return Exception\IdempotencyException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
|
||||
// The beginning of the section generated from our OpenAPI spec
|
||||
case 'temporary_session_expired':
|
||||
return Exception\TemporarySessionExpiredException::factory(
|
||||
$msg,
|
||||
$rcode,
|
||||
$rbody,
|
||||
$resp,
|
||||
$rheaders,
|
||||
$code
|
||||
);
|
||||
|
||||
// The end of the section generated from our OpenAPI spec
|
||||
default:
|
||||
return self::_specificV1APIError($rbody, $rcode, $rheaders, $resp, $errorData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* @param bool|string $rbody
|
||||
* @param int $rcode
|
||||
* @param array $rheaders
|
||||
* @param array $resp
|
||||
* @param string $errorCode
|
||||
*
|
||||
* @return Exception\OAuth\OAuthErrorException
|
||||
*/
|
||||
private static function _specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorCode)
|
||||
{
|
||||
$description = isset($resp['error_description']) ? $resp['error_description'] : $errorCode;
|
||||
|
||||
switch ($errorCode) {
|
||||
case 'invalid_client':
|
||||
return Exception\OAuth\InvalidClientException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
|
||||
|
||||
case 'invalid_grant':
|
||||
return Exception\OAuth\InvalidGrantException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
|
||||
|
||||
case 'invalid_request':
|
||||
return Exception\OAuth\InvalidRequestException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
|
||||
|
||||
case 'invalid_scope':
|
||||
return Exception\OAuth\InvalidScopeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
|
||||
|
||||
case 'unsupported_grant_type':
|
||||
return Exception\OAuth\UnsupportedGrantTypeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
|
||||
|
||||
case 'unsupported_response_type':
|
||||
return Exception\OAuth\UnsupportedResponseTypeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
|
||||
|
||||
default:
|
||||
return Exception\OAuth\UnknownOAuthErrorException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* @param null|array $appInfo
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
private static function _formatAppInfo($appInfo)
|
||||
{
|
||||
if (null !== $appInfo) {
|
||||
$string = $appInfo['name'];
|
||||
if (\array_key_exists('version', $appInfo) && null !== $appInfo['version']) {
|
||||
$string .= '/' . $appInfo['version'];
|
||||
}
|
||||
if (\array_key_exists('url', $appInfo) && null !== $appInfo['url']) {
|
||||
$string .= ' (' . $appInfo['url'] . ')';
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* @param string $disableFunctionsOutput - String value of the 'disable_function' setting, as output by \ini_get('disable_functions')
|
||||
* @param string $functionName - Name of the function we are interesting in seeing whether or not it is disabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function _isDisabled($disableFunctionsOutput, $functionName)
|
||||
{
|
||||
$disabledFunctions = \explode(',', $disableFunctionsOutput);
|
||||
foreach ($disabledFunctions as $disabledFunction) {
|
||||
if (\trim($disabledFunction) === $functionName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* @param string $apiKey the Stripe API key, to be used in regular API requests
|
||||
* @param null $clientInfo client user agent information
|
||||
* @param null $appInfo information to identify a plugin that integrates Stripe using this library
|
||||
* @param 'v1'|'v2' $apiMode
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function _defaultHeaders($apiKey, $clientInfo = null, $appInfo = null, $apiMode = 'v1')
|
||||
{
|
||||
$uaString = "Stripe/{$apiMode} PhpBindings/" . Stripe::VERSION;
|
||||
|
||||
$langVersion = \PHP_VERSION;
|
||||
$uname_disabled = self::_isDisabled(\ini_get('disable_functions'), 'php_uname');
|
||||
$uname = $uname_disabled ? '(disabled)' : \php_uname();
|
||||
|
||||
// Fallback to global configuration to maintain backwards compatibility.
|
||||
$appInfo = $appInfo ?: Stripe::getAppInfo();
|
||||
$ua = [
|
||||
'bindings_version' => Stripe::VERSION,
|
||||
'lang' => 'php',
|
||||
'lang_version' => $langVersion,
|
||||
'publisher' => 'stripe',
|
||||
'uname' => $uname,
|
||||
];
|
||||
if ($clientInfo) {
|
||||
$ua = \array_merge($clientInfo, $ua);
|
||||
}
|
||||
if (null !== $appInfo) {
|
||||
$uaString .= ' ' . self::_formatAppInfo($appInfo);
|
||||
$ua['application'] = $appInfo;
|
||||
}
|
||||
|
||||
return [
|
||||
'X-Stripe-Client-User-Agent' => \json_encode($ua),
|
||||
'User-Agent' => $uaString,
|
||||
'Authorization' => 'Bearer ' . $apiKey,
|
||||
'Stripe-Version' => Stripe::getApiVersion(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'delete'|'get'|'post' $method
|
||||
* @param string $url
|
||||
* @param array $params
|
||||
* @param array $headers
|
||||
* @param 'v1'|'v2' $apiMode
|
||||
*/
|
||||
private function _prepareRequest($method, $url, $params, $headers, $apiMode)
|
||||
{
|
||||
$myApiKey = $this->_apiKey;
|
||||
if (!$myApiKey) {
|
||||
$myApiKey = Stripe::$apiKey;
|
||||
}
|
||||
|
||||
if (!$myApiKey) {
|
||||
$msg = 'No API key provided. (HINT: set your API key using '
|
||||
. '"Stripe::setApiKey(<API-KEY>)". You can generate API keys from '
|
||||
. 'the Stripe web interface. See https://stripe.com/api for '
|
||||
. 'details, or email support@stripe.com if you have any questions.';
|
||||
|
||||
throw new Exception\AuthenticationException($msg);
|
||||
}
|
||||
|
||||
// Clients can supply arbitrary additional keys to be included in the
|
||||
// X-Stripe-Client-User-Agent header via the optional getUserAgentInfo()
|
||||
// method
|
||||
$clientUAInfo = null;
|
||||
if (\method_exists(self::httpClient(), 'getUserAgentInfo')) {
|
||||
$clientUAInfo = self::httpClient()->getUserAgentInfo();
|
||||
}
|
||||
|
||||
if ($params && \is_array($params)) {
|
||||
$optionKeysInParams = \array_filter(
|
||||
self::$OPTIONS_KEYS,
|
||||
function ($key) use ($params) {
|
||||
return \array_key_exists($key, $params);
|
||||
}
|
||||
);
|
||||
if (\count($optionKeysInParams) > 0) {
|
||||
$message = \sprintf('Options found in $params: %s. Options should '
|
||||
. 'be passed in their own array after $params. (HINT: pass an '
|
||||
. 'empty array to $params if you do not have any.)', \implode(', ', $optionKeysInParams));
|
||||
\trigger_error($message, \E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
$absUrl = $this->_apiBase . $url;
|
||||
if ('v1' === $apiMode) {
|
||||
$params = self::_encodeObjects($params);
|
||||
}
|
||||
$defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo, $this->_appInfo, $apiMode);
|
||||
|
||||
if (Stripe::$accountId) {
|
||||
$defaultHeaders['Stripe-Account'] = Stripe::$accountId;
|
||||
}
|
||||
|
||||
if (Stripe::$enableTelemetry && null !== self::$requestTelemetry) {
|
||||
$defaultHeaders['X-Stripe-Client-Telemetry'] = self::_telemetryJson(self::$requestTelemetry);
|
||||
}
|
||||
|
||||
$hasFile = false;
|
||||
foreach ($params as $k => $v) {
|
||||
if (\is_resource($v)) {
|
||||
$hasFile = true;
|
||||
$params[$k] = self::_processResourceParam($v);
|
||||
} elseif ($v instanceof \CURLFile) {
|
||||
$hasFile = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasFile) {
|
||||
$defaultHeaders['Content-Type'] = 'multipart/form-data';
|
||||
} elseif ('v2' === $apiMode) {
|
||||
$defaultHeaders['Content-Type'] = 'application/json';
|
||||
} elseif ('v1' === $apiMode) {
|
||||
$defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
} else {
|
||||
throw new Exception\InvalidArgumentException('Unknown API mode: ' . $apiMode);
|
||||
}
|
||||
|
||||
$combinedHeaders = \array_merge($defaultHeaders, $headers);
|
||||
$rawHeaders = [];
|
||||
|
||||
foreach ($combinedHeaders as $header => $value) {
|
||||
$rawHeaders[] = $header . ': ' . $value;
|
||||
}
|
||||
|
||||
return [$absUrl, $rawHeaders, $params, $hasFile, $myApiKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'delete'|'get'|'post' $method
|
||||
* @param string $url
|
||||
* @param array $params
|
||||
* @param array $headers
|
||||
* @param 'v1'|'v2' $apiMode
|
||||
* @param string[] $usage
|
||||
*
|
||||
* @throws Exception\AuthenticationException
|
||||
* @throws Exception\ApiConnectionException
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function _requestRaw($method, $url, $params, $headers, $apiMode, $usage)
|
||||
{
|
||||
list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers, $apiMode);
|
||||
|
||||
// for some reason, PHP users will sometimes include null bytes in their paths, which leads to cryptic server 400s.
|
||||
// we'll be louder about this to help catch issues earlier.
|
||||
if (false !== \strpos($absUrl, "\0") || false !== \strpos($absUrl, '%00')) {
|
||||
throw new Exception\InvalidRequestException("URLs may not contain null bytes ('\\0'); double check any IDs you're including with the request.");
|
||||
}
|
||||
|
||||
$requestStartMs = Util\Util::currentTimeMillis();
|
||||
|
||||
list($rbody, $rcode, $rheaders) = self::httpClient()->request(
|
||||
$method,
|
||||
$absUrl,
|
||||
$rawHeaders,
|
||||
$params,
|
||||
$hasFile,
|
||||
$apiMode
|
||||
);
|
||||
|
||||
if (
|
||||
isset($rheaders['request-id'])
|
||||
&& \is_string($rheaders['request-id'])
|
||||
&& '' !== $rheaders['request-id']
|
||||
) {
|
||||
self::$requestTelemetry = new RequestTelemetry(
|
||||
$rheaders['request-id'],
|
||||
Util\Util::currentTimeMillis() - $requestStartMs,
|
||||
$usage
|
||||
);
|
||||
}
|
||||
|
||||
return [$rbody, $rcode, $rheaders, $myApiKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'delete'|'get'|'post' $method
|
||||
* @param string $url
|
||||
* @param array $params
|
||||
* @param array $headers
|
||||
* @param string[] $usage
|
||||
* @param callable $readBodyChunkCallable
|
||||
* @param 'v1'|'v2' $apiMode
|
||||
*
|
||||
* @throws Exception\AuthenticationException
|
||||
* @throws Exception\ApiConnectionException
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function _requestRawStreaming($method, $url, $params, $headers, $apiMode, $usage, $readBodyChunkCallable)
|
||||
{
|
||||
list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers, $apiMode);
|
||||
|
||||
$requestStartMs = Util\Util::currentTimeMillis();
|
||||
|
||||
list($rbody, $rcode, $rheaders) = self::streamingHttpClient()->requestStream(
|
||||
$method,
|
||||
$absUrl,
|
||||
$rawHeaders,
|
||||
$params,
|
||||
$hasFile,
|
||||
$readBodyChunkCallable
|
||||
);
|
||||
|
||||
if (
|
||||
isset($rheaders['request-id'])
|
||||
&& \is_string($rheaders['request-id'])
|
||||
&& '' !== $rheaders['request-id']
|
||||
) {
|
||||
self::$requestTelemetry = new RequestTelemetry(
|
||||
$rheaders['request-id'],
|
||||
Util\Util::currentTimeMillis() - $requestStartMs
|
||||
);
|
||||
}
|
||||
|
||||
return [$rbody, $rcode, $rheaders, $myApiKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $resource
|
||||
*
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*
|
||||
* @return \CURLFile|string
|
||||
*/
|
||||
private function _processResourceParam($resource)
|
||||
{
|
||||
if ('stream' !== \get_resource_type($resource)) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Attempted to upload a resource that is not a stream'
|
||||
);
|
||||
}
|
||||
|
||||
$metaData = \stream_get_meta_data($resource);
|
||||
if ('plainfile' !== $metaData['wrapper_type']) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Only plainfile resource streams are supported'
|
||||
);
|
||||
}
|
||||
|
||||
// We don't have the filename or mimetype, but the API doesn't care
|
||||
return new \CURLFile($metaData['uri']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $rbody
|
||||
* @param int $rcode
|
||||
* @param array $rheaders
|
||||
* @param 'v1'|'v2' $apiMode
|
||||
*
|
||||
* @throws Exception\UnexpectedValueException
|
||||
* @throws Exception\ApiErrorException
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function _interpretResponse($rbody, $rcode, $rheaders, $apiMode)
|
||||
{
|
||||
$resp = \json_decode($rbody, true);
|
||||
$jsonError = \json_last_error();
|
||||
if (null === $resp && \JSON_ERROR_NONE !== $jsonError) {
|
||||
$msg = "Invalid response body from API: {$rbody} "
|
||||
. "(HTTP response code was {$rcode}, json_last_error() was {$jsonError})";
|
||||
|
||||
throw new Exception\UnexpectedValueException($msg, $rcode);
|
||||
}
|
||||
|
||||
if ($rcode < 200 || $rcode >= 300) {
|
||||
$this->handleErrorResponse($rbody, $rcode, $rheaders, $resp, $apiMode);
|
||||
}
|
||||
|
||||
return $resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* @param HttpClient\ClientInterface $client
|
||||
*/
|
||||
public static function setHttpClient($client)
|
||||
{
|
||||
self::$_httpClient = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* @param HttpClient\StreamingClientInterface $client
|
||||
*/
|
||||
public static function setStreamingHttpClient($client)
|
||||
{
|
||||
self::$_streamingHttpClient = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* Resets any stateful telemetry data
|
||||
*/
|
||||
public static function resetTelemetry()
|
||||
{
|
||||
self::$requestTelemetry = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HttpClient\ClientInterface
|
||||
*/
|
||||
public static function httpClient()
|
||||
{
|
||||
if (!self::$_httpClient) {
|
||||
self::$_httpClient = HttpClient\CurlClient::instance();
|
||||
}
|
||||
|
||||
return self::$_httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HttpClient\StreamingClientInterface
|
||||
*/
|
||||
public static function streamingHttpClient()
|
||||
{
|
||||
if (!self::$_streamingHttpClient) {
|
||||
self::$_streamingHttpClient = HttpClient\CurlClient::instance();
|
||||
}
|
||||
|
||||
return self::$_streamingHttpClient;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* Class ApiResource.
|
||||
*
|
||||
* */
|
||||
abstract class ApiResource extends StripeObject
|
||||
{
|
||||
use ApiOperations\Request;
|
||||
|
||||
/**
|
||||
* @return \Stripe\Util\Set A list of fields that can be their own type of
|
||||
* API resource (say a nested card under an account for example), and if
|
||||
* that resource is set, it should be transmitted to the API on a create or
|
||||
* update. Doing so is not the default behavior because API resources
|
||||
* should normally be persisted on their own RESTful endpoints.
|
||||
*/
|
||||
public static function getSavedNestedResources()
|
||||
{
|
||||
static $savedNestedResources = null;
|
||||
if (null === $savedNestedResources) {
|
||||
$savedNestedResources = new Util\Set();
|
||||
}
|
||||
|
||||
return $savedNestedResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var bool A flag that can be set a behavior that will cause this
|
||||
* resource to be encoded and sent up along with an update of its parent
|
||||
* resource. This is usually not desirable because resources are updated
|
||||
* individually on their own endpoints, but there are certain cases,
|
||||
* replacing a customer's source for example, where this is allowed.
|
||||
*/
|
||||
public $saveWithParent = false;
|
||||
|
||||
public function __set($k, $v)
|
||||
{
|
||||
parent::__set($k, $v);
|
||||
$v = $this->{$k};
|
||||
if ((static::getSavedNestedResources()->includes($k))
|
||||
&& ($v instanceof ApiResource)) {
|
||||
$v->saveWithParent = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception\ApiErrorException
|
||||
*
|
||||
* @return ApiResource the refreshed resource
|
||||
*/
|
||||
public function refresh()
|
||||
{
|
||||
$requestor = new ApiRequestor($this->_opts->apiKey, static::baseUrl());
|
||||
$url = $this->instanceUrl();
|
||||
|
||||
list($response, $this->_opts->apiKey) = $requestor->request(
|
||||
'get',
|
||||
$url,
|
||||
$this->_retrieveOptions,
|
||||
$this->_opts->headers
|
||||
);
|
||||
$this->setLastResponse($response);
|
||||
$this->refreshFrom($response->json, $this->_opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the base URL for the given class
|
||||
*/
|
||||
public static function baseUrl()
|
||||
{
|
||||
return Stripe::$apiBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the endpoint URL for the given class
|
||||
*/
|
||||
public static function classUrl()
|
||||
{
|
||||
// Replace dots with slashes for namespaced resources, e.g. if the object's name is
|
||||
// "foo.bar", then its URL will be "/v1/foo/bars".
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$base = \str_replace('.', '/', static::OBJECT_NAME);
|
||||
|
||||
return "/v1/{$base}s";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|string $id the ID of the resource
|
||||
*
|
||||
* @throws Exception\UnexpectedValueException if $id is null
|
||||
*
|
||||
* @return string the instance endpoint URL for the given class
|
||||
*/
|
||||
public static function resourceUrl($id)
|
||||
{
|
||||
if (null === $id) {
|
||||
$class = static::class;
|
||||
$message = 'Could not determine which URL to request: '
|
||||
. "{$class} instance has invalid ID: {$id}";
|
||||
|
||||
throw new Exception\UnexpectedValueException($message);
|
||||
}
|
||||
$id = Util\Util::utf8($id);
|
||||
$base = static::classUrl();
|
||||
$extn = \urlencode($id);
|
||||
|
||||
return "{$base}/{$extn}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the full API URL for this API resource
|
||||
*/
|
||||
public function instanceUrl()
|
||||
{
|
||||
return static::resourceUrl($this['id']);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
use Stripe\Util\CaseInsensitiveArray;
|
||||
|
||||
/**
|
||||
* Class ApiResponse.
|
||||
*/
|
||||
class ApiResponse
|
||||
{
|
||||
/**
|
||||
* @var null|array|CaseInsensitiveArray
|
||||
*/
|
||||
public $headers;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $body;
|
||||
|
||||
/**
|
||||
* @var null|array
|
||||
*/
|
||||
public $json;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $code;
|
||||
|
||||
/**
|
||||
* @param string $body
|
||||
* @param int $code
|
||||
* @param null|array|CaseInsensitiveArray $headers
|
||||
* @param null|array $json
|
||||
*/
|
||||
public function __construct($body, $code, $headers, $json)
|
||||
{
|
||||
$this->body = $body;
|
||||
$this->code = $code;
|
||||
$this->headers = $headers;
|
||||
$this->json = $json;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property string $domain_name
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
*/
|
||||
class ApplePayDomain extends ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'apple_pay_domain';
|
||||
|
||||
/**
|
||||
* Create an apple pay domain.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\ApplePayDomain the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an apple pay domain.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\ApplePayDomain the deleted resource
|
||||
*/
|
||||
public function delete($params = null, $opts = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
|
||||
$url = $this->instanceUrl();
|
||||
list($response, $opts) = $this->_request('delete', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* List apple pay domains.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\ApplePayDomain> of ApiResources
|
||||
*/
|
||||
public static function all($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl();
|
||||
|
||||
return static::_requestPage($url, \Stripe\Collection::class, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an apple pay domain.
|
||||
*
|
||||
* @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\ApplePayDomain
|
||||
*/
|
||||
public static function retrieve($id, $opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static($id, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The class URL for this resource. It needs to be special
|
||||
* cased because it doesn't fit into the standard resource pattern.
|
||||
*/
|
||||
public static function classUrl()
|
||||
{
|
||||
return '/v1/apple_pay/domains';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property null|string $name The name of the application.
|
||||
*/
|
||||
class Application extends ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'application';
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property string|\Stripe\Account $account ID of the Stripe account this fee was taken from.
|
||||
* @property int $amount Amount earned, in cents (or local equivalent).
|
||||
* @property int $amount_refunded Amount in cents (or local equivalent) refunded (can be less than the amount attribute on the fee if a partial refund was issued)
|
||||
* @property string|\Stripe\Application $application ID of the Connect application that earned the fee.
|
||||
* @property null|string|\Stripe\BalanceTransaction $balance_transaction Balance transaction that describes the impact of this collected application fee on your account balance (not including refunds).
|
||||
* @property string|\Stripe\Charge $charge ID of the charge that the application fee was taken from.
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property string $currency Three-letter <a href="https://www.iso.org/iso-4217-currency-codes.html">ISO currency code</a>, in lowercase. Must be a <a href="https://stripe.com/docs/currencies">supported currency</a>.
|
||||
* @property null|\Stripe\StripeObject $fee_source Polymorphic source of the application fee. Includes the ID of the object the application fee was created from.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property null|string|\Stripe\Charge $originating_transaction ID of the corresponding charge on the platform account, if this fee was the result of a charge using the <code>destination</code> parameter.
|
||||
* @property bool $refunded Whether the fee has been fully refunded. If the fee is only partially refunded, this attribute will still be false.
|
||||
* @property \Stripe\Collection<\Stripe\ApplicationFeeRefund> $refunds A list of refunds that have been applied to the fee.
|
||||
*/
|
||||
class ApplicationFee extends ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'application_fee';
|
||||
|
||||
use ApiOperations\NestedResource;
|
||||
|
||||
/**
|
||||
* Returns a list of application fees you’ve previously collected. The application
|
||||
* fees are returned in sorted order, with the most recent fees appearing first.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\ApplicationFee> of ApiResources
|
||||
*/
|
||||
public static function all($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl();
|
||||
|
||||
return static::_requestPage($url, \Stripe\Collection::class, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the details of an application fee that your account has collected. The
|
||||
* same information is returned when refunding the application fee.
|
||||
*
|
||||
* @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\ApplicationFee
|
||||
*/
|
||||
public static function retrieve($id, $opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static($id, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
const PATH_REFUNDS = '/refunds';
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the application fee on which to retrieve the application fee refunds
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\ApplicationFeeRefund> the list of application fee refunds
|
||||
*/
|
||||
public static function allRefunds($id, $params = null, $opts = null)
|
||||
{
|
||||
return self::_allNestedResources($id, static::PATH_REFUNDS, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the application fee on which to create the application fee refund
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\ApplicationFeeRefund
|
||||
*/
|
||||
public static function createRefund($id, $params = null, $opts = null)
|
||||
{
|
||||
return self::_createNestedResource($id, static::PATH_REFUNDS, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the application fee to which the application fee refund belongs
|
||||
* @param string $refundId the ID of the application fee refund to retrieve
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\ApplicationFeeRefund
|
||||
*/
|
||||
public static function retrieveRefund($id, $refundId, $params = null, $opts = null)
|
||||
{
|
||||
return self::_retrieveNestedResource($id, static::PATH_REFUNDS, $refundId, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the application fee to which the application fee refund belongs
|
||||
* @param string $refundId the ID of the application fee refund to update
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\ApplicationFeeRefund
|
||||
*/
|
||||
public static function updateRefund($id, $refundId, $params = null, $opts = null)
|
||||
{
|
||||
return self::_updateNestedResource($id, static::PATH_REFUNDS, $refundId, $params, $opts);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* <code>Application Fee Refund</code> objects allow you to refund an application fee that
|
||||
* has previously been created but not yet refunded. Funds will be refunded to
|
||||
* the Stripe account from which the fee was originally collected.
|
||||
*
|
||||
* Related guide: <a href="https://stripe.com/docs/connect/destination-charges#refunding-app-fee">Refunding application fees</a>
|
||||
*
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property int $amount Amount, in cents (or local equivalent).
|
||||
* @property null|string|\Stripe\BalanceTransaction $balance_transaction Balance transaction that describes the impact on your account balance.
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property string $currency Three-letter <a href="https://www.iso.org/iso-4217-currency-codes.html">ISO currency code</a>, in lowercase. Must be a <a href="https://stripe.com/docs/currencies">supported currency</a>.
|
||||
* @property string|\Stripe\ApplicationFee $fee ID of the application fee that was refunded.
|
||||
* @property null|\Stripe\StripeObject $metadata Set of <a href="https://stripe.com/docs/api/metadata">key-value pairs</a> that you can attach to an object. This can be useful for storing additional information about the object in a structured format.
|
||||
*/
|
||||
class ApplicationFeeRefund extends ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'fee_refund';
|
||||
|
||||
use ApiOperations\Update {
|
||||
save as protected _save;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the API URL for this Stripe refund
|
||||
*/
|
||||
public function instanceUrl()
|
||||
{
|
||||
$id = $this['id'];
|
||||
$fee = $this['fee'];
|
||||
if (!$id) {
|
||||
throw new Exception\UnexpectedValueException(
|
||||
'Could not determine which URL to request: ' .
|
||||
"class instance has invalid ID: {$id}",
|
||||
null
|
||||
);
|
||||
}
|
||||
$id = Util\Util::utf8($id);
|
||||
$fee = Util\Util::utf8($fee);
|
||||
|
||||
$base = ApplicationFee::classUrl();
|
||||
$feeExtn = \urlencode($fee);
|
||||
$extn = \urlencode($id);
|
||||
|
||||
return "{$base}/{$feeExtn}/refunds/{$extn}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @return ApplicationFeeRefund the saved refund
|
||||
*/
|
||||
public function save($opts = null)
|
||||
{
|
||||
return $this->_save($opts);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe\Apps;
|
||||
|
||||
/**
|
||||
* Secret Store is an API that allows Stripe Apps developers to securely persist secrets for use by UI Extensions and app backends.
|
||||
*
|
||||
* The primary resource in Secret Store is a <code>secret</code>. Other apps can't view secrets created by an app. Additionally, secrets are scoped to provide further permission control.
|
||||
*
|
||||
* All Dashboard users and the app backend share <code>account</code> scoped secrets. Use the <code>account</code> scope for secrets that don't change per-user, like a third-party API key.
|
||||
*
|
||||
* A <code>user</code> scoped secret is accessible by the app backend and one specific Dashboard user. Use the <code>user</code> scope for per-user secrets like per-user OAuth tokens, where different users might have different permissions.
|
||||
*
|
||||
* Related guide: <a href="https://stripe.com/docs/stripe-apps/store-auth-data-custom-objects">Store data between page reloads</a>
|
||||
*
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property null|bool $deleted If true, indicates that this secret has been deleted
|
||||
* @property null|int $expires_at The Unix timestamp for the expiry time of the secret, after which the secret deletes.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property string $name A name for the secret that's unique within the scope.
|
||||
* @property null|string $payload The plaintext secret value to be stored.
|
||||
* @property \Stripe\StripeObject $scope
|
||||
*/
|
||||
class Secret extends \Stripe\ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'apps.secret';
|
||||
|
||||
/**
|
||||
* Create or replace a secret in the secret store.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Apps\Secret the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all secrets stored on the given scope.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\Apps\Secret> of ApiResources
|
||||
*/
|
||||
public static function all($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl();
|
||||
|
||||
return static::_requestPage($url, \Stripe\Collection::class, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Apps\Secret the deleted secret
|
||||
*/
|
||||
public static function deleteWhere($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl() . '/delete';
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $opts);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Apps\Secret the finded secret
|
||||
*/
|
||||
public static function find($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl() . '/find';
|
||||
list($response, $opts) = static::_staticRequest('get', $url, $params, $opts);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* This is an object representing your Stripe balance. You can retrieve it to see
|
||||
* the balance currently on your Stripe account.
|
||||
*
|
||||
* You can also retrieve the balance history, which contains a list of
|
||||
* <a href="https://stripe.com/docs/reporting/balance-transaction-types">transactions</a> that contributed to the balance
|
||||
* (charges, payouts, and so forth).
|
||||
*
|
||||
* The available and pending amounts for each currency are broken down further by
|
||||
* payment source types.
|
||||
*
|
||||
* Related guide: <a href="https://stripe.com/docs/connect/account-balances">Understanding Connect account balances</a>
|
||||
*
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property \Stripe\StripeObject[] $available Available funds that you can transfer or pay out automatically by Stripe or explicitly through the <a href="https://stripe.com/docs/api#transfers">Transfers API</a> or <a href="https://stripe.com/docs/api#payouts">Payouts API</a>. You can find the available balance for each currency and payment type in the <code>source_types</code> property.
|
||||
* @property null|\Stripe\StripeObject[] $connect_reserved Funds held due to negative balances on connected accounts where <a href="/api/accounts/object#account_object-controller-requirement_collection">account.controller.requirement_collection</a> is <code>application</code>, which includes Custom accounts. You can find the connect reserve balance for each currency and payment type in the <code>source_types</code> property.
|
||||
* @property null|\Stripe\StripeObject[] $instant_available Funds that you can pay out using Instant Payouts.
|
||||
* @property null|\Stripe\StripeObject $issuing
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property \Stripe\StripeObject[] $pending Funds that aren't available in the balance yet. You can find the pending balance for each currency and each payment type in the <code>source_types</code> property.
|
||||
*/
|
||||
class Balance extends SingletonApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'balance';
|
||||
|
||||
/**
|
||||
* Retrieves the current account balance, based on the authentication that was used
|
||||
* to make the request. For a sample request, see <a
|
||||
* href="/docs/connect/account-balances#accounting-for-negative-balances">Accounting
|
||||
* for negative balances</a>.
|
||||
*
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Balance
|
||||
*/
|
||||
public static function retrieve($opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static(null, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* Balance transactions represent funds moving through your Stripe account.
|
||||
* Stripe creates them for every type of transaction that enters or leaves your Stripe account balance.
|
||||
*
|
||||
* Related guide: <a href="https://stripe.com/docs/reports/balance-transaction-types">Balance transaction types</a>
|
||||
*
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property int $amount Gross amount of this transaction (in cents (or local equivalent)). A positive value represents funds charged to another party, and a negative value represents funds sent to another party.
|
||||
* @property int $available_on The date that the transaction's net funds become available in the Stripe balance.
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property string $currency Three-letter <a href="https://www.iso.org/iso-4217-currency-codes.html">ISO currency code</a>, in lowercase. Must be a <a href="https://stripe.com/docs/currencies">supported currency</a>.
|
||||
* @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users.
|
||||
* @property null|float $exchange_rate If applicable, this transaction uses an exchange rate. If money converts from currency A to currency B, then the <code>amount</code> in currency A, multipled by the <code>exchange_rate</code>, equals the <code>amount</code> in currency B. For example, if you charge a customer 10.00 EUR, the PaymentIntent's <code>amount</code> is <code>1000</code> and <code>currency</code> is <code>eur</code>. If this converts to 12.34 USD in your Stripe account, the BalanceTransaction's <code>amount</code> is <code>1234</code>, its <code>currency</code> is <code>usd</code>, and the <code>exchange_rate</code> is <code>1.234</code>.
|
||||
* @property int $fee Fees (in cents (or local equivalent)) paid for this transaction. Represented as a positive integer when assessed.
|
||||
* @property \Stripe\StripeObject[] $fee_details Detailed breakdown of fees (in cents (or local equivalent)) paid for this transaction.
|
||||
* @property int $net Net impact to a Stripe balance (in cents (or local equivalent)). A positive value represents incrementing a Stripe balance, and a negative value decrementing a Stripe balance. You can calculate the net impact of a transaction on a balance by <code>amount</code> - <code>fee</code>
|
||||
* @property string $reporting_category Learn more about how <a href="https://stripe.com/docs/reports/reporting-categories">reporting categories</a> can help you understand balance transactions from an accounting perspective.
|
||||
* @property null|string|\Stripe\ApplicationFee|\Stripe\ApplicationFeeRefund|\Stripe\Charge|\Stripe\ConnectCollectionTransfer|\Stripe\CustomerCashBalanceTransaction|\Stripe\Dispute|\Stripe\Issuing\Authorization|\Stripe\Issuing\Dispute|\Stripe\Issuing\Transaction|\Stripe\Payout|\Stripe\Refund|\Stripe\ReserveTransaction|\Stripe\TaxDeductedAtSource|\Stripe\Topup|\Stripe\Transfer|\Stripe\TransferReversal $source This transaction relates to the Stripe object.
|
||||
* @property string $status The transaction's net funds status in the Stripe balance, which are either <code>available</code> or <code>pending</code>.
|
||||
* @property string $type Transaction type: <code>adjustment</code>, <code>advance</code>, <code>advance_funding</code>, <code>anticipation_repayment</code>, <code>application_fee</code>, <code>application_fee_refund</code>, <code>charge</code>, <code>climate_order_purchase</code>, <code>climate_order_refund</code>, <code>connect_collection_transfer</code>, <code>contribution</code>, <code>issuing_authorization_hold</code>, <code>issuing_authorization_release</code>, <code>issuing_dispute</code>, <code>issuing_transaction</code>, <code>obligation_outbound</code>, <code>obligation_reversal_inbound</code>, <code>payment</code>, <code>payment_failure_refund</code>, <code>payment_network_reserve_hold</code>, <code>payment_network_reserve_release</code>, <code>payment_refund</code>, <code>payment_reversal</code>, <code>payment_unreconciled</code>, <code>payout</code>, <code>payout_cancel</code>, <code>payout_failure</code>, <code>payout_minimum_balance_hold</code>, <code>payout_minimum_balance_release</code>, <code>refund</code>, <code>refund_failure</code>, <code>reserve_transaction</code>, <code>reserved_funds</code>, <code>stripe_fee</code>, <code>stripe_fx_fee</code>, <code>tax_fee</code>, <code>topup</code>, <code>topup_reversal</code>, <code>transfer</code>, <code>transfer_cancel</code>, <code>transfer_failure</code>, or <code>transfer_refund</code>. Learn more about <a href="https://stripe.com/docs/reports/balance-transaction-types">balance transaction types and what they represent</a>. To classify transactions for accounting purposes, consider <code>reporting_category</code> instead.
|
||||
*/
|
||||
class BalanceTransaction extends ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'balance_transaction';
|
||||
|
||||
const TYPE_ADJUSTMENT = 'adjustment';
|
||||
const TYPE_ADVANCE = 'advance';
|
||||
const TYPE_ADVANCE_FUNDING = 'advance_funding';
|
||||
const TYPE_ANTICIPATION_REPAYMENT = 'anticipation_repayment';
|
||||
const TYPE_APPLICATION_FEE = 'application_fee';
|
||||
const TYPE_APPLICATION_FEE_REFUND = 'application_fee_refund';
|
||||
const TYPE_CHARGE = 'charge';
|
||||
const TYPE_CLIMATE_ORDER_PURCHASE = 'climate_order_purchase';
|
||||
const TYPE_CLIMATE_ORDER_REFUND = 'climate_order_refund';
|
||||
const TYPE_CONNECT_COLLECTION_TRANSFER = 'connect_collection_transfer';
|
||||
const TYPE_CONTRIBUTION = 'contribution';
|
||||
const TYPE_ISSUING_AUTHORIZATION_HOLD = 'issuing_authorization_hold';
|
||||
const TYPE_ISSUING_AUTHORIZATION_RELEASE = 'issuing_authorization_release';
|
||||
const TYPE_ISSUING_DISPUTE = 'issuing_dispute';
|
||||
const TYPE_ISSUING_TRANSACTION = 'issuing_transaction';
|
||||
const TYPE_OBLIGATION_OUTBOUND = 'obligation_outbound';
|
||||
const TYPE_OBLIGATION_REVERSAL_INBOUND = 'obligation_reversal_inbound';
|
||||
const TYPE_PAYMENT = 'payment';
|
||||
const TYPE_PAYMENT_FAILURE_REFUND = 'payment_failure_refund';
|
||||
const TYPE_PAYMENT_NETWORK_RESERVE_HOLD = 'payment_network_reserve_hold';
|
||||
const TYPE_PAYMENT_NETWORK_RESERVE_RELEASE = 'payment_network_reserve_release';
|
||||
const TYPE_PAYMENT_REFUND = 'payment_refund';
|
||||
const TYPE_PAYMENT_REVERSAL = 'payment_reversal';
|
||||
const TYPE_PAYMENT_UNRECONCILED = 'payment_unreconciled';
|
||||
const TYPE_PAYOUT = 'payout';
|
||||
const TYPE_PAYOUT_CANCEL = 'payout_cancel';
|
||||
const TYPE_PAYOUT_FAILURE = 'payout_failure';
|
||||
const TYPE_PAYOUT_MINIMUM_BALANCE_HOLD = 'payout_minimum_balance_hold';
|
||||
const TYPE_PAYOUT_MINIMUM_BALANCE_RELEASE = 'payout_minimum_balance_release';
|
||||
const TYPE_REFUND = 'refund';
|
||||
const TYPE_REFUND_FAILURE = 'refund_failure';
|
||||
const TYPE_RESERVED_FUNDS = 'reserved_funds';
|
||||
const TYPE_RESERVE_TRANSACTION = 'reserve_transaction';
|
||||
const TYPE_STRIPE_FEE = 'stripe_fee';
|
||||
const TYPE_STRIPE_FX_FEE = 'stripe_fx_fee';
|
||||
const TYPE_TAX_FEE = 'tax_fee';
|
||||
const TYPE_TOPUP = 'topup';
|
||||
const TYPE_TOPUP_REVERSAL = 'topup_reversal';
|
||||
const TYPE_TRANSFER = 'transfer';
|
||||
const TYPE_TRANSFER_CANCEL = 'transfer_cancel';
|
||||
const TYPE_TRANSFER_FAILURE = 'transfer_failure';
|
||||
const TYPE_TRANSFER_REFUND = 'transfer_refund';
|
||||
|
||||
/**
|
||||
* Returns a list of transactions that have contributed to the Stripe account
|
||||
* balance (e.g., charges, transfers, and so forth). The transactions are returned
|
||||
* in sorted order, with the most recent transactions appearing first.
|
||||
*
|
||||
* Note that this endpoint was previously called “Balance history” and used the
|
||||
* path <code>/v1/balance/history</code>.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\BalanceTransaction> of ApiResources
|
||||
*/
|
||||
public static function all($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl();
|
||||
|
||||
return static::_requestPage($url, \Stripe\Collection::class, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the balance transaction with the given ID.
|
||||
*
|
||||
* Note that this endpoint previously used the path
|
||||
* <code>/v1/balance/history/:id</code>.
|
||||
*
|
||||
* @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\BalanceTransaction
|
||||
*/
|
||||
public static function retrieve($id, $opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static($id, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* These bank accounts are payment methods on <code>Customer</code> objects.
|
||||
*
|
||||
* On the other hand <a href="/api#external_accounts">External Accounts</a> are transfer
|
||||
* destinations on <code>Account</code> objects for connected accounts.
|
||||
* They can be bank accounts or debit cards as well, and are documented in the links above.
|
||||
*
|
||||
* Related guide: <a href="/payments/bank-debits-transfers">Bank debits and transfers</a>
|
||||
*
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property null|string|\Stripe\Account $account The ID of the account that the bank account is associated with.
|
||||
* @property null|string $account_holder_name The name of the person or business that owns the bank account.
|
||||
* @property null|string $account_holder_type The type of entity that holds the account. This can be either <code>individual</code> or <code>company</code>.
|
||||
* @property null|string $account_type The bank account type. This can only be <code>checking</code> or <code>savings</code> in most countries. In Japan, this can only be <code>futsu</code> or <code>toza</code>.
|
||||
* @property null|string[] $available_payout_methods A set of available payout methods for this bank account. Only values from this set should be passed as the <code>method</code> when creating a payout.
|
||||
* @property null|string $bank_name Name of the bank associated with the routing number (e.g., <code>WELLS FARGO</code>).
|
||||
* @property string $country Two-letter ISO code representing the country the bank account is located in.
|
||||
* @property string $currency Three-letter <a href="https://stripe.com/docs/payouts">ISO code for the currency</a> paid out to the bank account.
|
||||
* @property null|string|\Stripe\Customer $customer The ID of the customer that the bank account is associated with.
|
||||
* @property null|bool $default_for_currency Whether this bank account is the default external account for its currency.
|
||||
* @property null|string $fingerprint Uniquely identifies this particular bank account. You can use this attribute to check whether two bank accounts are the same.
|
||||
* @property null|\Stripe\StripeObject $future_requirements Information about the <a href="https://stripe.com/docs/connect/custom-accounts/future-requirements">upcoming new requirements for the bank account</a>, including what information needs to be collected, and by when.
|
||||
* @property string $last4 The last four digits of the bank account number.
|
||||
* @property null|\Stripe\StripeObject $metadata Set of <a href="https://stripe.com/docs/api/metadata">key-value pairs</a> that you can attach to an object. This can be useful for storing additional information about the object in a structured format.
|
||||
* @property null|\Stripe\StripeObject $requirements Information about the requirements for the bank account, including what information needs to be collected.
|
||||
* @property null|string $routing_number The routing transit number for the bank account.
|
||||
* @property string $status <p>For bank accounts, possible values are <code>new</code>, <code>validated</code>, <code>verified</code>, <code>verification_failed</code>, or <code>errored</code>. A bank account that hasn't had any activity or validation performed is <code>new</code>. If Stripe can determine that the bank account exists, its status will be <code>validated</code>. Note that there often isn’t enough information to know (e.g., for smaller credit unions), and the validation is not always run. If customer bank account verification has succeeded, the bank account status will be <code>verified</code>. If the verification failed for any reason, such as microdeposit failure, the status will be <code>verification_failed</code>. If a payout sent to this bank account fails, we'll set the status to <code>errored</code> and will not continue to send <a href="https://stripe.com/docs/payouts#payout-schedule">scheduled payouts</a> until the bank details are updated.</p><p>For external accounts, possible values are <code>new</code>, <code>errored</code> and <code>verification_failed</code>. If a payout fails, the status is set to <code>errored</code> and scheduled payouts are stopped until account details are updated. In the US and India, if we can't <a href="https://support.stripe.com/questions/bank-account-ownership-verification">verify the owner of the bank account</a>, we'll set the status to <code>verification_failed</code>. Other validations aren't run against external accounts because they're only used for payouts. This means the other statuses don't apply.</p>
|
||||
*/
|
||||
class BankAccount extends ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'bank_account';
|
||||
|
||||
/**
|
||||
* Delete a specified external account for a given account.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\BankAccount the deleted resource
|
||||
*/
|
||||
public function delete($params = null, $opts = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
|
||||
$url = $this->instanceUrl();
|
||||
list($response, $opts) = $this->_request('delete', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible string representations of the bank verification status.
|
||||
*
|
||||
* @see https://stripe.com/docs/api/external_account_bank_accounts/object#account_bank_account_object-status
|
||||
*/
|
||||
const STATUS_NEW = 'new';
|
||||
const STATUS_VALIDATED = 'validated';
|
||||
const STATUS_VERIFIED = 'verified';
|
||||
const STATUS_VERIFICATION_FAILED = 'verification_failed';
|
||||
const STATUS_ERRORED = 'errored';
|
||||
|
||||
/**
|
||||
* @return string The instance URL for this resource. It needs to be special
|
||||
* cased because it doesn't fit into the standard resource pattern.
|
||||
*/
|
||||
public function instanceUrl()
|
||||
{
|
||||
if ($this['customer']) {
|
||||
$base = Customer::classUrl();
|
||||
$parent = $this['customer'];
|
||||
$path = 'sources';
|
||||
} elseif ($this['account']) {
|
||||
$base = Account::classUrl();
|
||||
$parent = $this['account'];
|
||||
$path = 'external_accounts';
|
||||
} else {
|
||||
$msg = 'Bank accounts cannot be accessed without a customer ID or account ID.';
|
||||
|
||||
throw new Exception\UnexpectedValueException($msg, null);
|
||||
}
|
||||
$parentExtn = \urlencode(Util\Util::utf8($parent));
|
||||
$extn = \urlencode(Util\Util::utf8($this['id']));
|
||||
|
||||
return "{$base}/{$parentExtn}/{$path}/{$extn}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $_id
|
||||
* @param null|array|string $_opts
|
||||
*
|
||||
* @throws \Stripe\Exception\BadMethodCallException
|
||||
*/
|
||||
public static function retrieve($_id, $_opts = null)
|
||||
{
|
||||
$msg = 'Bank accounts cannot be retrieved without a customer ID or ' .
|
||||
'an account ID. Retrieve a bank account using ' .
|
||||
"`Customer::retrieveSource('customer_id', " .
|
||||
"'bank_account_id')` or `Account::retrieveExternalAccount(" .
|
||||
"'account_id', 'bank_account_id')`.";
|
||||
|
||||
throw new Exception\BadMethodCallException($msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $_id
|
||||
* @param null|array $_params
|
||||
* @param null|array|string $_options
|
||||
*
|
||||
* @throws \Stripe\Exception\BadMethodCallException
|
||||
*/
|
||||
public static function update($_id, $_params = null, $_options = null)
|
||||
{
|
||||
$msg = 'Bank accounts cannot be updated without a customer ID or an ' .
|
||||
'account ID. Update a bank account using ' .
|
||||
"`Customer::updateSource('customer_id', 'bank_account_id', " .
|
||||
'$updateParams)` or `Account::updateExternalAccount(' .
|
||||
"'account_id', 'bank_account_id', \$updateParams)`.";
|
||||
|
||||
throw new Exception\BadMethodCallException($msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return static the saved resource
|
||||
*
|
||||
* @deprecated The `save` method is deprecated and will be removed in a
|
||||
* future major version of the library. Use the static method `update`
|
||||
* on the resource instead.
|
||||
*/
|
||||
public function save($opts = null)
|
||||
{
|
||||
$params = $this->serializeParameters();
|
||||
if (\count($params) > 0) {
|
||||
$url = $this->instanceUrl();
|
||||
list($response, $opts) = $this->_request('post', $url, $params, $opts, ['save']);
|
||||
$this->refreshFrom($response, $opts);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return BankAccount the verified bank account
|
||||
*/
|
||||
public function verify($params = null, $opts = null)
|
||||
{
|
||||
$url = $this->instanceUrl() . '/verify';
|
||||
list($response, $opts) = $this->_request('post', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,466 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
use Stripe\Util\Util;
|
||||
|
||||
class BaseStripeClient implements StripeClientInterface, StripeStreamingClientInterface
|
||||
{
|
||||
/** @var string default base URL for Stripe's API */
|
||||
const DEFAULT_API_BASE = 'https://api.stripe.com';
|
||||
|
||||
/** @var string default base URL for Stripe's OAuth API */
|
||||
const DEFAULT_CONNECT_BASE = 'https://connect.stripe.com';
|
||||
|
||||
/** @var string default base URL for Stripe's Files API */
|
||||
const DEFAULT_FILES_BASE = 'https://files.stripe.com';
|
||||
|
||||
/** @var string default base URL for Stripe's Meter Events API */
|
||||
const DEFAULT_METER_EVENTS_BASE = 'https://meter-events.stripe.com';
|
||||
|
||||
/** @var array<string, null|string> */
|
||||
const DEFAULT_CONFIG = [
|
||||
'api_key' => null,
|
||||
'app_info' => null,
|
||||
'client_id' => null,
|
||||
'stripe_account' => null,
|
||||
'stripe_context' => null,
|
||||
'stripe_version' => \Stripe\Util\ApiVersion::CURRENT,
|
||||
'api_base' => self::DEFAULT_API_BASE,
|
||||
'connect_base' => self::DEFAULT_CONNECT_BASE,
|
||||
'files_base' => self::DEFAULT_FILES_BASE,
|
||||
'meter_events_base' => self::DEFAULT_METER_EVENTS_BASE,
|
||||
];
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
private $config;
|
||||
|
||||
/** @var \Stripe\Util\RequestOptions */
|
||||
private $defaultOpts;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the {@link BaseStripeClient} class.
|
||||
*
|
||||
* The constructor takes a single argument. The argument can be a string, in which case it
|
||||
* should be the API key. It can also be an array with various configuration settings.
|
||||
*
|
||||
* Configuration settings include the following options:
|
||||
*
|
||||
* - api_key (null|string): the Stripe API key, to be used in regular API requests.
|
||||
* - app_info (null|array): information to identify a plugin that integrates Stripe using this library.
|
||||
* Expects: array{name: string, version?: string, url?: string, partner_id?: string}
|
||||
* - client_id (null|string): the Stripe client ID, to be used in OAuth requests.
|
||||
* - stripe_account (null|string): a Stripe account ID. If set, all requests sent by the client
|
||||
* will automatically use the {@code Stripe-Account} header with that account ID.
|
||||
* - stripe_context (null|string): a Stripe account or compartment ID. If set, all requests sent by the client
|
||||
* will automatically use the {@code Stripe-Context} header with that ID.
|
||||
* - stripe_version (null|string): a Stripe API version. If set, all requests sent by the client
|
||||
* will include the {@code Stripe-Version} header with that API version.
|
||||
*
|
||||
* The following configuration settings are also available, though setting these should rarely be necessary
|
||||
* (only useful if you want to send requests to a mock server like stripe-mock):
|
||||
*
|
||||
* - api_base (string): the base URL for regular API requests. Defaults to
|
||||
* {@link DEFAULT_API_BASE}.
|
||||
* - connect_base (string): the base URL for OAuth requests. Defaults to
|
||||
* {@link DEFAULT_CONNECT_BASE}.
|
||||
* - files_base (string): the base URL for file creation requests. Defaults to
|
||||
* {@link DEFAULT_FILES_BASE}.
|
||||
* - meter_events_base (string): the base URL for high throughput requests. Defaults to
|
||||
* {@link DEFAULT_METER_EVENTS_BASE}.
|
||||
*
|
||||
* @param array<string, mixed>|string $config the API key as a string, or an array containing
|
||||
* the client configuration settings
|
||||
*/
|
||||
public function __construct($config = [])
|
||||
{
|
||||
if (\is_string($config)) {
|
||||
$config = ['api_key' => $config];
|
||||
} elseif (!\is_array($config)) {
|
||||
throw new \Stripe\Exception\InvalidArgumentException('$config must be a string or an array');
|
||||
}
|
||||
|
||||
$config = \array_merge(self::DEFAULT_CONFIG, $config);
|
||||
$this->validateConfig($config);
|
||||
|
||||
$this->config = $config;
|
||||
|
||||
$this->defaultOpts = \Stripe\Util\RequestOptions::parse([
|
||||
'stripe_account' => $config['stripe_account'],
|
||||
'stripe_context' => $config['stripe_context'],
|
||||
'stripe_version' => $config['stripe_version'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API key used by the client to send requests.
|
||||
*
|
||||
* @return null|string the API key used by the client to send requests
|
||||
*/
|
||||
public function getApiKey()
|
||||
{
|
||||
return $this->config['api_key'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the client ID used by the client in OAuth requests.
|
||||
*
|
||||
* @return null|string the client ID used by the client in OAuth requests
|
||||
*/
|
||||
public function getClientId()
|
||||
{
|
||||
return $this->config['client_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base URL for Stripe's API.
|
||||
*
|
||||
* @return string the base URL for Stripe's API
|
||||
*/
|
||||
public function getApiBase()
|
||||
{
|
||||
return $this->config['api_base'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base URL for Stripe's OAuth API.
|
||||
*
|
||||
* @return string the base URL for Stripe's OAuth API
|
||||
*/
|
||||
public function getConnectBase()
|
||||
{
|
||||
return $this->config['connect_base'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base URL for Stripe's Files API.
|
||||
*
|
||||
* @return string the base URL for Stripe's Files API
|
||||
*/
|
||||
public function getFilesBase()
|
||||
{
|
||||
return $this->config['files_base'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base URL for Stripe's Meter Events API.
|
||||
*
|
||||
* @return string the base URL for Stripe's Meter Events API
|
||||
*/
|
||||
public function getMeterEventsBase()
|
||||
{
|
||||
return $this->config['meter_events_base'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the app info for this client.
|
||||
*
|
||||
* @return null|array information to identify a plugin that integrates Stripe using this library
|
||||
*/
|
||||
public function getAppInfo()
|
||||
{
|
||||
return $this->config['app_info'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to Stripe's API.
|
||||
*
|
||||
* @param 'delete'|'get'|'post' $method the HTTP method
|
||||
* @param string $path the path of the request
|
||||
* @param array $params the parameters of the request
|
||||
* @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
|
||||
*
|
||||
* @return \Stripe\StripeObject the object returned by Stripe's API
|
||||
*/
|
||||
public function request($method, $path, $params, $opts)
|
||||
{
|
||||
$defaultRequestOpts = $this->defaultOpts;
|
||||
$apiMode = \Stripe\Util\Util::getApiMode($path);
|
||||
|
||||
$opts = $defaultRequestOpts->merge($opts, true);
|
||||
|
||||
$baseUrl = $opts->apiBase ?: $this->getApiBase();
|
||||
$requestor = new \Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl, $this->getAppInfo());
|
||||
list($response, $opts->apiKey) = $requestor->request($method, $path, $params, $opts->headers, $apiMode, ['stripe_client']);
|
||||
$opts->discardNonPersistentHeaders();
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts, $apiMode);
|
||||
if (\is_array($obj)) {
|
||||
// Edge case for v2 endpoints that return empty/void response
|
||||
// Example: client->v2->billing->meterEventStream->create
|
||||
$obj = new \Stripe\StripeObject();
|
||||
}
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a raw request to Stripe's API. This is the lowest level method for interacting
|
||||
* with the Stripe API. This method is useful for interacting with endpoints that are not
|
||||
* covered yet in stripe-php.
|
||||
*
|
||||
* @param 'delete'|'get'|'post' $method the HTTP method
|
||||
* @param string $path the path of the request
|
||||
* @param null|array $params the parameters of the request
|
||||
* @param array $opts the special modifiers of the request
|
||||
*
|
||||
* @return \Stripe\ApiResponse
|
||||
*/
|
||||
public function rawRequest($method, $path, $params = null, $opts = [])
|
||||
{
|
||||
if ('post' !== $method && null !== $params) {
|
||||
throw new Exception\InvalidArgumentException('Error: rawRequest only supports $params on post requests. Please pass null and add your parameters to $path');
|
||||
}
|
||||
$apiMode = \Stripe\Util\Util::getApiMode($path);
|
||||
$headers = [];
|
||||
if (\is_array($opts) && \array_key_exists('headers', $opts)) {
|
||||
$headers = $opts['headers'] ?: [];
|
||||
unset($opts['headers']);
|
||||
}
|
||||
if (\is_array($opts) && \array_key_exists('stripe_context', $opts)) {
|
||||
$headers['Stripe-Context'] = $opts['stripe_context'];
|
||||
unset($opts['stripe_context']);
|
||||
}
|
||||
|
||||
$defaultRawRequestOpts = $this->defaultOpts;
|
||||
|
||||
$opts = $defaultRawRequestOpts->merge($opts, true);
|
||||
|
||||
// Concatenate $headers to $opts->headers, removing duplicates.
|
||||
$opts->headers = \array_merge($opts->headers, $headers);
|
||||
$baseUrl = $opts->apiBase ?: $this->getApiBase();
|
||||
$requestor = new \Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl);
|
||||
list($response) = $requestor->request($method, $path, $params, $opts->headers, $apiMode, ['raw_request']);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to Stripe's API, passing chunks of the streamed response
|
||||
* into a user-provided $readBodyChunkCallable callback.
|
||||
*
|
||||
* @param 'delete'|'get'|'post' $method the HTTP method
|
||||
* @param string $path the path of the request
|
||||
* @param callable $readBodyChunkCallable a function that will be called
|
||||
* @param array $params the parameters of the request
|
||||
* @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
|
||||
*
|
||||
* with chunks of bytes from the body if the request is successful
|
||||
*/
|
||||
public function requestStream($method, $path, $readBodyChunkCallable, $params, $opts)
|
||||
{
|
||||
$opts = $this->defaultOpts->merge($opts, true);
|
||||
$baseUrl = $opts->apiBase ?: $this->getApiBase();
|
||||
$requestor = new \Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl, $this->getAppInfo());
|
||||
$apiMode = \Stripe\Util\Util::getApiMode($path);
|
||||
list($response, $opts->apiKey) = $requestor->requestStream($method, $path, $readBodyChunkCallable, $params, $opts->headers, $apiMode, ['stripe_client']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to Stripe's API.
|
||||
*
|
||||
* @param 'delete'|'get'|'post' $method the HTTP method
|
||||
* @param string $path the path of the request
|
||||
* @param array $params the parameters of the request
|
||||
* @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
|
||||
*
|
||||
* @return \Stripe\Collection|\Stripe\V2\Collection of ApiResources
|
||||
*/
|
||||
public function requestCollection($method, $path, $params, $opts)
|
||||
{
|
||||
$obj = $this->request($method, $path, $params, $opts);
|
||||
$apiMode = \Stripe\Util\Util::getApiMode($path);
|
||||
if ('v1' === $apiMode) {
|
||||
if (!($obj instanceof \Stripe\Collection)) {
|
||||
$received_class = \get_class($obj);
|
||||
$msg = "Expected to receive `Stripe\\Collection` object from Stripe API. Instead received `{$received_class}`.";
|
||||
|
||||
throw new \Stripe\Exception\UnexpectedValueException($msg);
|
||||
}
|
||||
$obj->setFilters($params);
|
||||
} else {
|
||||
if (!($obj instanceof \Stripe\V2\Collection)) {
|
||||
$received_class = \get_class($obj);
|
||||
$msg = "Expected to receive `Stripe\\V2\\Collection` object from Stripe API. Instead received `{$received_class}`.";
|
||||
|
||||
throw new \Stripe\Exception\UnexpectedValueException($msg);
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to Stripe's API.
|
||||
*
|
||||
* @param 'delete'|'get'|'post' $method the HTTP method
|
||||
* @param string $path the path of the request
|
||||
* @param array $params the parameters of the request
|
||||
* @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
|
||||
*
|
||||
* @return \Stripe\SearchResult of ApiResources
|
||||
*/
|
||||
public function requestSearchResult($method, $path, $params, $opts)
|
||||
{
|
||||
$obj = $this->request($method, $path, $params, $opts);
|
||||
if (!($obj instanceof \Stripe\SearchResult)) {
|
||||
$received_class = \get_class($obj);
|
||||
$msg = "Expected to receive `Stripe\\SearchResult` object from Stripe API. Instead received `{$received_class}`.";
|
||||
|
||||
throw new \Stripe\Exception\UnexpectedValueException($msg);
|
||||
}
|
||||
$obj->setFilters($params);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Stripe\Util\RequestOptions $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\AuthenticationException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function apiKeyForRequest($opts)
|
||||
{
|
||||
$apiKey = $opts->apiKey ?: $this->getApiKey();
|
||||
|
||||
if (null === $apiKey) {
|
||||
$msg = 'No API key provided. Set your API key when constructing the '
|
||||
. 'StripeClient instance, or provide it on a per-request basis '
|
||||
. 'using the `api_key` key in the $opts argument.';
|
||||
|
||||
throw new \Stripe\Exception\AuthenticationException($msg);
|
||||
}
|
||||
|
||||
return $apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config
|
||||
*
|
||||
* @throws \Stripe\Exception\InvalidArgumentException
|
||||
*/
|
||||
private function validateConfig($config)
|
||||
{
|
||||
// api_key
|
||||
if (null !== $config['api_key'] && !\is_string($config['api_key'])) {
|
||||
throw new \Stripe\Exception\InvalidArgumentException('api_key must be null or a string');
|
||||
}
|
||||
|
||||
if (null !== $config['api_key'] && ('' === $config['api_key'])) {
|
||||
$msg = 'api_key cannot be the empty string';
|
||||
|
||||
throw new \Stripe\Exception\InvalidArgumentException($msg);
|
||||
}
|
||||
|
||||
if (null !== $config['api_key'] && (\preg_match('/\s/', $config['api_key']))) {
|
||||
$msg = 'api_key cannot contain whitespace';
|
||||
|
||||
throw new \Stripe\Exception\InvalidArgumentException($msg);
|
||||
}
|
||||
|
||||
// client_id
|
||||
if (null !== $config['client_id'] && !\is_string($config['client_id'])) {
|
||||
throw new \Stripe\Exception\InvalidArgumentException('client_id must be null or a string');
|
||||
}
|
||||
|
||||
// stripe_account
|
||||
if (null !== $config['stripe_account'] && !\is_string($config['stripe_account'])) {
|
||||
throw new \Stripe\Exception\InvalidArgumentException('stripe_account must be null or a string');
|
||||
}
|
||||
|
||||
// stripe_context
|
||||
if (null !== $config['stripe_context'] && !\is_string($config['stripe_context'])) {
|
||||
throw new \Stripe\Exception\InvalidArgumentException('stripe_context must be null or a string');
|
||||
}
|
||||
|
||||
// stripe_version
|
||||
if (null !== $config['stripe_version'] && !\is_string($config['stripe_version'])) {
|
||||
throw new \Stripe\Exception\InvalidArgumentException('stripe_version must be null or a string');
|
||||
}
|
||||
|
||||
// api_base
|
||||
if (!\is_string($config['api_base'])) {
|
||||
throw new \Stripe\Exception\InvalidArgumentException('api_base must be a string');
|
||||
}
|
||||
|
||||
// connect_base
|
||||
if (!\is_string($config['connect_base'])) {
|
||||
throw new \Stripe\Exception\InvalidArgumentException('connect_base must be a string');
|
||||
}
|
||||
|
||||
// files_base
|
||||
if (!\is_string($config['files_base'])) {
|
||||
throw new \Stripe\Exception\InvalidArgumentException('files_base must be a string');
|
||||
}
|
||||
|
||||
// app info
|
||||
if (null !== $config['app_info'] && !\is_array($config['app_info'])) {
|
||||
throw new \Stripe\Exception\InvalidArgumentException('app_info must be an array');
|
||||
}
|
||||
|
||||
$appInfoKeys = ['name', 'version', 'url', 'partner_id'];
|
||||
if (null !== $config['app_info'] && array_diff_key($config['app_info'], array_flip($appInfoKeys))) {
|
||||
$msg = 'app_info must be of type array{name: string, version?: string, url?: string, partner_id?: string}';
|
||||
|
||||
throw new \Stripe\Exception\InvalidArgumentException($msg);
|
||||
}
|
||||
|
||||
// check absence of extra keys
|
||||
$extraConfigKeys = \array_diff(\array_keys($config), \array_keys(self::DEFAULT_CONFIG));
|
||||
if (!empty($extraConfigKeys)) {
|
||||
// Wrap in single quote to more easily catch trailing spaces errors
|
||||
$invalidKeys = "'" . \implode("', '", $extraConfigKeys) . "'";
|
||||
|
||||
throw new \Stripe\Exception\InvalidArgumentException('Found unknown key(s) in configuration array: ' . $invalidKeys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the raw JSON string returned by rawRequest into a similar class.
|
||||
*
|
||||
* @param string $json
|
||||
* @param 'v1'|'v2' $apiMode
|
||||
*
|
||||
* @return \Stripe\StripeObject
|
||||
* */
|
||||
public function deserialize($json, $apiMode = 'v1')
|
||||
{
|
||||
return \Stripe\Util\Util::convertToStripeObject(\json_decode($json, true), [], $apiMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a V2\Events instance using the provided JSON payload. Throws an
|
||||
* Exception\UnexpectedValueException if the payload is not valid JSON, and
|
||||
* an Exception\SignatureVerificationException if the signature
|
||||
* verification fails for any reason.
|
||||
*
|
||||
* @param string $payload the payload sent by Stripe
|
||||
* @param string $sigHeader the contents of the signature header sent by
|
||||
* Stripe
|
||||
* @param string $secret secret used to generate the signature
|
||||
* @param int $tolerance maximum difference allowed between the header's
|
||||
* timestamp and the current time. Defaults to 300 seconds (5 min)
|
||||
*
|
||||
* @throws Exception\SignatureVerificationException if the verification fails
|
||||
* @throws Exception\UnexpectedValueException if the payload is not valid JSON,
|
||||
*
|
||||
* @return \Stripe\ThinEvent
|
||||
*/
|
||||
public function parseThinEvent($payload, $sigHeader, $secret, $tolerance = Webhook::DEFAULT_TOLERANCE)
|
||||
{
|
||||
$eventData = Util::utf8($payload);
|
||||
WebhookSignature::verifyHeader($payload, $sigHeader, $secret, $tolerance);
|
||||
|
||||
try {
|
||||
return Util::json_decode_thin_event_object(
|
||||
$eventData,
|
||||
'\Stripe\ThinEvent'
|
||||
);
|
||||
} catch (\ReflectionException $e) {
|
||||
// Fail gracefully
|
||||
return new \Stripe\ThinEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Stripe;
|
||||
|
||||
/**
|
||||
* Interface for a Stripe client.
|
||||
*/
|
||||
interface BaseStripeClientInterface
|
||||
{
|
||||
/**
|
||||
* Gets the API key used by the client to send requests.
|
||||
*
|
||||
* @return null|string the API key used by the client to send requests
|
||||
*/
|
||||
public function getApiKey();
|
||||
|
||||
/**
|
||||
* Gets the client ID used by the client in OAuth requests.
|
||||
*
|
||||
* @return null|string the client ID used by the client in OAuth requests
|
||||
*/
|
||||
public function getClientId();
|
||||
|
||||
/**
|
||||
* Gets the base URL for Stripe's API.
|
||||
*
|
||||
* @return string the base URL for Stripe's API
|
||||
*/
|
||||
public function getApiBase();
|
||||
|
||||
/**
|
||||
* Gets the base URL for Stripe's OAuth API.
|
||||
*
|
||||
* @return string the base URL for Stripe's OAuth API
|
||||
*/
|
||||
public function getConnectBase();
|
||||
|
||||
/**
|
||||
* Gets the base URL for Stripe's Files API.
|
||||
*
|
||||
* @return string the base URL for Stripe's Files API
|
||||
*/
|
||||
public function getFilesBase();
|
||||
|
||||
/**
|
||||
* Gets the base URL for Stripe's Meter Events API.
|
||||
*
|
||||
* @return string the base URL for Stripe's Meter Events API
|
||||
*/
|
||||
public function getMeterEventsBase();
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe\Billing;
|
||||
|
||||
/**
|
||||
* A billing alert is a resource that notifies you when a certain usage threshold on a meter is crossed. For example, you might create a billing alert to notify you when a certain user made 100 API requests.
|
||||
*
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property string $alert_type Defines the type of the alert.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property null|string $status Status of the alert. This can be active, inactive or archived.
|
||||
* @property string $title Title of the alert.
|
||||
* @property null|\Stripe\StripeObject $usage_threshold Encapsulates configuration of the alert to monitor usage on a specific <a href="https://stripe.com/docs/api/billing/meter">Billing Meter</a>.
|
||||
*/
|
||||
class Alert extends \Stripe\ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'billing.alert';
|
||||
|
||||
const STATUS_ACTIVE = 'active';
|
||||
const STATUS_ARCHIVED = 'archived';
|
||||
const STATUS_INACTIVE = 'inactive';
|
||||
|
||||
/**
|
||||
* Creates a billing alert.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\Alert the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists billing active and inactive alerts.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\Billing\Alert> of ApiResources
|
||||
*/
|
||||
public static function all($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl();
|
||||
|
||||
return static::_requestPage($url, \Stripe\Collection::class, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a billing alert given an ID.
|
||||
*
|
||||
* @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\Alert
|
||||
*/
|
||||
public static function retrieve($id, $opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static($id, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\Alert the activated alert
|
||||
*/
|
||||
public function activate($params = null, $opts = null)
|
||||
{
|
||||
$url = $this->instanceUrl() . '/activate';
|
||||
list($response, $opts) = $this->_request('post', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\Alert the archived alert
|
||||
*/
|
||||
public function archive($params = null, $opts = null)
|
||||
{
|
||||
$url = $this->instanceUrl() . '/archive';
|
||||
list($response, $opts) = $this->_request('post', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\Alert the deactivated alert
|
||||
*/
|
||||
public function deactivate($params = null, $opts = null)
|
||||
{
|
||||
$url = $this->instanceUrl() . '/deactivate';
|
||||
list($response, $opts) = $this->_request('post', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe\Billing;
|
||||
|
||||
/**
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property \Stripe\Billing\Alert $alert A billing alert is a resource that notifies you when a certain usage threshold on a meter is crossed. For example, you might create a billing alert to notify you when a certain user made 100 API requests.
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property string $customer ID of customer for which the alert triggered
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property int $value The value triggering the alert
|
||||
*/
|
||||
class AlertTriggered extends \Stripe\ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'billing.alert_triggered';
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe\Billing;
|
||||
|
||||
/**
|
||||
* Indicates the billing credit balance for billing credits granted to a customer.
|
||||
*
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property \Stripe\StripeObject[] $balances The billing credit balances. One entry per credit grant currency. If a customer only has credit grants in a single currency, then this will have a single balance entry.
|
||||
* @property string|\Stripe\Customer $customer The customer the balance is for.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
*/
|
||||
class CreditBalanceSummary extends \Stripe\SingletonApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'billing.credit_balance_summary';
|
||||
|
||||
/**
|
||||
* Retrieves the credit balance summary for a customer.
|
||||
*
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\CreditBalanceSummary
|
||||
*/
|
||||
public static function retrieve($opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static(null, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe\Billing;
|
||||
|
||||
/**
|
||||
* A credit balance transaction is a resource representing a transaction (either a credit or a debit) against an existing credit grant.
|
||||
*
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property null|\Stripe\StripeObject $credit Credit details for this credit balance transaction. Only present if type is <code>credit</code>.
|
||||
* @property string|\Stripe\Billing\CreditGrant $credit_grant The credit grant associated with this credit balance transaction.
|
||||
* @property null|\Stripe\StripeObject $debit Debit details for this credit balance transaction. Only present if type is <code>debit</code>.
|
||||
* @property int $effective_at The effective time of this credit balance transaction.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property null|string|\Stripe\TestHelpers\TestClock $test_clock ID of the test clock this credit balance transaction belongs to.
|
||||
* @property null|string $type The type of credit balance transaction (credit or debit).
|
||||
*/
|
||||
class CreditBalanceTransaction extends \Stripe\ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'billing.credit_balance_transaction';
|
||||
|
||||
const TYPE_CREDIT = 'credit';
|
||||
const TYPE_DEBIT = 'debit';
|
||||
|
||||
/**
|
||||
* Retrieve a list of credit balance transactions.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\Billing\CreditBalanceTransaction> of ApiResources
|
||||
*/
|
||||
public static function all($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl();
|
||||
|
||||
return static::_requestPage($url, \Stripe\Collection::class, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a credit balance transaction.
|
||||
*
|
||||
* @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\CreditBalanceTransaction
|
||||
*/
|
||||
public static function retrieve($id, $opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static($id, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe\Billing;
|
||||
|
||||
/**
|
||||
* A credit grant is an API resource that documents the allocation of some billing credits to a customer.
|
||||
*
|
||||
* Related guide: <a href="https://docs.stripe.com/billing/subscriptions/usage-based/billing-credits">Billing credits</a>
|
||||
*
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property \Stripe\StripeObject $amount
|
||||
* @property \Stripe\StripeObject $applicability_config
|
||||
* @property string $category The category of this credit grant. This is for tracking purposes and isn't displayed to the customer.
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property string|\Stripe\Customer $customer ID of the customer receiving the billing credits.
|
||||
* @property null|int $effective_at The time when the billing credits become effective-when they're eligible for use.
|
||||
* @property null|int $expires_at The time when the billing credits expire. If not present, the billing credits don't expire.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property \Stripe\StripeObject $metadata Set of <a href="https://stripe.com/docs/api/metadata">key-value pairs</a> that you can attach to an object. This can be useful for storing additional information about the object in a structured format.
|
||||
* @property null|string $name A descriptive name shown in dashboard.
|
||||
* @property null|int $priority The priority for applying this credit grant. The highest priority is 0 and the lowest is 100.
|
||||
* @property null|string|\Stripe\TestHelpers\TestClock $test_clock ID of the test clock this credit grant belongs to.
|
||||
* @property int $updated Time at which the object was last updated. Measured in seconds since the Unix epoch.
|
||||
* @property null|int $voided_at The time when this credit grant was voided. If not present, the credit grant hasn't been voided.
|
||||
*/
|
||||
class CreditGrant extends \Stripe\ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'billing.credit_grant';
|
||||
|
||||
use \Stripe\ApiOperations\Update;
|
||||
|
||||
const CATEGORY_PAID = 'paid';
|
||||
const CATEGORY_PROMOTIONAL = 'promotional';
|
||||
|
||||
/**
|
||||
* Creates a credit grant.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\CreditGrant the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of credit grants.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\Billing\CreditGrant> of ApiResources
|
||||
*/
|
||||
public static function all($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl();
|
||||
|
||||
return static::_requestPage($url, \Stripe\Collection::class, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a credit grant.
|
||||
*
|
||||
* @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\CreditGrant
|
||||
*/
|
||||
public static function retrieve($id, $opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static($id, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a credit grant.
|
||||
*
|
||||
* @param string $id the ID of the resource to update
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\CreditGrant the updated resource
|
||||
*/
|
||||
public static function update($id, $params = null, $opts = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::resourceUrl($id);
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $opts);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\CreditGrant the expired credit grant
|
||||
*/
|
||||
public function expire($params = null, $opts = null)
|
||||
{
|
||||
$url = $this->instanceUrl() . '/expire';
|
||||
list($response, $opts) = $this->_request('post', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\CreditGrant the voided credit grant
|
||||
*/
|
||||
public function voidGrant($params = null, $opts = null)
|
||||
{
|
||||
$url = $this->instanceUrl() . '/void';
|
||||
list($response, $opts) = $this->_request('post', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe\Billing;
|
||||
|
||||
/**
|
||||
* Meters specify how to aggregate meter events over a billing period. Meter events represent the actions that customers take in your system. Meters attach to prices and form the basis of the bill.
|
||||
*
|
||||
* Related guide: <a href="https://docs.stripe.com/billing/subscriptions/usage-based">Usage based billing</a>
|
||||
*
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property \Stripe\StripeObject $customer_mapping
|
||||
* @property \Stripe\StripeObject $default_aggregation
|
||||
* @property string $display_name The meter's name.
|
||||
* @property string $event_name The name of the meter event to record usage for. Corresponds with the <code>event_name</code> field on meter events.
|
||||
* @property null|string $event_time_window The time window to pre-aggregate meter events for, if any.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property string $status The meter's status.
|
||||
* @property \Stripe\StripeObject $status_transitions
|
||||
* @property int $updated Time at which the object was last updated. Measured in seconds since the Unix epoch.
|
||||
* @property \Stripe\StripeObject $value_settings
|
||||
*/
|
||||
class Meter extends \Stripe\ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'billing.meter';
|
||||
|
||||
use \Stripe\ApiOperations\NestedResource;
|
||||
use \Stripe\ApiOperations\Update;
|
||||
|
||||
const EVENT_TIME_WINDOW_DAY = 'day';
|
||||
const EVENT_TIME_WINDOW_HOUR = 'hour';
|
||||
|
||||
const STATUS_ACTIVE = 'active';
|
||||
const STATUS_INACTIVE = 'inactive';
|
||||
|
||||
/**
|
||||
* Creates a billing meter.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\Meter the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of billing meters.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\Billing\Meter> of ApiResources
|
||||
*/
|
||||
public static function all($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl();
|
||||
|
||||
return static::_requestPage($url, \Stripe\Collection::class, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a billing meter given an ID.
|
||||
*
|
||||
* @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\Meter
|
||||
*/
|
||||
public static function retrieve($id, $opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static($id, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a billing meter.
|
||||
*
|
||||
* @param string $id the ID of the resource to update
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\Meter the updated resource
|
||||
*/
|
||||
public static function update($id, $params = null, $opts = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::resourceUrl($id);
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $opts);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\Meter the deactivated meter
|
||||
*/
|
||||
public function deactivate($params = null, $opts = null)
|
||||
{
|
||||
$url = $this->instanceUrl() . '/deactivate';
|
||||
list($response, $opts) = $this->_request('post', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\Meter the reactivated meter
|
||||
*/
|
||||
public function reactivate($params = null, $opts = null)
|
||||
{
|
||||
$url = $this->instanceUrl() . '/reactivate';
|
||||
list($response, $opts) = $this->_request('post', $url, $params, $opts);
|
||||
$this->refreshFrom($response, $opts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
const PATH_EVENT_SUMMARIES = '/event_summaries';
|
||||
|
||||
/**
|
||||
* @param string $id the ID of the meter on which to retrieve the meter event summaries
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\Billing\MeterEventSummary> the list of meter event summaries
|
||||
*/
|
||||
public static function allEventSummaries($id, $params = null, $opts = null)
|
||||
{
|
||||
return self::_allNestedResources($id, static::PATH_EVENT_SUMMARIES, $params, $opts);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe\Billing;
|
||||
|
||||
/**
|
||||
* Meter events represent actions that customers take in your system. You can use meter events to bill a customer based on their usage. Meter events are associated with billing meters, which define both the contents of the event’s payload and how to aggregate those events.
|
||||
*
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property string $event_name The name of the meter event. Corresponds with the <code>event_name</code> field on a meter.
|
||||
* @property string $identifier A unique identifier for the event.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property \Stripe\StripeObject $payload The payload of the event. This contains the fields corresponding to a meter's <code>customer_mapping.event_payload_key</code> (default is <code>stripe_customer_id</code>) and <code>value_settings.event_payload_key</code> (default is <code>value</code>). Read more about the <a href="https://stripe.com/docs/billing/subscriptions/usage-based/recording-usage#payload-key-overrides">payload</a>.
|
||||
* @property int $timestamp The timestamp passed in when creating the event. Measured in seconds since the Unix epoch.
|
||||
*/
|
||||
class MeterEvent extends \Stripe\ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'billing.meter_event';
|
||||
|
||||
/**
|
||||
* Creates a billing meter event.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\MeterEvent the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe\Billing;
|
||||
|
||||
/**
|
||||
* A billing meter event adjustment is a resource that allows you to cancel a meter event. For example, you might create a billing meter event adjustment to cancel a meter event that was created in error or attached to the wrong customer.
|
||||
*
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property null|\Stripe\StripeObject $cancel Specifies which event to cancel.
|
||||
* @property string $event_name The name of the meter event. Corresponds with the <code>event_name</code> field on a meter.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property string $status The meter event adjustment's status.
|
||||
* @property string $type Specifies whether to cancel a single event or a range of events for a time period. Time period cancellation is not supported yet.
|
||||
*/
|
||||
class MeterEventAdjustment extends \Stripe\ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'billing.meter_event_adjustment';
|
||||
|
||||
const STATUS_COMPLETE = 'complete';
|
||||
const STATUS_PENDING = 'pending';
|
||||
|
||||
/**
|
||||
* Creates a billing meter event adjustment.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Billing\MeterEventAdjustment the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe\Billing;
|
||||
|
||||
/**
|
||||
* A billing meter event summary represents an aggregated view of a customer's billing meter events within a specified timeframe. It indicates how much
|
||||
* usage was accrued by a customer for that period.
|
||||
*
|
||||
* Note: Meters events are aggregated asynchronously so the meter event summaries provide an eventually consistent view of the reported usage.
|
||||
*
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property float $aggregated_value Aggregated value of all the events within <code>start_time</code> (inclusive) and <code>end_time</code> (inclusive). The aggregation strategy is defined on meter via <code>default_aggregation</code>.
|
||||
* @property int $end_time End timestamp for this event summary (exclusive). Must be aligned with minute boundaries.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property string $meter The meter associated with this event summary.
|
||||
* @property int $start_time Start timestamp for this event summary (inclusive). Must be aligned with minute boundaries.
|
||||
*/
|
||||
class MeterEventSummary extends \Stripe\ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'billing.meter_event_summary';
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
// File generated from our OpenAPI spec
|
||||
|
||||
namespace Stripe\BillingPortal;
|
||||
|
||||
/**
|
||||
* A portal configuration describes the functionality and behavior of a portal session.
|
||||
*
|
||||
* @property string $id Unique identifier for the object.
|
||||
* @property string $object String representing the object's type. Objects of the same type share the same value.
|
||||
* @property bool $active Whether the configuration is active and can be used to create portal sessions.
|
||||
* @property null|string|\Stripe\Application $application ID of the Connect Application that created the configuration.
|
||||
* @property \Stripe\StripeObject $business_profile
|
||||
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
|
||||
* @property null|string $default_return_url The default URL to redirect customers to when they click on the portal's link to return to your website. This can be <a href="https://stripe.com/docs/api/customer_portal/sessions/create#create_portal_session-return_url">overriden</a> when creating the session.
|
||||
* @property \Stripe\StripeObject $features
|
||||
* @property bool $is_default Whether the configuration is the default. If <code>true</code>, this configuration can be managed in the Dashboard and portal sessions will use this configuration unless it is overriden when creating the session.
|
||||
* @property bool $livemode Has the value <code>true</code> if the object exists in live mode or the value <code>false</code> if the object exists in test mode.
|
||||
* @property \Stripe\StripeObject $login_page
|
||||
* @property null|\Stripe\StripeObject $metadata Set of <a href="https://stripe.com/docs/api/metadata">key-value pairs</a> that you can attach to an object. This can be useful for storing additional information about the object in a structured format.
|
||||
* @property int $updated Time at which the object was last updated. Measured in seconds since the Unix epoch.
|
||||
*/
|
||||
class Configuration extends \Stripe\ApiResource
|
||||
{
|
||||
const OBJECT_NAME = 'billing_portal.configuration';
|
||||
|
||||
use \Stripe\ApiOperations\Update;
|
||||
|
||||
/**
|
||||
* Creates a configuration that describes the functionality and behavior of a
|
||||
* PortalSession.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $options
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\BillingPortal\Configuration the created resource
|
||||
*/
|
||||
public static function create($params = null, $options = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::classUrl();
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $options);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of configurations that describe the functionality of the customer
|
||||
* portal.
|
||||
*
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\Collection<\Stripe\BillingPortal\Configuration> of ApiResources
|
||||
*/
|
||||
public static function all($params = null, $opts = null)
|
||||
{
|
||||
$url = static::classUrl();
|
||||
|
||||
return static::_requestPage($url, \Stripe\Collection::class, $params, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a configuration that describes the functionality of the customer
|
||||
* portal.
|
||||
*
|
||||
* @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\BillingPortal\Configuration
|
||||
*/
|
||||
public static function retrieve($id, $opts = null)
|
||||
{
|
||||
$opts = \Stripe\Util\RequestOptions::parse($opts);
|
||||
$instance = new static($id, $opts);
|
||||
$instance->refresh();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a configuration that describes the functionality of the customer portal.
|
||||
*
|
||||
* @param string $id the ID of the resource to update
|
||||
* @param null|array $params
|
||||
* @param null|array|string $opts
|
||||
*
|
||||
* @throws \Stripe\Exception\ApiErrorException if the request fails
|
||||
*
|
||||
* @return \Stripe\BillingPortal\Configuration the updated resource
|
||||
*/
|
||||
public static function update($id, $params = null, $opts = null)
|
||||
{
|
||||
self::_validateParams($params);
|
||||
$url = static::resourceUrl($id);
|
||||
|
||||
list($response, $opts) = static::_staticRequest('post', $url, $params, $opts);
|
||||
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
|
||||
$obj->setLastResponse($response);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue