This commit is contained in:
Flatlogic Bot 2025-11-20 09:45:59 +00:00
parent eea139a530
commit 903cf599f0
32194 changed files with 3964328 additions and 141 deletions

94
admin/create.php Normal file
View File

@ -0,0 +1,94 @@
<?php
session_start();
require_once '../db/config.php';
// Check if user is logged in and is an admin
if (!isset($_SESSION["user_id"]) || $_SESSION["role"] !== 'admin') {
header("Location: ../login.php");
exit();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = $_POST['title'] ?? '';
$description = $_POST['description'] ?? '';
$servings = $_POST['servings'] ?? '';
$prep_time = $_POST['prep_time'] ?? '';
$image_path = '';
$ingredients = $_POST['ingredients'] ?? '';
$instructions = $_POST['instructions'] ?? '';
// Handle file upload
if (isset($_FILES['image']) && $_FILES['image']['error'] == 0) {
$target_dir = "../assets/images/";
$target_file = $target_dir . basename($_FILES["image"]["name"]);
if (move_uploaded_file($_FILES["image"]["tmp_name"], $target_file)) {
$image_path = "assets/images/" . basename($_FILES["image"]["name"]);
}
}
if (!empty($title) && !empty($description) && !empty($servings) && !empty($prep_time) && !empty($image_path)) {
try {
$stmt = db()->prepare("INSERT INTO recipes (title, description, servings, prep_time, image_path, ingredients, instructions) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$title, $description, $servings, $prep_time, $image_path, $ingredients, $instructions]);
$_SESSION['success_message'] = 'Recipe created successfully!';
header("Location: index.php");
exit();
} catch (PDOException $e) {
$error = "Database error: " . $e->getMessage();
}
} else {
$error = "Please fill all required fields and upload an image.";
}
}
include 'templates/header.php';
?>
<div class="admin-header">
<h1>Create New Recipe</h1>
<a href="index.php" class="btn btn-secondary"><i class="fas fa-arrow-left me-2"></i> Back to List</a>
</div>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<div class="card admin-form">
<div class="card-body">
<form action="create.php" method="POST" enctype="multipart/form-data">
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" id="title" name="title" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description" rows="3" required></textarea>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="servings" class="form-label">Servings</label>
<input type="text" class="form-control" id="servings" name="servings" required>
</div>
<div class="col-md-6 mb-3">
<label for="prep_time" class="form-label">Prep Time</label>
<input type="text" class="form-control" id="prep_time" name="prep_time" required>
</div>
</div>
<div class="mb-3">
<label for="image" class="form-label">Recipe Image</label>
<input type="file" class="form-control" id="image" name="image" required>
</div>
<div class="mb-3">
<label for="ingredients" class="form-label">Ingredients (one per line)</label>
<textarea class="form-control" id="ingredients" name="ingredients" rows="5"></textarea>
</div>
<div class="mb-3">
<label for="instructions" class="form-label">Instructions (one per line)</label>
<textarea class="form-control" id="instructions" name="instructions" rows="8"></textarea>
</div>
<button type="submit" class="btn btn-primary">Create Recipe</button>
</form>
</div>
</div>
<?php include 'templates/footer.php'; ?>

22
admin/delete.php Normal file
View File

@ -0,0 +1,22 @@
<?php
require_once '../db/config.php';
session_start();
if (!isset($_GET['id'])) {
header('Location: index.php');
exit;
}
$id = $_GET['id'];
try {
$pdo = db();
$stmt = $pdo->prepare('DELETE FROM recipes WHERE id = ?');
$stmt->execute([$id]);
$_SESSION['flash_message'] = ['type' => 'success', 'message' => 'Recipe deleted successfully!'];
} catch (PDOException $e) {
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Database error: ' . $e->getMessage()];
}
header('Location: index.php');
exit;

118
admin/edit.php Normal file
View File

@ -0,0 +1,118 @@
<?php
session_start();
require_once '../db/config.php';
// Check if user is logged in and is an admin
if (!isset($_SESSION["user_id"]) || $_SESSION["role"] !== 'admin') {
header("Location: ../login.php");
exit();
}
$id = $_GET['id'] ?? null;
if (!$id) {
header("Location: index.php");
exit();
}
// Fetch recipe details
try {
$stmt = db()->prepare("SELECT * FROM recipes WHERE id = ?");
$stmt->execute([$id]);
$recipe = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$recipe) {
$_SESSION['success_message'] = 'Recipe not found.';
header("Location: index.php");
exit();
}
} catch (PDOException $e) {
$error = "Database error: " . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = $_POST['title'] ?? '';
$description = $_POST['description'] ?? '';
$servings = $_POST['servings'] ?? '';
$prep_time = $_POST['prep_time'] ?? '';
$ingredients = $_POST['ingredients'] ?? '';
$instructions = $_POST['instructions'] ?? '';
$image_path = $recipe['image_path']; // Keep old image by default
// Handle file upload if a new image is provided
if (isset($_FILES['image']) && $_FILES['image']['error'] == 0) {
$target_dir = "../assets/images/";
$target_file = $target_dir . basename($_FILES["image"]["name"]);
if (move_uploaded_file($_FILES["image"]["tmp_name"], $target_file)) {
$image_path = "assets/images/" . basename($_FILES["image"]["name"]);
}
}
if (!empty($title) && !empty($description) && !empty($servings) && !empty($prep_time)) {
try {
$stmt = db()->prepare("UPDATE recipes SET title = ?, description = ?, servings = ?, prep_time = ?, image_path = ?, ingredients = ?, instructions = ? WHERE id = ?");
$stmt->execute([$title, $description, $servings, $prep_time, $image_path, $ingredients, $instructions, $id]);
$_SESSION['success_message'] = 'Recipe updated successfully!';
header("Location: index.php");
exit();
} catch (PDOException $e) {
$error = "Database error: " . $e->getMessage();
}
} else {
$error = "Please fill all required fields.";
}
}
include 'templates/header.php';
?>
<div class="admin-header">
<h1>Edit Recipe</h1>
<a href="index.php" class="btn btn-secondary"><i class="fas fa-arrow-left me-2"></i> Back to List</a>
</div>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<div class="card admin-form">
<div class="card-body">
<form action="edit.php?id=<?php echo $id; ?>" method="POST" enctype="multipart/form-data">
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" id="title" name="title" value="<?php echo htmlspecialchars($recipe['title']); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description" rows="3" required><?php echo htmlspecialchars($recipe['description']); ?></textarea>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="servings" class="form-label">Servings</label>
<input type="text" class="form-control" id="servings" name="servings" value="<?php echo htmlspecialchars($recipe['servings']); ?>" required>
</div>
<div class="col-md-6 mb-3">
<label for="prep_time" class="form-label">Prep Time</label>
<input type="text" class="form-control" id="prep_time" name="prep_time" value="<?php echo htmlspecialchars($recipe['prep_time']); ?>" required>
</div>
</div>
<div class="mb-3">
<label for="image" class="form-label">Current Image</label>
<div>
<img src="../<?php echo htmlspecialchars($recipe['image_path']); ?>" alt="Current Image" style="max-width: 200px; border-radius: 8px; margin-bottom: 10px;">
</div>
<label for="image" class="form-label">Upload New Image (optional)</label>
<input type="file" class="form-control" id="image" name="image">
</div>
<div class="mb-3">
<label for="ingredients" class="form-label">Ingredients (one per line)</label>
<textarea class="form-control" id="ingredients" name="ingredients" rows="5"><?php echo htmlspecialchars($recipe['ingredients']); ?></textarea>
</div>
<div class="mb-3">
<label for="instructions" class="form-label">Instructions (one per line)</label>
<textarea class="form-control" id="instructions" name="instructions" rows="8"><?php echo htmlspecialchars($recipe['instructions']); ?></textarea>
</div>
<button type="submit" class="btn btn-primary">Update Recipe</button>
</form>
</div>
</div>
<?php include 'templates/footer.php'; ?>

79
admin/index.php Normal file
View File

@ -0,0 +1,79 @@
<?php
session_start();
require_once '../db/config.php';
// Check if user is logged in and is an admin
if (!isset($_SESSION["user_id"]) || $_SESSION["role"] !== 'admin') {
header("Location: ../login.php");
exit();
}
// Fetch all recipes from the database
try {
$stmt = db()->query("SELECT * FROM recipes ORDER BY created_at DESC");
$recipes = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
// Handle database errors
$error = "Error fetching recipes: " . $e->getMessage();
$recipes = [];
}
include 'templates/header.php';
?>
<div class="admin-header">
<h1>Manage Recipes</h1>
<a href="create.php" class="btn btn-primary"><i class="fas fa-plus me-2"></i> Create New Recipe</a>
</div>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<?php if (isset($_SESSION['success_message'])): ?>
<div class="alert alert-success"><?php echo htmlspecialchars($_SESSION['success_message']); unset($_SESSION['success_message']); ?></div>
<?php endif; ?>
<div class="card admin-table">
<div class="card-body">
<table class="table table-hover">
<thead>
<tr>
<th>Image</th>
<th>Title</th>
<th>Servings</th>
<th>Prep Time</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($recipes)): ?>
<tr>
<td colspan="5" class="text-center">No recipes found.</td>
</tr>
<?php else: ?>
<?php foreach ($recipes as $recipe): ?>
<tr>
<td>
<img src="../<?php echo htmlspecialchars($recipe['image_path']); ?>" alt="<?php echo htmlspecialchars($recipe['title']); ?>" style="width: 100px; height: 60px; object-fit: cover; border-radius: 8px;">
</td>
<td><?php echo htmlspecialchars($recipe['title']); ?></td>
<td><?php echo htmlspecialchars($recipe['servings']); ?></td>
<td><?php echo htmlspecialchars($recipe['prep_time']); ?></td>
<td class="text-end">
<a href="edit.php?id=<?php echo $recipe['id']; ?>" class="btn btn-sm btn-outline-primary">
<i class="fas fa-edit"></i> Edit
</a>
<a href="delete.php?id=<?php echo $recipe['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this recipe?');">
<i class="fas fa-trash"></i> Delete
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php include 'templates/footer.php'; ?>

6
admin/logout.php Normal file
View File

@ -0,0 +1,6 @@
<?php
session_start();
session_unset();
session_destroy();
header('Location: ../login.php');
exit;

60
admin/products/create.php Normal file
View File

@ -0,0 +1,60 @@
<?php
session_start();
require_once __DIR__ . '/../../db/config.php';
if (!isset($_SESSION["user_id"]) || $_SESSION["role"] !== 'admin') {
header("Location: ../../login.php");
exit();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$description = $_POST['description'] ?? '';
$price = $_POST['price'] ?? '';
$image_url = $_POST['image_url'] ?? '';
if (!empty($name) && !empty($price)) {
try {
$stmt = db()->prepare("INSERT INTO products (name, description, price, image_url) VALUES (?, ?, ?, ?)");
$stmt->execute([$name, $description, $price, $image_url]);
header("Location: index.php");
exit();
} catch (PDOException $e) {
$error = "Database error: " . $e->getMessage();
}
} else {
$error = "Please fill all required fields.";
}
}
include __DIR__ . '/../templates/header.php';
?>
<div class="container">
<h1 class="mt-4">Add New Product</h1>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form action="create.php" method="POST">
<div class="mb-3">
<label for="name" class="form-label">Product Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="price" class="form-label">Price</label>
<input type="number" step="0.01" class="form-control" id="price" name="price" required>
</div>
<div class="mb-3">
<label for="image_url" class="form-label">Image URL</label>
<input type="text" class="form-control" id="image_url" name="image_url">
</div>
<button type="submit" class="btn btn-primary">Create Product</button>
<a href="index.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
<?php include __DIR__ . '/../templates/footer.php'; ?>

21
admin/products/delete.php Normal file
View File

@ -0,0 +1,21 @@
<?php
session_start();
require_once __DIR__ . '/../../db/config.php';
if (!isset($_SESSION["user_id"]) || $_SESSION["role"] !== 'admin') {
header("Location: ../../login.php");
exit();
}
$id = $_GET['id'] ?? null;
if ($id) {
try {
$stmt = db()->prepare("DELETE FROM products WHERE id = ?");
$stmt->execute([$id]);
} catch (PDOException $e) {
// Optional: handle error, e.g., log it or show a message
}
}
header("Location: index.php");
exit();

75
admin/products/edit.php Normal file
View File

@ -0,0 +1,75 @@
<?php
session_start();
require_once __DIR__ . '/../../db/config.php';
if (!isset($_SESSION["user_id"]) || $_SESSION["role"] !== 'admin') {
header("Location: ../../login.php");
exit();
}
$id = $_GET['id'] ?? null;
if (!$id) {
header("Location: index.php");
exit();
}
$stmt = db()->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$id]);
$product = $stmt->fetch();
if (!$product) {
header("Location: index.php");
exit();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$description = $_POST['description'] ?? '';
$price = $_POST['price'] ?? '';
$image_url = $_POST['image_url'] ?? '';
if (!empty($name) && !empty($price)) {
try {
$stmt = db()->prepare("UPDATE products SET name = ?, description = ?, price = ?, image_url = ? WHERE id = ?");
$stmt->execute([$name, $description, $price, $image_url, $id]);
header("Location: index.php");
exit();
} catch (PDOException $e) {
$error = "Database error: " . $e->getMessage();
}
} else {
$error = "Please fill all required fields.";
}
}
include __DIR__ . '/../templates/header.php';
?>
<div class="container">
<h1 class="mt-4">Edit Product</h1>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form action="edit.php?id=<?= $id ?>" method="POST">
<div class="mb-3">
<label for="name" class="form-label">Product Name</label>
<input type="text" class="form-control" id="name" name="name" value="<?= htmlspecialchars($product['name']) ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description" rows="3"><?= htmlspecialchars($product['description']) ?></textarea>
</div>
<div class="mb-3">
<label for="price" class="form-label">Price</label>
<input type="number" step="0.01" class="form-control" id="price" name="price" value="<?= htmlspecialchars($product['price']) ?>" required>
</div>
<div class="mb-3">
<label for="image_url" class="form-label">Image URL</label>
<input type="text" class="form-control" id="image_url" name="image_url" value="<?= htmlspecialchars($product['image_url']) ?>">
</div>
<button type="submit" class="btn btn-primary">Update Product</button>
<a href="index.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
<?php include __DIR__ . '/../templates/footer.php'; ?>

37
admin/products/index.php Normal file
View File

@ -0,0 +1,37 @@
<?php
require_once __DIR__ . '/../../db/config.php';
require_once __DIR__ . '/../templates/header.php';
$stmt = db()->query('SELECT * FROM products ORDER BY created_at DESC');
$products = $stmt->fetchAll();
?>
<div class="container">
<h1 class="mt-4">Manage Products</h1>
<a href="create.php" class="btn btn-success mb-3">Add New Product</a>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($products as $product): ?>
<tr>
<td><?= htmlspecialchars($product['id']) ?></td>
<td><?= htmlspecialchars($product['name']) ?></td>
<td>$<?= htmlspecialchars(number_format($product['price'], 2)) ?></td>
<td>
<a href="edit.php?id=<?= $product['id'] ?>" class="btn btn-sm btn-primary">Edit</a>
<a href="delete.php?id=<?= $product['id'] ?>" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure?')">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php require_once __DIR__ . '/../templates/footer.php'; ?>

View File

@ -0,0 +1,5 @@
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body class="admin-page">
<div class="admin-wrapper">
<aside class="admin-sidebar">
<div class="admin-logo">
<a href="../index.php" class="text-decoration-none">RecipeApp</a>
</div>
<nav class="admin-nav nav flex-column">
<a class="nav-link active" href="index.php">
<i class="fas fa-tachometer-alt me-2"></i> Dashboard
</a>
<a class="nav-link" href="products/index.php">
<i class="fas fa-box me-2"></i> Products
</a>
<a class="nav-link" href="../index.php" target="_blank">
<i class="fas fa-home me-2"></i> View Site
</a>
<a class="nav-link" href="logout.php">
<i class="fas fa-sign-out-alt me-2"></i> Logout
</a>
</nav>
</aside>
<main class="admin-main-content">

365
assets/css/custom.css Normal file
View File

@ -0,0 +1,365 @@
/* assets/css/custom.css */
/* General Body Styles */
body {
font-family: 'Inter', sans-serif;
background-color: #f8f9fa;
color: #212529;
display: flex;
flex-direction: column;
min-height: 100vh;
margin: 0;
}
/* Main Content Area */
main {
flex: 1;
padding-top: 80px; /* Adjust for fixed navbar */
}
/* Navbar Styles */
.navbar {
background-color: #ffffff;
border-bottom: 1px solid #e9ecef;
font-weight: 500;
transition: box-shadow 0.3s ease;
}
.navbar-brand {
font-weight: 700;
color: #007bff !important;
}
.navbar:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.nav-link {
color: #495057 !important;
transition: color 0.2s ease;
}
.nav-link:hover, .nav-link.active {
color: #007bff !important;
}
.btn-primary {
background-color: #007bff;
border-color: #007bff;
font-weight: 500;
transition: background-color 0.2s ease, border-color 0.2s ease;
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #0056b3;
}
/* Hero Section */
.hero-section {
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
color: white;
padding: 100px 0;
text-align: center;
}
.hero-section h1 {
font-size: 3.5rem;
font-weight: 700;
}
.hero-section p {
font-size: 1.25rem;
max-width: 600px;
margin: 0 auto;
}
/* Recipe Cards */
.recipe-card {
border: none;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
height: 100%;
}
.recipe-card:hover {
transform: translateY(-10px);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15);
}
.recipe-card-img-top {
height: 200px;
object-fit: cover;
}
.recipe-card .card-body {
display: flex;
flex-direction: column;
}
.recipe-card .card-title {
font-weight: 600;
}
.recipe-card .card-text {
flex-grow: 1;
}
/* Footer */
.footer {
background-color: #343a40;
color: white;
padding: 40px 0;
margin-top: auto;
}
.footer a {
color: #f8f9fa;
text-decoration: none;
}
.footer a:hover {
color: #007bff;
}
/* Form & Auth Pages */
.auth-container {
max-width: 450px;
margin: 40px auto;
padding: 40px;
background-color: #ffffff;
border-radius: 15px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
.auth-container h2 {
font-weight: 700;
margin-bottom: 2rem;
text-align: center;
}
.form-control {
border-radius: 8px;
padding: 12px 15px;
}
.form-control:focus {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.btn-social {
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
/* Recipe Detail Page */
.recipe-header {
background-size: cover;
background-position: center;
color: white;
padding: 120px 0;
text-align: center;
position: relative;
}
.recipe-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.recipe-header-content {
position: relative;
z-index: 1;
}
.recipe-header h1 {
font-size: 3.5rem;
font-weight: 700;
}
.recipe-meta span {
display: inline-block;
background-color: rgba(255, 255, 255, 0.2);
padding: 5px 15px;
border-radius: 20px;
margin: 0 5px;
}
/* Pricing Page */
.pricing-header {
text-align: center;
padding: 60px 0;
}
.pricing-header h1 {
font-weight: 700;
}
.pricing-card {
border: 1px solid #e9ecef;
border-radius: 15px;
padding: 40px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
background-color: #ffffff;
height: 100%;
display: flex;
flex-direction: column;
}
.pricing-card:hover, .pricing-card.featured {
transform: translateY(-10px);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.1);
border-color: #007bff;
}
.pricing-card.featured {
position: relative;
overflow: hidden;
}
.featured-badge {
position: absolute;
top: -1px;
right: -1px;
background-color: #007bff;
color: white;
padding: 8px 16px;
font-size: 0.9rem;
font-weight: 500;
border-bottom-left-radius: 15px;
}
.pricing-card h3 {
font-weight: 600;
color: #007bff;
}
.price {
font-size: 3rem;
font-weight: 700;
}
.price .period {
font-size: 1rem;
font-weight: 400;
color: #6c757d;
}
.feature-list {
list-style: none;
padding: 0;
margin: 20px 0;
flex-grow: 1;
}
.feature-list li {
margin-bottom: 10px;
display: flex;
align-items: center;
}
.feature-list li::before {
content: '✔';
color: #28a745;
margin-right: 10px;
font-weight: bold;
}
/* Admin Panel Styles */
.admin-wrapper {
display: flex;
min-height: 100vh;
}
.admin-sidebar {
width: 250px;
background-color: #343a40;
color: white;
padding: 20px;
flex-shrink: 0;
}
.admin-sidebar .admin-logo {
font-weight: 700;
font-size: 1.5rem;
text-align: center;
margin-bottom: 30px;
color: #007bff;
}
.admin-nav .nav-link {
color: #adb5bd !important;
padding: 10px 15px;
border-radius: 8px;
margin-bottom: 5px;
transition: background-color 0.2s ease, color 0.2s ease;
}
.admin-nav .nav-link:hover {
background-color: #495057;
color: #ffffff !important;
}
.admin-nav .nav-link.active {
background-color: #007bff;
color: #ffffff !important;
}
.admin-main-content {
flex-grow: 1;
padding: 40px;
background-color: #f8f9fa;
}
.admin-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
.admin-header h1 {
font-weight: 700;
}
.admin-table {
width: 100%;
background-color: #ffffff;
border-radius: 15px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.05);
}
.admin-table th, .admin-table td {
vertical-align: middle;
}
.admin-table .btn-sm {
margin-right: 5px;
}
.admin-form {
background-color: #ffffff;
padding: 40px;
border-radius: 15px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.05);
}
.admin-form .form-label {
font-weight: 600;
}
/* Make body background white for admin pages */
body.admin-page {
background-color: #ffffff;
}

1
assets/js/main.js Normal file
View File

@ -0,0 +1 @@
// rfresh - Custom JS. Can be used for future interactivity.

19
cancel.php Normal file
View File

@ -0,0 +1,19 @@
<?php
session_start();
include 'templates/header.php';
?>
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8 text-center">
<div class="auth-container">
<h2 class="text-warning">Checkout Canceled</h2>
<p class="lead">Your subscription process has been canceled.</p>
<p>You have not been charged. You can always choose a plan later.</p>
<a href="pricing.php" class="btn btn-primary mt-3">View Plans</a>
</div>
</div>
</div>
</div>
<?php include 'templates/footer.php'; ?>

65
cancel_subscription.php Normal file
View File

@ -0,0 +1,65 @@
<?php
require_once 'db/config.php';
require_once 'vendor/autoload.php';
$stripe_config = require_once 'config/stripe.php';
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$message = '';
$error = false;
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT * FROM subscriptions WHERE user_id = ? AND status = \'active\'');
$stmt->execute([$_SESSION['user_id']]);
$subscription = $stmt->fetch();
if ($subscription) {
\Stripe\Stripe::setApiKey($stripe_config['stripe']['secret_key']);
// Cancel the subscription in Stripe
$stripe_subscription = \Stripe\Subscription::update($subscription['stripe_subscription_id'], ['cancel_at_period_end' => true]);
// Update the status in the local database
$stmt = $pdo->prepare("UPDATE subscriptions SET status = 'canceled' WHERE id = ?");
$stmt->execute([$subscription['id']]);
$message = "Your subscription has been successfully canceled. You will have access until the end of the current billing period.";
} else {
$message = "No active subscription found to cancel.";
$error = true;
}
} catch (\Stripe\Exception\ApiErrorException $e) {
$message = "Error canceling subscription: " . $e->getMessage();
$error = true;
} catch (PDOException $e) {
$message = "Database error: " . $e->getMessage();
$error = true;
}
include 'templates/header.php';
?>
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8 text-center">
<div class="auth-container">
<?php if ($error): ?>
<h2 class="text-danger">Cancellation Failed</h2>
<?php else: ?>
<h2 class="text-success">Subscription Canceled</h2>
<?php endif; ?>
<p class="lead"><?php echo htmlspecialchars($message); ?></p>
<a href="profile.php" class="btn btn-primary mt-3">Back to Profile</a>
</div>
</div>
</div>
</div>
<?php include 'templates/footer.php'; ?>

1788
composer-setup.php Normal file

File diff suppressed because it is too large Load Diff

7
composer.json Normal file
View File

@ -0,0 +1,7 @@
{
"require": {
"google/apiclient": "^2.0",
"facebook/graph-sdk": "^4.0",
"stripe/stripe-php": "^1.8"
}
}

1388
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

7
config/sms.php Normal file
View File

@ -0,0 +1,7 @@
<?php
return [
'smslink' => [
'api_key' => 'YOUR_SMSLINK_API_KEY',
'sender' => 'My App',
],
];

13
config/social_logins.php Normal file
View File

@ -0,0 +1,13 @@
<?php
return [
'google' => [
'client_id' => 'YOUR_GOOGLE_CLIENT_ID',
'client_secret' => 'YOUR_GOOGLE_CLIENT_SECRET',
'redirect_uri' => 'http://localhost/google_callback.php',
],
'facebook' => [
'client_id' => 'YOUR_FACEBOOK_APP_ID',
'client_secret' => 'YOUR_FACEBOOK_APP_SECRET',
'redirect_uri' => 'http://localhost/facebook_callback.php',
],
];

7
config/stripe.php Normal file
View File

@ -0,0 +1,7 @@
<?php
return [
'stripe' => [
'publishable_key' => 'YOUR_STRIPE_PUBLISHABLE_KEY',
'secret_key' => 'YOUR_STRIPE_SECRET_KEY',
],
];

View File

@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS `recipes` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`title` VARCHAR(255) NOT NULL,
`description` TEXT,
`image_url` VARCHAR(255),
`prep_time_minutes` INT,
`difficulty` VARCHAR(50),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(50) NOT NULL UNIQUE,
`password` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1 @@
ALTER TABLE `users` ADD `role` VARCHAR(50) NOT NULL DEFAULT 'customer';

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS `user_social_logins` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`provider` VARCHAR(50) NOT NULL,
`provider_id` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

View File

@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN phone_number VARCHAR(255) DEFAULT NULL, ADD COLUMN phone_verified_at TIMESTAMP NULL DEFAULT NULL, ADD COLUMN two_factor_secret VARCHAR(255) DEFAULT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE recipes ADD COLUMN ingredients TEXT, ADD COLUMN instructions TEXT;

View File

@ -0,0 +1,19 @@
CREATE TABLE IF NOT EXISTS subscription_plans (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
meals_per_week INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS subscriptions (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
plan_id INT NOT NULL,
stripe_subscription_id VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL, -- e.g., active, canceled, paused
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (plan_id) REFERENCES subscription_plans(id)
);

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS `products` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`price` DECIMAL(10, 2) NOT NULL,
`image_url` VARCHAR(255),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS `user_subscription_products` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`product_id` INT NOT NULL,
`quantity` INT NOT NULL DEFAULT 1,
`stripe_subscription_id` VARCHAR(255) NOT NULL,
`added_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

126
facebook_callback.php Normal file
View File

@ -0,0 +1,126 @@
<?php
session_start();
require_once 'vendor/autoload.php';
require_once 'db/config.php';
$social_logins = require_once 'config/social_logins.php';
$fb = new Facebook\Facebook([
'app_id' => $social_logins['facebook']['client_id'],
'app_secret' => $social_logins['facebook']['client_secret'],
'default_graph_version' => 'v2.10',
]);
$helper = $fb->getRedirectLoginHelper();
try {
$accessToken = $helper->getAccessToken();
} catch(Facebook\Exceptions\FacebookResponseException $e) {
// When Graph returns an error
echo 'Graph returned an error: ' . $e->getMessage();
exit;
} catch(Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
echo 'Facebook SDK returned an error: ' . $e->getMessage();
exit;
}
if (! isset($accessToken)) {
if ($helper->getError()) {
header('HTTP/1.0 401 Unauthorized');
echo "Error: " . $helper->getError() . "\n";
echo "Error Code: " . $helper->getErrorCode() . "\n";
echo "Error Reason: " . $helper->getErrorReason() . "\n";
echo "Error Description: " . $helper->getErrorDescription() . "\n";
} else {
header('HTTP/1.0 400 Bad Request');
echo 'Bad request';
}
exit;
}
// The OAuth 2.0 client handler helps us manage access tokens
$oAuth2Client = $fb->getOAuth2Client();
// Get the access token metadata from /debug_token
$tokenMetadata = $oAuth2Client->debugToken($accessToken);
// Validation (these will throw FacebookSDKException's when they fail)
$tokenMetadata->validateAppId($social_logins['facebook']['client_id']);
$tokenMetadata->validateExpiration();
if (! $accessToken->isLongLived()) {
// Exchanges a short-lived access token for a long-lived one
try {
$accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
} catch (Facebook\Exceptions\FacebookSDKException $e) {
echo "<p>Error getting long-lived access token: " . $e->getMessage() . "</p>\n\n";
exit;
}
}
$_SESSION['fb_access_token'] = (string) $accessToken;
try {
// Returns a `Facebook\FacebookResponse` object
$response = $fb->get('/me?fields=id,name,email', $accessToken);
} catch(Facebook\Exceptions\FacebookResponseException $e) {
echo 'Graph returned an error: ' . $e->getMessage();
exit;
} catch(Facebook\Exceptions\FacebookSDKException $e) {
echo 'Facebook SDK returned an error: ' . $e->getMessage();
exit;
}
$user = $response->getGraphUser();
$pdoconn = db();
// Check if user exists in user_social_logins
$stmt = $pdoconn->prepare("SELECT * FROM user_social_logins WHERE provider = 'facebook' AND provider_id = ?");
$stmt->execute([$user->getId()]);
$social_login = $stmt->fetch();
if ($social_login) {
// User exists, log them in
$stmt = $pdoconn->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$social_login['user_id']]);
$app_user = $stmt->fetch();
$_SESSION['user_id'] = $app_user['id'];
$_SESSION['username'] = $app_user['username'];
$_SESSION['role'] = $app_user['role'];
header('Location: profile.php');
exit;
} else {
// New user, create account
$email = $user->getEmail();
$username = $user->getName();
// Check if email is already in use
$stmt = $pdoconn->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
$existing_user = $stmt->fetch();
if ($existing_user) {
// Email in use, link account
$user_id = $existing_user['id'];
} else {
// Create new user
$stmt = $pdoconn->prepare("INSERT INTO users (username, email, role) VALUES (?, ?, ?)");
$stmt->execute([$username, $email, 'customer']);
$user_id = $pdoconn->lastInsertId();
}
// Create social login entry
$stmt = $pdoconn->prepare("INSERT INTO user_social_logins (user_id, provider, provider_id) VALUES (?, 'facebook', ?)");
$stmt->execute([$user_id, $user->getId()]);
// Log in the new user
$_SESSION['user_id'] = $user_id;
$_SESSION['username'] = $username;
$_SESSION['role'] = 'customer';
header('Location: profile.php');
exit;
}

17
facebook_login.php Normal file
View File

@ -0,0 +1,17 @@
<?php
session_start();
require_once 'vendor/autoload.php';
$social_logins = require_once 'config/social_logins.php';
$fb = new Facebook\Facebook([
'app_id' => $social_logins['facebook']['client_id'],
'app_secret' => $social_logins['facebook']['client_secret'],
'default_graph_version' => 'v2.10',
]);
$helper = $fb->getRedirectLoginHelper();
$permissions = ['email']; // Optional permissions
$loginUrl = $helper->getLoginUrl($social_logins['facebook']['redirect_uri'], $permissions);
header('Location: ' . $loginUrl);
exit;

76
google_callback.php Normal file
View File

@ -0,0 +1,76 @@
<?php
require_once 'vendor/autoload.php';
require_once 'db/config.php';
session_start();
$social_logins = require 'config/social_logins.php';
$google_config = $social_logins['google'];
$client = new Google_Client();
$client->setClientId($google_config['client_id']);
$client->setClientSecret($google_config['client_secret']);
$client->setRedirectUri($google_config['redirect_uri']);
if (isset($_GET['code'])) {
$token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
$client->setAccessToken($token['access_token']);
// get profile info
$google_oauth = new Google_Service_Oauth2($client);
$google_account_info = $google_oauth->userinfo->get();
$email = $google_account_info->email;
$name = $google_account_info->name;
$google_id = $google_account_info->id;
try {
$pdo = db();
// Check if user exists with this google id
$stmt = $pdo->prepare('SELECT u.* FROM users u JOIN user_social_logins usl ON u.id = usl.user_id WHERE usl.provider = ? AND usl.provider_id = ?');
$stmt->execute(['google', $google_id]);
$user = $stmt->fetch();
if ($user) {
// User exists, log them in
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
} else {
// User does not exist, create a new user
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = ?');
$stmt->execute([$email]);
$user_exists = $stmt->fetch();
if ($user_exists) {
// A user with this email already exists, but not linked to this google id.
// For simplicity, we will just link them.
$user_id = $user_exists['id'];
} else {
// Create a new user
$stmt = $pdo->prepare("INSERT INTO users (username, role) VALUES (?, ?)");
$stmt->execute([$email, 'customer']);
$user_id = $pdo->lastInsertId();
}
// Link the social login
$stmt = $pdo->prepare("INSERT INTO user_social_logins (user_id, provider, provider_id) VALUES (?, ?, ?)");
$stmt->execute([$user_id, 'google', $google_id]);
// Log the new user in
$_SESSION['user_id'] = $user_id;
$_SESSION['username'] = $email;
$_SESSION['role'] = 'customer';
}
header('Location: profile.php');
exit;
} catch (PDOException $e) {
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Database error: ' . $e->getMessage()];
header('Location: login.php');
exit;
}
} else {
header('Location: login.php');
exit;
}

15
google_login.php Normal file
View File

@ -0,0 +1,15 @@
<?php
require_once 'vendor/autoload.php';
$social_logins = require 'config/social_logins.php';
$google_config = $social_logins['google'];
$client = new Google_Client();
$client->setClientId($google_config['client_id']);
$client->setClientSecret($google_config['client_secret']);
$client->setRedirectUri($google_config['redirect_uri']);
$client->addScope("email");
$client->addScope("profile");
$auth_url = $client->createAuthUrl();
header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));

265
index.php
View File

@ -1,150 +1,133 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
require_once __DIR__ . '/db/config.php';
$recipes = [];
$error_message = null;
try {
$pdo = db();
$stmt = $pdo->query('SELECT * FROM recipes ORDER BY created_at DESC');
$recipes = $stmt->fetchAll();
} catch (PDOException $e) {
error_log('Database error: ' . $e->getMessage());
$error_message = 'We are currently experiencing technical difficulties. Please try again later.';
}
$project_image_url = $_SERVER['PROJECT_IMAGE_URL'] ?? 'https://via.placeholder.com/1200x630.png?text=rfresh';
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?>
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>rfresh - Fresh Recipes Delivered</title>
<meta name="description" content="Discover delicious, chef-designed recipes with fresh ingredients delivered to your door. Cooking made easy and fun.">
<meta name="keywords" content="meal kit, recipe box, food delivery, cooking, fresh ingredients, subscription service, dinner, healthy eating, convenient meals, weekly recipes, food subscription, Built with Flatlogic Generator">
<meta property="og:type" content="website">
<meta property="og:title" content="rfresh - Fresh Recipes Delivered">
<meta property="og:description" content="Discover delicious, chef-designed recipes with fresh ingredients delivered to your door. Cooking made easy and fun.">
<meta property="og:image" content="<?php echo htmlspecialchars($project_image_url); ?>">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="rfresh - Fresh Recipes Delivered">
<meta name="twitter:description" content="Discover delicious, chef-designed recipes with fresh ingredients delivered to your door. Cooking made easy and fun.">
<meta name="twitter:image" content="<?php echo htmlspecialchars($project_image_url); ?>">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Georgia&family=Helvetica+Neue&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm sticky-top">
<div class="container">
<a class="navbar-brand" href="/">rfresh</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto align-items-center">
<li class="nav-item">
<a class="nav-link" href="#recipes">Our Menus</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pricing.php">Pricing</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/login.php">Log In</a>
</li>
<li class="nav-item ms-lg-3">
<a class="btn btn-primary" href="/register.php">Sign Up</a>
</li>
</ul>
</div>
</div>
</nav>
<main>
<section class="hero-section text-center">
<div class="container">
<h1 class="display-4">Eat well, live fresh.</h1>
<p class="lead">Chef-crafted recipes and fresh, pre-portioned ingredients delivered to your door. Say goodbye to meal planning and hello to delicious, home-cooked dinners.</p>
<a href="#recipes" class="btn btn-primary btn-lg">Explore This Week's Menu</a>
</div>
</section>
<div class="album py-5" id="recipes">
<div class="container">
<div class="page-title">
<h2>This Week's Recipes</h2>
<p class="lead text-muted">Choose from a variety of delicious meals to fit your lifestyle.</p>
</div>
<?php if ($error_message): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($error_message); ?>
</div>
<?php elseif (empty($recipes)): ?>
<div class="text-center">
<p>No recipes available at the moment. Please check back later!</p>
</div>
<?php else: ?>
<div class="row row-cols-1 row-cols-sm-2 row-cols-lg-3 g-4">
<?php foreach ($recipes as $recipe): ?>
<div class="col">
<a href="recipe.php?id=<?php echo $recipe['id']; ?>" class="text-decoration-none">
<div class="card recipe-card h-100">
<img src="<?php echo htmlspecialchars($recipe['image_url']); ?>" class="card-img-top" alt="<?php echo htmlspecialchars($recipe['title']); ?>">
<div class="card-body d-flex flex-column">
<h3 class="card-title"><?php echo htmlspecialchars($recipe['title']); ?></h3>
<p class="card-text flex-grow-1"><?php echo htmlspecialchars($recipe['description']); ?></p>
<div class="d-flex justify-content-between align-items-center mt-3">
<span class="badge bg-primary"><?php echo htmlspecialchars($recipe['difficulty']); ?></span>
<small class="text-muted"><?php echo htmlspecialchars($recipe['prep_time_minutes']); ?> min</small>
</div>
</div>
</div>
</a>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</main>
<footer class="footer">
<div class="container text-center">
<p class="mb-1">&copy; <?php echo date('Y'); ?> rfresh. All rights reserved.</p>
<p class="mb-0">Built with <a href="https://flatlogic.com" target="_blank" rel="noopener noreferrer">Flatlogic</a>.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

108
login.php Normal file
View File

@ -0,0 +1,108 @@
<?php
session_start();
require_once 'db/config.php';
$error_message = '';
if (isset($_SESSION['user_id'])) {
header('Location: ' . ($_SESSION['role'] === 'admin' ? 'admin/index.php' : 'profile.php'));
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
if (empty($username) || empty($password)) {
$error_message = 'Please enter both username and password.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username');
$stmt->execute([':username' => $username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
if ($user['role'] === 'customer' && $user['phone_verified_at']) {
$verification_code = str_pad(rand(0, 999999), 6, '0', STR_PAD_LEFT);
$stmt = $pdo->prepare('UPDATE users SET two_factor_secret = ? WHERE id = ?');
$stmt->execute([$verification_code, $user['id']]);
require_once 'mail/SmsService.php';
SmsService::sendSms($user['phone_number'], "Your login code is: {$verification_code}");
$_SESSION['user_id_2fa'] = $user['id'];
header('Location: login_2fa.php');
exit;
} else {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
header('Location: ' . ($user['role'] === 'admin' ? 'admin/index.php' : 'profile.php'));
exit;
}
} else {
$error_message = 'Invalid username or password.';
}
} catch (PDOException $e) {
error_log('Login Error: ' . $e->getMessage());
$error_message = 'An error occurred. Please try again later.';
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - rfresh</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Georgia&family=Helvetica+Neue&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<div class="auth-section">
<div class="card auth-card">
<div class="card-body">
<div class="text-center mb-4">
<a class="navbar-brand" href="/">rfresh</a>
</div>
<h1 class="card-title text-center mb-4">Welcome Back</h1>
<?php if ($error_message): ?>
<div class="alert alert-danger" role="alert"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<form action="login.php" method="POST">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</form>
<div class="text-center my-3">
<small class="text-muted">OR</small>
</div>
<div class="d-grid gap-2">
<a href="google_login.php" class="btn btn-social btn-google">Login with Google</a>
<a href="facebook_login.php" class="btn btn-social btn-facebook">Login with Facebook</a>
</div>
<div class="text-center mt-4">
<small>Don't have an account? <a href="register.php">Sign Up</a></small>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

99
login_2fa.php Normal file
View File

@ -0,0 +1,99 @@
<?php
session_start();
require_once 'db/config.php';
// If user doesn't need 2FA, redirect away
if (!isset($_SESSION['user_id_2fa'])) {
header('Location: login.php');
exit;
}
$error_message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$verification_code = $_POST['verification_code'] ?? '';
if (empty($verification_code)) {
$error_message = 'Please enter the verification code.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$_SESSION['user_id_2fa']]);
$user = $stmt->fetch();
if ($user && $user['two_factor_secret'] === $verification_code) {
// Correct code, log the user in
$stmt = $pdo->prepare('UPDATE users SET two_factor_secret = NULL WHERE id = ?');
$stmt->execute([$_SESSION['user_id_2fa']]);
unset($_SESSION['user_id_2fa']);
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
header('Location: profile.php');
exit;
} else {
$error_message = 'Invalid verification code. Please try again.';
}
} catch (PDOException $e) {
$error_message = 'Database error. Please try again later.';
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Two-Factor Authentication - AI Recipe Generator</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top">
<div class="container">
<a class="navbar-brand" href="index.php">AI Recipe Generator</a>
</div>
</nav>
<!-- Main Content -->
<main>
<div class="container py-5">
<div class="auth-container">
<h2 class="text-center">Two-Factor Authentication</h2>
<p class="text-center text-muted mb-4">A code has been sent to your phone. Please enter it below to complete your login.</p>
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<form action="login_2fa.php" method="POST">
<div class="mb-3">
<label for="verification_code" class="form-label">Verification Code</label>
<input type="text" class="form-control" id="verification_code" name="verification_code" required autofocus>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</form>
</div>
</div>
</main>
<!-- Footer -->
<footer class="footer">
<div class="container text-center">
<p>&copy; <?php echo date("Y"); ?> AI Recipe Generator. All Rights Reserved.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

44
mail/SmsService.php Normal file
View File

@ -0,0 +1,44 @@
<?php
class SmsService {
public static function sendSms($to, $message) {
$sms_config = require __DIR__ . '/../config/sms.php';
$api_key = $sms_config['smslink']['api_key'];
$sender = $sms_config['smslink']['sender'];
if ($api_key === 'YOUR_SMSLINK_API_KEY') {
// Don't send SMS if the API key is not set
error_log('SMS not sent: SMSLink API key is not set.');
return ['success' => false, 'error' => 'SMSLink API key is not set.'];
}
$url = 'https://secure.smslink.ro/sms/gateway/communicate/index.php';
$post_fields = [
'connection_id' => $api_key,
'to' => $to,
'message' => $message,
'sender' => $sender,
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
return ['success' => false, 'error' => $err];
} else {
// Basic check for success response from SMSLink
if (strpos($response, 'RESPONSE_CODE: 0') !== false) {
return ['success' => true];
} else {
return ['success' => false, 'error' => $response];
}
}
}
}

188
pricing.php Normal file
View File

@ -0,0 +1,188 @@
<?php
session_start();
require_once 'db/config.php';
// Base price per meal.
$base_price_per_meal = 9.99;
// Fetch products from the database
try {
$stmt = db()->query("SELECT * FROM products ORDER BY name ASC");
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
// Handle error if products can't be fetched
$products = [];
error_log("Could not fetch products: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Build Your Plan - AI Recipe Generator</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top">
<div class="container">
<a class="navbar-brand" href="index.php">AI Recipe Generator</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="index.php">Home</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="pricing.php">Pricing</a>
</li>
<?php if (isset($_SESSION['user_id'])): ?>
<li class="nav-item">
<a class="nav-link" href="profile.php">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">Logout</a>
</li>
<?php else: ?>
<li class="nav-item">
<a class="nav-link" href="login.php">Login</a>
</li>
<li class="nav-item">
<a class="btn btn-primary" href="register.php">Register</a>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<main>
<div class="container py-5">
<div class="pricing-header">
<h1>Build Your Perfect Meal Plan</h1>
<p class="lead">Customize your weekly delivery by choosing the number of people and meals.</p>
</div>
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="pricing-card interactive-card">
<form action="subscribe.php" method="post" id="plan-form">
<div class="row g-4 align-items-center">
<!-- People Selector -->
<div class="col-md-6">
<label for="people" class="form-label">Number of People</label>
<select class="form-select" id="people" name="people">
<option value="1">1 person</option>
<option value="2" selected>2 people</option>
<option value="3">3 people</option>
<option value="4">4 people</option>
<option value="5">5 people</option>
<option value="6">6 people</option>
</select>
</div>
<!-- Meals Selector -->
<div class="col-md-6">
<label for="meals" class="form-label">Meals per Week</label>
<select class="form-select" id="meals" name="meals">
<option value="2">2 meals</option>
<option value="3" selected>3 meals</option>
<option value="4">4 meals</option>
<option value="5">5 meals</option>
<option value="6">6 meals</option>
</select>
</div>
</div>
<hr class="my-4">
<!-- Add-on Products -->
<?php if (!empty($products)): ?>
<div class="mb-4">
<h5>Add-on Products</h5>
<p class="text-muted">Select any additional products to include in your weekly delivery.</p>
<div class="row">
<?php foreach ($products as $product): ?>
<div class="col-md-6">
<div class="form-check product-checkbox">
<input class="form-check-input" type="checkbox" name="products[]" value="<?= $product['id'] ?>" id="product_<?= $product['id'] ?>" data-price="<?= $product['price'] ?>">
<label class="form-check-label" for="product_<?= $product['id'] ?>">
<?= htmlspecialchars($product['name']) ?>
<span class="text-muted">(+$<?= htmlspecialchars(number_format($product['price'], 2)) ?>)</span>
</label>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<hr class="my-4">
<?php endif; ?>
<div class="text-center">
<p class="text-muted">Your custom plan price:</p>
<div class="price mb-3">
$<span id="total-price">0.00</span><span class="period">/week</span>
</div>
<button type="submit" class="btn btn-primary btn-lg">Subscribe Now</button>
</div>
</form>
</div>
</div>
</div>
</div>
</main>
<!-- Footer -->
<footer class="footer">
<div class="container text-center">
<p>&copy; <?php echo date("Y"); ?> AI Recipe Generator. All Rights Reserved.</p>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function () {
const peopleSelect = document.getElementById('people');
const mealsSelect = document.getElementById('meals');
const productCheckboxes = document.querySelectorAll('.product-checkbox input[type="checkbox"]');
const totalPriceEl = document.getElementById('total-price');
const basePrice = <?php echo $base_price_per_meal; ?>;
function updatePrice() {
// Calculate meal cost
const people = parseInt(peopleSelect.value, 10);
const meals = parseInt(mealsSelect.value, 10);
let total = people * meals * basePrice;
// Add cost of selected products
productCheckboxes.forEach(function(checkbox) {
if (checkbox.checked) {
total += parseFloat(checkbox.dataset.price);
}
});
totalPriceEl.textContent = total.toFixed(2);
}
peopleSelect.addEventListener('change', updatePrice);
mealsSelect.addEventListener('change', updatePrice);
productCheckboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', updatePrice);
});
// Initial price calculation
updatePrice();
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

136
profile.php Normal file
View File

@ -0,0 +1,136 @@
<?php
session_start();
require_once 'db/config.php';
// If the user is not logged in, redirect to the login page
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$user = null;
$subscription = null;
$error_message = '';
try {
$pdo = db();
// Fetch user details
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
// Fetch subscription details
// This part assumes you have a 'subscriptions' table that links to Stripe plan IDs.
// You might need to adjust the table and column names (e.g., stripe_plan_id, status, etc.)
$stmt = $pdo->prepare("SELECT * FROM subscriptions WHERE user_id = ? AND status = 'active'");
$stmt->execute([$_SESSION['user_id']]);
$subscription = $stmt->fetch();
} catch (PDOException $e) {
$error_message = "Database error: " . $e->getMessage();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Profile - AI Recipe Generator</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top">
<div class="container">
<a class="navbar-brand" href="index.php">AI Recipe Generator</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="index.php">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="pricing.php">Pricing</a>
</li>
<?php if (isset($_SESSION['user_id'])): ?>
<li class="nav-item">
<a class="nav-link active" href="profile.php">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">Logout</a>
</li>
<?php else: ?>
<li class="nav-item">
<a class="nav-link" href="login.php">Login</a>
</li>
<li class="nav-item">
<a class="btn btn-primary" href="register.php">Register</a>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<main>
<div class="container py-5">
<div class="auth-container" style="max-width: 800px;">
<h2 class="text-center mb-4">My Profile</h2>
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php endif; ?>
<?php if (isset($_SESSION['flash_message'])): ?>
<div class="alert alert-success">
<?php echo htmlspecialchars($_SESSION['flash_message']); unset($_SESSION['flash_message']); ?>
</div>
<?php endif; ?>
<?php if ($user): ?>
<div class="card mb-4">
<div class="card-header">Account Details</div>
<div class="card-body">
<p><strong>Username:</strong> <?php echo htmlspecialchars($user['username']); ?></p>
<p><strong>Email:</strong> <?php echo htmlspecialchars($user['email']); ?></p>
<p><strong>Phone:</strong> <?php echo htmlspecialchars($user['phone_number'] ?? 'Not provided'); ?></p>
</div>
</div>
<div class="card">
<div class="card-header">Subscription Status</div>
<div class="card-body">
<?php if ($subscription): ?>
<h5 class="card-title">Active Plan: <span class="text-primary"><?php echo htmlspecialchars(ucfirst(str_replace('price_', '', $subscription['stripe_plan_id']))); ?></span></h5>
<p><strong>Status:</strong> <span class="badge bg-success"><?php echo htmlspecialchars(ucfirst($subscription['status'])); ?></span></p>
<p>Subscribed since: <?php echo htmlspecialchars(date("F j, Y", strtotime($subscription['created_at']))); ?></p>
<a href="cancel_subscription.php" class="btn btn-danger mt-2">Cancel Subscription</a>
<?php else: ?>
<p>You are not subscribed to any plan.</p>
<a href="pricing.php" class="btn btn-primary">View Plans</a>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
</main>
<!-- Footer -->
<footer class="footer">
<div class="container text-center">
<p>&copy; <?php echo date("Y"); ?> AI Recipe Generator. All Rights Reserved.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

183
recipe.php Normal file
View File

@ -0,0 +1,183 @@
<?php
require_once __DIR__ . '/db/config.php';
$recipe_id = $_GET['id'] ?? null;
if (!$recipe_id || !is_numeric($recipe_id)) {
header('Location: index.php');
exit;
}
$recipe = null;
$error_message = null;
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT * FROM recipes WHERE id = ?');
$stmt->execute([$recipe_id]);
$recipe = $stmt->fetch();
if (!$recipe) {
http_response_code(404);
$error_message = "Recipe not found.";
}
} catch (PDOException $e) {
error_log('Database error: ' . $e->getMessage());
$error_message = 'We are currently experiencing technical difficulties. Please try again later.';
}
$project_image_url = $_SERVER['PROJECT_IMAGE_URL'] ?? 'https://via.placeholder.com/1200x630.png?text=rfresh';
$page_title = $recipe ? $recipe['title'] . ' - rfresh' : 'Recipe Not Found';
$page_description = $recipe ? htmlspecialchars($recipe['description']) : 'This recipe could not be found.';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $page_title; ?></title>
<meta name="description" content="<?php echo $page_description; ?>">
<meta property="og:type" content="website">
<meta property="og:title" content="<?php echo $page_title; ?>">
<meta property="og:description" content="<?php echo $page_description; ?>">
<meta property="og:image" content="<?php echo $recipe ? htmlspecialchars($recipe['image_url']) : $project_image_url; ?>">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="<?php echo $page_title; ?>">
<meta name="twitter:description" content="<?php echo $page_description; ?>">
<meta name="twitter:image" content="<?php echo $recipe ? htmlspecialchars($recipe['image_url']) : $project_image_url; ?>">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Georgia&family=Helvetica+Neue&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<style>
.recipe-header {
background-size: cover;
background-position: center;
color: white;
padding: 6rem 0;
text-shadow: 0 2px 10px rgba(0,0,0,0.5);
}
.recipe-content {
background: var(--card-bg-color);
margin-top: -4rem;
border-radius: 15px;
padding: 3rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.recipe-meta span {
margin-right: 1.5rem;
font-weight: 500;
}
.ingredients-list, .instructions-list {
padding-left: 1.2rem;
}
.ingredients-list li, .instructions-list li {
margin-bottom: 0.75rem;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm sticky-top">
<div class="container">
<a class="navbar-brand" href="/">rfresh</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto align-items-center">
<li class="nav-item">
<a class="nav-link" href="/#recipes">Our Menus</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pricing.php">Pricing</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/login.php">Log In</a>
</li>
<li class="nav-item ms-lg-3">
<a class="btn btn-primary" href="/register.php">Sign Up</a>
</li>
</ul>
</div>
</div>
</nav>
<main>
<?php if ($error_message): ?>
<div class="container py-5 text-center">
<h1 class="display-4">Error</h1>
<p class="lead"><?php echo htmlspecialchars($error_message); ?></p>
<a href="/" class="btn btn-primary mt-3">Back to Homepage</a>
</div>
<?php else: ?>
<header class="recipe-header text-center" style="background-image: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url(<?php echo htmlspecialchars($recipe['image_url']); ?>);">
<div class="container">
<h1 class="display-3"><?php echo htmlspecialchars($recipe['title']); ?></h1>
<p class="lead"><?php echo htmlspecialchars($recipe['description']); ?></p>
</div>
</header>
<div class="container">
<div class="row">
<div class="col-lg-10 mx-auto">
<div class="recipe-content">
<div class="recipe-meta text-center mb-4 pb-3 border-bottom">
<span class="badge bg-primary fs-6 me-3"><?php echo htmlspecialchars($recipe['difficulty']); ?></span>
<span class="text-muted">Prep Time: <?php echo htmlspecialchars($recipe['prep_time_minutes']); ?> minutes</span>
</div>
<div class="row">
<div class="col-md-5">
<h3 class="mb-3">Ingredients</h3>
<ul class="ingredients-list">
<?php
$ingredients = explode("\n", $recipe['ingredients']);
foreach ($ingredients as $ingredient) {
if (!empty(trim($ingredient))) {
echo '<li>' . htmlspecialchars(trim($ingredient)) . '</li>';
}
}
?>
</ul>
</div>
<div class="col-md-7">
<h3 class="mb-3">Instructions</h3>
<ol class="instructions-list">
<?php
$instructions = explode("\n", $recipe['instructions']);
foreach ($instructions as $instruction) {
if (!empty(trim($instruction))) {
echo '<li>' . htmlspecialchars(trim($instruction)) . '</li>';
}
}
?>
</ol>
</div>
</div>
<div class="text-center mt-5">
<a href="/" class="btn btn-outline-secondary">&larr; Back to All Recipes</a>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</main>
<footer class="footer">
<div class="container text-center">
<p class="mb-1">&copy; <?php echo date('Y'); ?> rfresh. All rights reserved.</p>
<p class="mb-0">Built with <a href="https://flatlogic.com" target="_blank" rel="noopener noreferrer">Flatlogic</a>.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

111
register.php Normal file
View File

@ -0,0 +1,111 @@
<?php
require_once 'db/config.php';
session_start();
$flash_message = null;
if (isset($_SESSION['flash_message'])) {
$flash_message = $_SESSION['flash_message'];
unset($_SESSION['flash_message']);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$phone_number = $_POST['phone_number'] ?? '';
if (empty($username) || empty($password) || empty($phone_number)) {
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Please fill out all fields.'];
} else {
try {
$pdo = db();
$stmt = $pdo->prepare('SELECT id FROM users WHERE username = ?');
$stmt->execute([$username]);
if ($stmt->fetch()) {
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Username already exists.'];
} else {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare('INSERT INTO users (username, password, phone_number, role) VALUES (?, ?, ?, ?)');
$stmt->execute([$username, $hashed_password, $phone_number, 'customer']);
$user_id = $pdo->lastInsertId();
$verification_code = str_pad(rand(0, 999999), 6, '0', STR_PAD_LEFT);
$stmt = $pdo->prepare('UPDATE users SET two_factor_secret = ? WHERE id = ?');
$stmt->execute([$verification_code, $user_id]);
require_once 'mail/SmsService.php';
SmsService::sendSms($phone_number, "Your verification code is: {$verification_code}");
$_SESSION['user_id_to_verify'] = $user_id;
header('Location: verify_phone.php');
exit;
}
} catch (PDOException $e) {
error_log('Registration Error: ' . $e->getMessage());
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'A database error occurred. Please try again.'];
}
}
header('Location: register.php');
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Create Account - rfresh</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Georgia&family=Helvetica+Neue&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<div class="auth-section">
<div class="card auth-card">
<div class="card-body">
<div class="text-center mb-4">
<a class="navbar-brand" href="/">rfresh</a>
</div>
<h1 class="card-title text-center mb-4">Create Your Account</h1>
<?php if ($flash_message): ?>
<div class="alert alert-<?php echo htmlspecialchars($flash_message['type']); ?>" role="alert">
<?php echo htmlspecialchars($flash_message['message']); ?>
</div>
<?php endif; ?>
<form action="register.php" method="POST">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3">
<label for="phone_number" class="form-label">Phone Number</label>
<input type="tel" class="form-control" id="phone_number" name="phone_number" required placeholder="+1234567890">
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-primary">Create Account</button>
</div>
</form>
<div class="text-center my-3">
<small class="text-muted">OR</small>
</div>
<div class="d-grid gap-2">
<a href="google_login.php" class="btn btn-social btn-google">Sign Up with Google</a>
<a href="facebook_login.php" class="btn btn-social btn-facebook">Sign Up with Facebook</a>
</div>
<div class="text-center mt-4">
<small>Already have an account? <a href="login.php">Log In</a></small>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

107
subscribe.php Normal file
View File

@ -0,0 +1,107 @@
<?php
require_once 'db/config.php';
require_once 'vendor/autoload.php';
$stripe_config = require_once 'config/stripe.php';
session_start();
// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
// Save the intended plan in session and redirect to login
$_SESSION['intended_plan'] = $_POST;
header('Location: login.php');
exit;
}
// --- Plan Customization ---
$people = filter_input(INPUT_POST, 'people', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 10]]);
$meals = filter_input(INPUT_POST, 'meals', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 10]]);
$base_price_per_meal = 9.99; // Should match the price on pricing.php
if ($people === false || $meals === false) {
header('Location: pricing.php?error=invalid_input');
exit;
}
// --- Product Selection ---
$selected_products = $_POST['products'] ?? [];
$product_total_price = 0;
$product_ids_for_metadata = [];
$line_items = [];
if (!empty($selected_products)) {
$placeholders = implode(',', array_fill(0, count($selected_products), '?'));
$stmt = db()->prepare("SELECT id, name, price FROM products WHERE id IN ($placeholders)");
$stmt->execute($selected_products);
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($products as $product) {
$product_total_price += $product['price'];
$product_ids_for_metadata[] = $product['id'];
}
}
// --- Price Calculation ---
$weekly_meal_price = $people * $meals * $base_price_per_meal;
$weekly_total = $weekly_meal_price + $product_total_price;
$monthly_price = $weekly_total * 4; // Approximate monthly price
$plan_name = sprintf("Weekly plan for %d %s, %d %s per week",
$people, ($people > 1 ? 'people' : 'person'),
$meals, ($meals > 1 ? 'meals' : 'meal')
);
if (!empty($products)) {
$plan_name .= " with add-ons";
}
// --- Stripe Checkout ---
if ($stripe_config['stripe']['secret_key'] === 'YOUR_STRIPE_SECRET_KEY') {
die('Error: Stripe secret key is not configured. Please update config/stripe.php with your actual Stripe keys.');
}
\Stripe\Stripe::setApiKey($stripe_config['stripe']['secret_key']);
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http";
$host = $_SERVER['HTTP_HOST'];
try {
$checkout_session = \Stripe\Checkout\Session::create([
'payment_method_types' => ['card'],
'line_items' => [[
'price_data' => [
'currency' => 'usd',
'product_data' => [
'name' => $plan_name,
],
'unit_amount' => round($monthly_price * 100), // Price in cents
'recurring' => [
'interval' => 'month',
],
],
'quantity' => 1,
]],
'mode' => 'subscription',
'success_url' => $protocol . '://' . $host . '/success.php?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => $protocol . '://' . $host . '/pricing.php',
'customer_email' => $_SESSION['user_email'] ?? null,
'metadata' => [
'user_id' => $_SESSION['user_id'],
'people' => $people,
'meals_per_week' => $meals,
'product_ids' => json_encode($product_ids_for_metadata) // Add product IDs to metadata
]
]);
header("Location: " . $checkout_session->url);
exit;
} catch (\Stripe\Exception\ApiErrorException $e) {
error_log('Stripe API error: ' . $e->getMessage());
header('Location: pricing.php?error=stripe_error');
exit;
} catch (Exception $e) {
error_log('General error: ' . $e->getMessage());
header('Location: pricing.php?error=general_error');
exit;
}

103
success.php Normal file
View File

@ -0,0 +1,103 @@
<?php
require_once 'db/config.php';
require_once 'vendor/autoload.php';
$stripe_config = require_once 'config/stripe.php';
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$session_id = $_GET['session_id'] ?? null;
if (!$session_id) {
header('Location: pricing.php');
exit;
}
if ($stripe_config['stripe']['secret_key'] === 'YOUR_STRIPE_SECRET_KEY') {
die('Error: Stripe secret key is not configured. Please update config/stripe.php with your actual Stripe keys.');
}
\Stripe\Stripe::setApiKey($stripe_config['stripe']['secret_key']);
try {
$checkout_session = \Stripe\Checkout\Session::retrieve($session_id);
$stripe_subscription_id = $checkout_session->subscription;
$user_id = $checkout_session->metadata->user_id;
// Double-check that the user ID from the session matches the one in Stripe metadata
if ($user_id != $_SESSION['user_id']) {
throw new Exception("User ID mismatch.");
}
// Reconstruct plan name from metadata
$people = $checkout_session->metadata->people;
$meals = $checkout_session->metadata->meals_per_week;
$plan_name = sprintf("Weekly plan for %d %s, %d %s per week",
$people, ($people > 1 ? 'people' : 'person'),
$meals, ($meals > 1 ? 'meals' : 'meal')
);
$product_ids = json_decode($checkout_session->metadata->product_ids ?? '[]');
if (!empty($product_ids)) {
$plan_name .= " with add-ons";
}
$pdo = db();
$pdo->beginTransaction();
// Save the main subscription
$stmt = $pdo->prepare("INSERT INTO subscriptions (user_id, plan_id, stripe_subscription_id, status, plan_name) VALUES (?, ?, ?, 'active', ?)");
$stmt->execute([$user_id, 'custom', $stripe_subscription_id, $plan_name]);
// Save the subscribed products
if (!empty($product_ids)) {
$product_stmt = $pdo->prepare("INSERT INTO user_subscription_products (user_id, product_id, stripe_subscription_id, quantity) VALUES (?, ?, ?, 1)");
foreach ($product_ids as $product_id) {
$product_stmt->execute([$user_id, $product_id, $stripe_subscription_id]);
}
}
$pdo->commit();
unset($_SESSION['intended_plan']);
} catch (\Stripe\Exception\ApiErrorException $e) {
if (isset($pdo) && $pdo->inTransaction()) {
$pdo->rollBack();
}
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Error: ' . $e->getMessage()];
header('Location: pricing.php');
exit;
} catch (Exception $e) {
if (isset($pdo) && $pdo->inTransaction()) {
$pdo->rollBack();
}
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Error: ' . $e->getMessage()];
header('Location: pricing.php');
exit;
}
include 'admin/templates/header.php';
?>
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8 text-center">
<div class="auth-container">
<h2 class="text-success">Payment Successful!</h2>
<p class="lead">Thank you for subscribing. Your plan and selected products are now active.</p>
<p>You will be redirected to your profile page shortly.</p>
<div class="spinner-border text-primary mt-3" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<meta http-equiv="refresh" content="5;url=profile.php">
</div>
</div>
</div>
</div>
<?php include 'admin/templates/footer.php'; ?>

22
vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,22 @@
<?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;
}
}
throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit3e6e79e410cd98a8d86f36f772fe3690::getLoader();

579
vendor/composer/ClassLoader.php vendored Normal file
View File

@ -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);
}
}

396
vendor/composer/InstalledVersions.php vendored Normal file
View File

@ -0,0 +1,396 @@
<?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 string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
* @internal
*/
private static $selfDir = null;
/**
* @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 string
*/
private static function getSelfDir()
{
if (self::$selfDir === null) {
self::$selfDir = strtr(__DIR__, '\\', '/');
}
return self::$selfDir;
}
/**
* @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 = self::getSelfDir();
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;
}
}

21
vendor/composer/LICENSE vendored Normal file
View File

@ -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.

55
vendor/composer/autoload_classmap.php vendored Normal file
View File

@ -0,0 +1,55 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Google_AccessToken_Revoke' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_AccessToken_Verify' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_AuthHandler_AuthHandlerFactory' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_AuthHandler_Guzzle6AuthHandler' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_AuthHandler_Guzzle7AuthHandler' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Client' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Collection' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Exception' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Http_Batch' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Http_MediaFileUpload' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Http_REST' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Model' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Service' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Service_Exception' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Service_Resource' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Task_Composer' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Task_Exception' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Task_Retryable' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Task_Runner' => $vendorDir . '/google/apiclient/src/aliases.php',
'Google_Utils_UriTemplate' => $vendorDir . '/google/apiclient/src/aliases.php',
'Stripe' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Stripe.php',
'Stripe_Account' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Account.php',
'Stripe_ApiConnectionError' => $vendorDir . '/stripe/stripe-php/lib/Stripe/ApiConnectionError.php',
'Stripe_ApiError' => $vendorDir . '/stripe/stripe-php/lib/Stripe/ApiError.php',
'Stripe_ApiRequestor' => $vendorDir . '/stripe/stripe-php/lib/Stripe/ApiRequestor.php',
'Stripe_ApiResource' => $vendorDir . '/stripe/stripe-php/lib/Stripe/ApiResource.php',
'Stripe_AuthenticationError' => $vendorDir . '/stripe/stripe-php/lib/Stripe/AuthenticationError.php',
'Stripe_CardError' => $vendorDir . '/stripe/stripe-php/lib/Stripe/CardError.php',
'Stripe_Charge' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Charge.php',
'Stripe_Coupon' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Coupon.php',
'Stripe_Customer' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Customer.php',
'Stripe_Error' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Error.php',
'Stripe_Event' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Event.php',
'Stripe_InvalidRequestError' => $vendorDir . '/stripe/stripe-php/lib/Stripe/InvalidRequestError.php',
'Stripe_Invoice' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Invoice.php',
'Stripe_InvoiceItem' => $vendorDir . '/stripe/stripe-php/lib/Stripe/InvoiceItem.php',
'Stripe_List' => $vendorDir . '/stripe/stripe-php/lib/Stripe/List.php',
'Stripe_Object' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Object.php',
'Stripe_Plan' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Plan.php',
'Stripe_Recipient' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Recipient.php',
'Stripe_SingletonApiResource' => $vendorDir . '/stripe/stripe-php/lib/Stripe/SingletonApiResource.php',
'Stripe_Token' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Token.php',
'Stripe_Transfer' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Transfer.php',
'Stripe_Util' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Util.php',
'Stripe_Util_Set' => $vendorDir . '/stripe/stripe-php/lib/Stripe/Util/Set.php',
);

15
vendor/composer/autoload_files.php vendored Normal file
View File

@ -0,0 +1,15 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'1f87db08236948d07391152dccb70f04' => $vendorDir . '/google/apiclient-services/autoload.php',
'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
'a8d3953fd9959404dd22d3dfcd0a79f0' => $vendorDir . '/google/apiclient/src/aliases.php',
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

24
vendor/composer/autoload_psr4.php vendored Normal file
View File

@ -0,0 +1,24 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'phpseclib3\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
'Google\\Service\\' => array($vendorDir . '/google/apiclient-services/src'),
'Google\\Auth\\' => array($vendorDir . '/google/auth/src'),
'Google\\' => array($vendorDir . '/google/apiclient/src'),
'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
'Facebook\\' => array($vendorDir . '/facebook/graph-sdk/src/Facebook'),
);

50
vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,50 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit3e6e79e410cd98a8d86f36f772fe3690
{
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('ComposerAutoloaderInit3e6e79e410cd98a8d86f36f772fe3690', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit3e6e79e410cd98a8d86f36f772fe3690', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit3e6e79e410cd98a8d86f36f772fe3690::getInitializer($loader));
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit3e6e79e410cd98a8d86f36f772fe3690::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) {
$requireFile($fileIdentifier, $file);
}
return $loader;
}
}

173
vendor/composer/autoload_static.php vendored Normal file
View File

@ -0,0 +1,173 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit3e6e79e410cd98a8d86f36f772fe3690
{
public static $files = array (
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
'1f87db08236948d07391152dccb70f04' => __DIR__ . '/..' . '/google/apiclient-services/autoload.php',
'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
'a8d3953fd9959404dd22d3dfcd0a79f0' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
);
public static $prefixLengthsPsr4 = array (
'p' =>
array (
'phpseclib3\\' => 11,
),
'P' =>
array (
'Psr\\Log\\' => 8,
'Psr\\Http\\Message\\' => 17,
'Psr\\Http\\Client\\' => 16,
'Psr\\Cache\\' => 10,
'ParagonIE\\ConstantTime\\' => 23,
),
'M' =>
array (
'Monolog\\' => 8,
),
'G' =>
array (
'GuzzleHttp\\Psr7\\' => 16,
'GuzzleHttp\\Promise\\' => 19,
'GuzzleHttp\\' => 11,
'Google\\Service\\' => 15,
'Google\\Auth\\' => 12,
'Google\\' => 7,
),
'F' =>
array (
'Firebase\\JWT\\' => 13,
'Facebook\\' => 9,
),
);
public static $prefixDirsPsr4 = array (
'phpseclib3\\' =>
array (
0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/src',
),
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-factory/src',
1 => __DIR__ . '/..' . '/psr/http-message/src',
),
'Psr\\Http\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-client/src',
),
'Psr\\Cache\\' =>
array (
0 => __DIR__ . '/..' . '/psr/cache/src',
),
'ParagonIE\\ConstantTime\\' =>
array (
0 => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src',
),
'Monolog\\' =>
array (
0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
),
'GuzzleHttp\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
),
'GuzzleHttp\\Promise\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
),
'GuzzleHttp\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
),
'Google\\Service\\' =>
array (
0 => __DIR__ . '/..' . '/google/apiclient-services/src',
),
'Google\\Auth\\' =>
array (
0 => __DIR__ . '/..' . '/google/auth/src',
),
'Google\\' =>
array (
0 => __DIR__ . '/..' . '/google/apiclient/src',
),
'Firebase\\JWT\\' =>
array (
0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
),
'Facebook\\' =>
array (
0 => __DIR__ . '/..' . '/facebook/graph-sdk/src/Facebook',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Google_AccessToken_Revoke' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_AccessToken_Verify' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_AuthHandler_AuthHandlerFactory' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_AuthHandler_Guzzle6AuthHandler' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_AuthHandler_Guzzle7AuthHandler' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Client' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Collection' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Exception' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Http_Batch' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Http_MediaFileUpload' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Http_REST' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Model' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Service' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Service_Exception' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Service_Resource' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Task_Composer' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Task_Exception' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Task_Retryable' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Task_Runner' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Google_Utils_UriTemplate' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php',
'Stripe' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Stripe.php',
'Stripe_Account' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Account.php',
'Stripe_ApiConnectionError' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/ApiConnectionError.php',
'Stripe_ApiError' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/ApiError.php',
'Stripe_ApiRequestor' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/ApiRequestor.php',
'Stripe_ApiResource' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/ApiResource.php',
'Stripe_AuthenticationError' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/AuthenticationError.php',
'Stripe_CardError' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/CardError.php',
'Stripe_Charge' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Charge.php',
'Stripe_Coupon' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Coupon.php',
'Stripe_Customer' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Customer.php',
'Stripe_Error' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Error.php',
'Stripe_Event' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Event.php',
'Stripe_InvalidRequestError' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/InvalidRequestError.php',
'Stripe_Invoice' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Invoice.php',
'Stripe_InvoiceItem' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/InvoiceItem.php',
'Stripe_List' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/List.php',
'Stripe_Object' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Object.php',
'Stripe_Plan' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Plan.php',
'Stripe_Recipient' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Recipient.php',
'Stripe_SingletonApiResource' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/SingletonApiResource.php',
'Stripe_Token' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Token.php',
'Stripe_Transfer' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Transfer.php',
'Stripe_Util' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Util.php',
'Stripe_Util_Set' => __DIR__ . '/..' . '/stripe/stripe-php/lib/Stripe/Util/Set.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit3e6e79e410cd98a8d86f36f772fe3690::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit3e6e79e410cd98a8d86f36f772fe3690::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit3e6e79e410cd98a8d86f36f772fe3690::$classMap;
}, null, ClassLoader::class);
}
}

1435
vendor/composer/installed.json vendored Normal file

File diff suppressed because it is too large Load Diff

227
vendor/composer/installed.php vendored Normal file
View File

@ -0,0 +1,227 @@
<?php return array(
'root' => array(
'name' => '__root__',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'eea139a530b4883242ee03552fea700bd821d27a',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'eea139a530b4883242ee03552fea700bd821d27a',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'facebook/graph-sdk' => array(
'pretty_version' => '4.0.23',
'version' => '4.0.23.0',
'reference' => 'c82611e05596622c9bd7abfa1cea1512d885c95e',
'type' => 'library',
'install_path' => __DIR__ . '/../facebook/graph-sdk',
'aliases' => array(),
'dev_requirement' => false,
),
'firebase/php-jwt' => array(
'pretty_version' => 'v6.11.1',
'version' => '6.11.1.0',
'reference' => 'd1e91ecf8c598d073d0995afa8cd5c75c6e19e66',
'type' => 'library',
'install_path' => __DIR__ . '/../firebase/php-jwt',
'aliases' => array(),
'dev_requirement' => false,
),
'google/apiclient' => array(
'pretty_version' => 'v2.18.4',
'version' => '2.18.4.0',
'reference' => '5b51fdb2cbd2a96088e3dfc6f565bdf6fb0af94b',
'type' => 'library',
'install_path' => __DIR__ . '/../google/apiclient',
'aliases' => array(),
'dev_requirement' => false,
),
'google/apiclient-services' => array(
'pretty_version' => 'v0.420.1',
'version' => '0.420.1.0',
'reference' => 'f1200dbf48d02dcfa36c5771f4dbc0433655a7ab',
'type' => 'library',
'install_path' => __DIR__ . '/../google/apiclient-services',
'aliases' => array(),
'dev_requirement' => false,
),
'google/auth' => array(
'pretty_version' => 'v1.49.0',
'version' => '1.49.0.0',
'reference' => '68e3d88cb59a49f713e3db25d4f6bb3cc0b70764',
'type' => 'library',
'install_path' => __DIR__ . '/../google/auth',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/guzzle' => array(
'pretty_version' => '7.10.0',
'version' => '7.10.0.0',
'reference' => 'b51ac707cfa420b7bfd4e4d5e510ba8008e822b4',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/promises' => array(
'pretty_version' => '2.3.0',
'version' => '2.3.0.0',
'reference' => '481557b130ef3790cf82b713667b43030dc9c957',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/promises',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/psr7' => array(
'pretty_version' => '2.8.0',
'version' => '2.8.0.0',
'reference' => '21dc724a0583619cd1652f673303492272778051',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(),
'dev_requirement' => false,
),
'monolog/monolog' => array(
'pretty_version' => '3.9.0',
'version' => '3.9.0.0',
'reference' => '10d85740180ecba7896c87e06a166e0c95a0e3b6',
'type' => 'library',
'install_path' => __DIR__ . '/../monolog/monolog',
'aliases' => array(),
'dev_requirement' => false,
),
'paragonie/constant_time_encoding' => array(
'pretty_version' => 'v3.1.3',
'version' => '3.1.3.0',
'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/constant_time_encoding',
'aliases' => array(),
'dev_requirement' => false,
),
'paragonie/random_compat' => array(
'pretty_version' => 'v9.99.100',
'version' => '9.99.100.0',
'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/random_compat',
'aliases' => array(),
'dev_requirement' => false,
),
'phpseclib/phpseclib' => array(
'pretty_version' => '3.0.47',
'version' => '3.0.47.0',
'reference' => '9d6ca36a6c2dd434765b1071b2644a1c683b385d',
'type' => 'library',
'install_path' => __DIR__ . '/../phpseclib/phpseclib',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/cache' => array(
'pretty_version' => '3.0.0',
'version' => '3.0.0.0',
'reference' => 'aa5030cfa5405eccfdcb1083ce040c2cb8d253bf',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/cache',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-client' => array(
'pretty_version' => '1.0.3',
'version' => '1.0.3.0',
'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-client',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-client-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-factory' => array(
'pretty_version' => '1.1.0',
'version' => '1.1.0.0',
'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-factory-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-message' => array(
'pretty_version' => '2.0',
'version' => '2.0.0.0',
'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-message',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-message-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/log' => array(
'pretty_version' => '3.0.2',
'version' => '3.0.2.0',
'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/log',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/log-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '3.0.0',
),
),
'ralouphie/getallheaders' => array(
'pretty_version' => '3.0.3',
'version' => '3.0.3.0',
'reference' => '120b605dfeb996808c31b6477290a714d356e822',
'type' => 'library',
'install_path' => __DIR__ . '/../ralouphie/getallheaders',
'aliases' => array(),
'dev_requirement' => false,
),
'stripe/stripe-php' => array(
'pretty_version' => 'v1.8.0',
'version' => '1.8.0.0',
'reference' => '5eba614baf4adefffdd59d196679a16d7baf70da',
'type' => 'library',
'install_path' => __DIR__ . '/../stripe/stripe-php',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.6.0',
'version' => '3.6.0.0',
'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

25
vendor/composer/platform_check.php vendored Normal file
View File

@ -0,0 +1,25 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 80100)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.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;
}
}
throw new \RuntimeException(
'Composer detected issues in your platform: ' . implode(' ', $issues)
);
}

7
vendor/facebook/graph-sdk/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
vendor/
composer.lock
composer.phar
.DS_Store
.idea/
tests/FacebookTestCredentials.php

View File

@ -0,0 +1,13 @@
Contributing
------------
For us to accept contributions you will have to first have signed the
[Contributor License Agreement](https://developers.facebook.com/opensource/cla).
When committing, keep all lines to less than 80 characters, and try to
follow the existing style.
Before creating a pull request, squash your commits into a single commit.
Add the comments where needed, and provide ample explanation in the
commit message.

19
vendor/facebook/graph-sdk/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright 2014 Facebook, Inc.
You are hereby granted a non-exclusive, worldwide, royalty-free license to
use, copy, modify, and distribute this software in source code or binary
form for use in connection with the web services and APIs provided by
Facebook.
As with any software that integrates with the Facebook platform, your use
of this software is subject to the Facebook Developer Principles and
Policies [http://developers.facebook.com/policy/]. This copyright 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.

88
vendor/facebook/graph-sdk/README.md vendored Normal file
View File

@ -0,0 +1,88 @@
Facebook SDK for PHP
====================
[![Latest Stable Version](http://img.shields.io/badge/Latest%20Stable-4.0.23-blue.svg)](https://packagist.org/packages/facebook/php-sdk-v4)
This repository contains the open source PHP SDK that allows you to access Facebook
Platform from your PHP app.
Usage
-----
This version of the Facebook SDK for PHP requires PHP 5.4 or greater.
Minimal example:
```php
<?php
// Skip these two lines if you're using Composer
define('FACEBOOK_SDK_V4_SRC_DIR', '/path/to/facebook-php-sdk-v4/src/Facebook/');
require __DIR__ . '/path/to/facebook-php-sdk-v4/autoload.php';
use Facebook\FacebookSession;
use Facebook\FacebookRequest;
use Facebook\GraphUser;
use Facebook\FacebookRequestException;
FacebookSession::setDefaultApplication('YOUR_APP_ID','YOUR_APP_SECRET');
// Use one of the helper classes to get a FacebookSession object.
// FacebookRedirectLoginHelper
// FacebookCanvasLoginHelper
// FacebookJavaScriptLoginHelper
// or create a FacebookSession with a valid access token:
$session = new FacebookSession('access-token-here');
// Get the GraphUser object for the current user:
try {
$me = (new FacebookRequest(
$session, 'GET', '/me'
))->execute()->getGraphObject(GraphUser::className());
echo $me->getName();
} catch (FacebookRequestException $e) {
// The Graph API returned an error
} catch (\Exception $e) {
// Some other error occurred
}
```
Complete documentation, installation instructions, and examples are available at:
[https://developers.facebook.com/docs/php](https://developers.facebook.com/docs/php)
Tests
-----
1) [Composer](https://getcomposer.org/) is a prerequisite for running the tests.
Install composer globally, then run `composer install` to install required files.
2) Create a test app on [Facebook Developers](https://developers.facebook.com), then
create `tests/FacebookTestCredentials.php` from `tests/FacebookTestCredentials.php.dist`
and edit it to add your credentials.
3) The tests can be executed by running this command from the root directory:
```bash
./vendor/bin/phpunit
```
Contributing
------------
For us to accept contributions you will have to first have signed the
[Contributor License Agreement](https://developers.facebook.com/opensource/cla).
When committing, keep all lines to less than 80 characters, and try to
follow the existing style.
Before creating a pull request, squash your commits into a single commit.
Add the comments where needed, and provide ample explanation in the
commit message.

70
vendor/facebook/graph-sdk/autoload.php vendored Normal file
View File

@ -0,0 +1,70 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
/**
* You only need this file if you are not using composer.
* Why are you not using composer?
* https://getcomposer.org/
*/
if (version_compare(PHP_VERSION, '5.4.0', '<')) {
throw new Exception('The Facebook SDK v4 requires PHP version 5.4 or higher.');
}
/**
* Register the autoloader for the Facebook SDK classes.
* Based off the official PSR-4 autoloader example found here:
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md
*
* @param string $class The fully-qualified class name.
* @return void
*/
spl_autoload_register(function ($class)
{
// project-specific namespace prefix
$prefix = 'Facebook\\';
// base directory for the namespace prefix
$base_dir = defined('FACEBOOK_SDK_V4_SRC_DIR') ? FACEBOOK_SDK_V4_SRC_DIR : __DIR__ . '/src/Facebook/';
// does the class use the namespace prefix?
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
// no, move to the next registered autoloader
return;
}
// get the relative class name
$relative_class = substr($class, $len);
// replace the namespace prefix with the base directory, replace namespace
// separators with directory separators in the relative class name, append
// with .php
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
// if the file exists, require it
if (file_exists($file)) {
require $file;
}
});

30
vendor/facebook/graph-sdk/composer.json vendored Normal file
View File

@ -0,0 +1,30 @@
{
"name": "facebook/php-sdk-v4",
"description": "Facebook SDK for PHP",
"keywords": ["facebook", "sdk"],
"type": "library",
"homepage": "https://github.com/facebook/facebook-php-sdk-v4",
"license": "Facebook Platform",
"authors": [
{
"name": "Facebook",
"homepage": "https://github.com/facebook/facebook-php-sdk-v4/contributors"
}
],
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "3.7.*",
"mockery/mockery": "dev-master",
"guzzlehttp/guzzle": "~4.0"
},
"suggest": {
"guzzlehttp/guzzle": "Allows for implementation of the Guzzle HTTP client"
},
"autoload": {
"psr-4": {
"Facebook\\": "src/Facebook/"
}
}
}

View File

@ -0,0 +1,37 @@
<card>
# FacebookCanvasLoginHelper for the Facebook SDK for PHP
A helper class for getting a FacebookSession in a Canvas app
</card>
<card>
## Facebook\FacebookCanvasLoginHelper {#overview}
If your app is loaded through Canvas, Facebook sends a POST request with a signed request. This helper class will handle processing and validating that information with Facebook, and returns a `FacebookSession`.
Usage:
~~~~
$helper = new FacebookCanvasLoginHelper();
try {
$session = $helper->getSession();
} catch (FacebookRequestException $ex) {
// When Facebook returns an error
} catch (\Exception $ex) {
// When validation fails or other local issues
}
if ($session) {
// Logged in.
}
~~~~
</card>
<card>
## Instance Methods {#instance-methods}
### getSession {#getsession}
`getSession()`
Processes the POST request from Facebook, if present. Returns a `FacebookSession` or `null`.
</card>

View File

@ -0,0 +1,39 @@
<card>
# FacebookJavaScriptLoginHelper for the Facebook SDK for PHP
A helper class for getting a FacebookSession using the session from the Facebook SDK for JavaScript.
</card>
<card>
## Facebook\FacebookJavaScriptLoginHelper {#overview}
If your web app uses the Facebook SDK for JavaScript, you can access that in your PHP code as well. This helper class will process and validate the cookie data used by the Facebook SDK for JavaScript, returning a `FacebookSession` on success.
Usage:
~~~~
$helper = new FacebookJavaScriptLoginHelper();
try {
$session = $helper->getSession();
} catch(FacebookRequestException $ex) {
// When Facebook returns an error
} catch(\Exception $ex) {
// When validation fails or other local issues
}
if ($session) {
// Logged in.
}
~~~~
It's important to note that on first access, or if a session has since expired, these methods will operate on data that is one request-cycle stale. You will likely want to make an Ajax request when the login state changes in the Facebook SDK for JavaScript. Information about that here: (FB.event.subscribe)[https://developers.facebook.com/docs/reference/javascript/FB.getLoginStatus/#events]
</card>
<card>
## Instance Methods {#instance-methods}
### getSession {#getsession}
`getSession()`
Processes the data available from the Facebook SDK for JavaScript, if present. Returns a `FacebookSession` or `null`.
</card>

View File

@ -0,0 +1,54 @@
<card>
# FacebookRedirectLoginHelper for the Facebook SDK for PHP
A helper class for getting a FacebookSession using the OAuth protocol.
</card>
<card>
## Facebook\FacebookRedirectLoginHelper {#overview}
If your web app uses Facebook Login on the back-end, you'll need to redirect your visitors to a URL at Facebook to initiate a login request. Facebook then redirects the user to your apps callback URL, providing session data. This helper class will generate the login URL for you, and can process and validate the data from Facebook, returning a `FacebookSession` on success.
This class can be extended, and the `storeState($state)` and `loadState()` methods overridden, to store the state check using another method besides the default `$_SESSION`.
Usage:
~~~~
$helper = new FacebookRedirectLoginHelper($redirect_url, $appId = NULL, $appSecret = NULL);
echo '<a href="' . $helper->getLoginUrl() . '">Login with Facebook</a>';
~~~~
Then, in your callback page (at the redirect url) when Facebook sends the user back:
~~~~
$helper = new FacebookRedirectLoginHelper($redirect_url);
try {
$session = $helper->getSessionFromRedirect();
} catch(FacebookRequestException $ex) {
// When Facebook returns an error
} catch(\Exception $ex) {
// When validation fails or other local issues
}
if ($session) {
// Logged in.
}
~~~~
</card>
<card>
## Instance Methods {#instance-methods}
### getLoginUrl {#getloginurl}
`getLoginUrl()`
Generates the URL to redirect a web visitor to Facebook to login to your app.
### getLogoutUrl {#getlogouturl}
`getLogoutUrl($next_url)`
Generates the URL to redirect a web visitor to Facebook to logout, with a url to redirect to after.
### getSessionFromRedirect {#getsessionfromredirect}
`getSessionFromRedirect()`
Processes the redirect data from Facebook, if present. Returns a `FacebookSession` or `null`.
</card>

View File

@ -0,0 +1,59 @@
<card>
# FacebookRequest for the Facebook SDK for PHP
Represents a request that will be made against the Graph API.
</card>
<card>
## Facebook\FacebookRequest {#overview}
Constructor:
~~~~
$request = new FacebookRequest(
FacebookSession $session,
string $httpMethod,
string $path,
array $params = NULL,
string $version = NULL
);
~~~~
Usage:
~~~~
// Make a new request and execute it.
try {
$response = (new FacebookRequest($session, 'GET', '/me'))->execute();
$object = $response->getGraphObject();
echo $object->getProperty('name');
} catch (FacebookRequestException $ex) {
echo $ex->getMessage();
} catch (\Exception $ex) {
echo $ex->getMessage();
}
// You can chain methods together and get a strongly typed GraphUser
$me = (new FacebookRequest(
$session, 'GET', '/me'
))->execute()->getGraphObject(GraphUser::className);
echo $me->getName();
~~~~
</card>
<card>
## Instance Methods {#instance-methods}
### execute {#execute}
`execute()`
Returns a `Facebook\FacebookResponse` from this request, from which a strongly-typed result can be retrieved. Throws an exception if the request fails. If the error is returned from Facebook, as opposed to a networking issue, a `Facebook\FacebookRequestException` is thrown.
### getPath {#getpath}
`getPath()`
Returns a copy of the path for the request, not including the version.
### getParameters {#getparams}
`getParameters()`
Returns a copy of the parameters array for the request.
### getSession {#getsession}
`getSession()`
Returns the `Facebook\FacebookSession` object associated with this request.
</card>

View File

@ -0,0 +1,43 @@
<card>
# FacebookRequestException for the Facebook SDK for PHP
Represents an exception thrown by executing a Facebook request.
</card>
<card>
## Facebook\FacebookRequestException {#overview}
This base class has several subclasses:
`FacebookAuthorizationException`
`FacebookClientException`
`FacebookPermissionException`
`FacebookServerException`
`FacebookThrottleException`
`FacebookOtherException`
Whenever a FacebookRequestException is thrown, it will be one of these types.
They are derived from the error information here: https://developers.facebook.com/docs/graph-api/using-graph-api/#errors
</card>
<card>
## Instance Methods {#instance-methods}
`FacebookRequestException` extends from the base `\Exception` class, so `getCode()` and `getMessage()` are available by default.
### getHttpStatusCode {#gethttpstatus}
`getHttpStatusCode()`
Returns the HTTP status code returned with this exception.
### getSubErrorCode {#getsuberrorcode}
`getSubErrorCode()`
Returns the numeric sub-error code returned from Facebook.
### getErrorType {#geterrortype}
`getErrorType()`
Returns the type of error as a string.
### getResponse {#getresponse}
`getResponse()`
Returns the decoded response used to create the exception.
### getRawResponse {#getrawresponse}
`getRawResponse()`
Returns the raw response used to create the exception.
</card>

View File

@ -0,0 +1,57 @@
<card>
# FacebookResponse for the Facebook SDK for PHP
Represents a response from the Graph API.
</card>
<card>
## Facebook\FacebookResponse {#overview}
Usage:
~~~~
// A FacebookResponse is returned from an executed FacebookRequest
try {
$response = (new FacebookRequest($session, 'GET', '/me'))->execute();
// You can get the request back:
$request = $response->getRequest();
// You can get the response as a GraphObject:
$object = $response->getGraphObject();
// You can get the response as a subclass of GraphObject:
$me = $response->getGraphObject(GraphUser::className());
// If this response has multiple pages, you can get a request for the next or previous pages:
$nextPageRequest = $response->getRequestForNextPage();
$previousPageRequest = $response->getRequestForPreviousPage();
} catch (FacebookRequestException $ex) {
echo $ex->getMessage();
} catch (\Exception $ex) {
echo $ex->getMessage();
}
// You can also chain the methods together:
$me = (new FacebookRequest(
$session, 'GET', '/me'
))->execute()->getGraphObject(GraphUser::className);
echo $me->getName();
~~~~
</card>
<card>
## Instance Methods {#instance-methods}
### getGraphObject {#getgraphobject}
`getGraphObject(string $type = 'Facebook\GraphObject')`
Returns the result as a `GraphObject`. If specified, a strongly-typed subclass of `GraphObject` is returned.
### getGraphObjectList {#getgraphobjectlist}
`getGraphObjectList(string $type = 'Facebook\GraphObject')`
Returns an array of `GraphObject` returned by this request. If specified, a strongly-typed subclass of `GraphObject` is returned.
### getRequest {#getrequest}
`getRequest()`
Returns the `FacebookRequest` that produced this response.
### getRequestForNextPage {#getnextpage}
`getRequestForNextPage()`
If the response has paginated data, produces a `FacebookRequest` for the next pge of data.
### getRequestForPreviousPage {#getpreviouspage}
`getRequestForPreviousPage()`
If the response has paginated data, produces a `FacebookRequest` for the previous page of data.
</card>

View File

@ -0,0 +1,71 @@
<card>
# FacebookSession for the Facebook SDK for PHP
Represents a Facebook Session, which is used when making requests to the Graph API.
</card>
<card>
## Facebook\FacebookSession {#overview}
Usage:
~~~~
use Facebook\FacebookSession;
FacebookSession::setDefaultApplication('app-id', 'app-secret');
// If you already have a valid access token:
$session = new FacebookSession('access-token');
// If you're making app-level requests:
$session = FacebookSession::newAppSession();
// To validate the session:
try {
$session->validate();
} catch (FacebookRequestException $ex) {
// Session not valid, Graph API returned an exception with the reason.
echo $ex->getMessage();
} catch (\Exception $ex) {
// Graph API returned info, but it may mismatch the current app or have expired.
echo $ex->getMessage();
}
~~~~
</card>
<card>
## Static Methods {#static-methods}
### setDefaultApplication {#setdefaultapp}
`setDefaultApplication(string $appId, string $appSecret)`
Configures and app ID and secret that will be used by default throughout the SDK (but can be overridden whenever necessary using parameters to other methods.
### validate {#validate}
`validate(Facebook\GraphSessionInfo $sessionInfo, string $appId = NULL, string $appSecret = NULL)`
Ensures that the provided GraphSessionInfo is valid, throwing an exception if not. It does this by ensuring the app ID in the token info matches the given (or default) app ID, ensuring the token itself is valid, and ensuring that the expiration time has not passed.
### newAppSession {#newappsession}
`newAppSession(string $appId = NULL, string $appSecret = NULL)`
Returns a `Facebook\FacebookSession` configured with a token for the app which can be used for publishing and for requesting app-level information.
### newSessionFromSignedRequest {#newsessionfromsr}
`newSessionFromSignedRequest(string $signedRequest)`
Returns a `Facebook\FacebookSession` for the given signed request.
</card>
<card>
## Instance Methods {#instance-methods}
### getToken {#gettoken}
`getToken()`
Returns the token string for the session.
### getSessionInfo {#getsessioninfo}
`getSessionInfo(string $appId = NULL, string $appSecret = NULL)`
Equivalent to calling the /debug_token endpoint of the Graph API to get the details for the access token for this session. Returns a `Facebook\GraphSessionInfo` object.
### getLongLivedSession {#getlonglivedsession}
`getLongLivedSession(string $appId = NULL, string $appSecret = NULL)`
Returns a new `Facebook\FacebookSession` resulting from extending a short-lived access token. This method will make a network request. If you know you already have a long-lived session, you do not need to call this. The only time you get a short-lived session as of March 2014 is from the Facebook SDK for JavaScript. If this session is not short-lived, this method will return `$this`. A long-lived session is on the order of months. A short-lived session is on the order of hours. You can figure out whether a session is short-lived or long-lived by checking the expiration date in the session info, but it's not a precise thing.
### getExchangeToken {#getexchangetoken}
`getExchangeToken(string $appId = NULL, string $appSecret = NULL)`
Returns an exchange token string which can be sent back to clients and exchanged for a device-linked access token. You need this when your user did not log in on a particular device, but you want to be able to make Graph API calls from that device as this user.
### validate {#validatei}
`validate(string $appId = NULL, string $appSecret = NULL)`
Ensures that a session is valid, throwing an exception if not. It does this by fetching the token info, ensuring the app ID in the token info matches the given (or default) app ID, ensuring the token itself is valid, and ensuring that the expiration time has not passed.
</card>

View File

@ -0,0 +1,155 @@
<card>
# GraphObject for the Facebook SDK for PHP
Represents an object returned by the Graph API.
</card>
<card>
## Facebook\GraphObject {#overview}
This base class has several subclasses, some are provided by default:
[__GraphUser__](#user-instance-methods)
[__GraphLocation__](#location-instance-methods)
[__GraphSessionInfo__](#sessioninfo-instance-methods)
Usage:
~~~~
// Get the base class GraphObject from the response
$object = $response->getGraphObject();
// Get the response typed as a GraphUser
$user = $response->getGraphObject(GraphUser::className());
// or convert the base object previously accessed
// $user = $object->cast(GraphUser::className());
// Get the response typed as a GraphLocation
$loc = $response->getGraphObject(GraphLocation::className());
// or convert the base object previously accessed
// $loc = $object->cast(GraphLocation::className());
// User example
echo $object->getProperty('name');
echo $user->getName();
// Location example
echo $object->getProperty('country');
echo $loc->getCountry();
// SessionInfo example
$info = $session->getSessionInfo());
echo $info->getxpiresAt();
~~~~
</card>
<card>
## GraphObject Instance Methods {#instance-methods}
### cast {#cast}
`cast(string $type)`
Returns a new instance of a GraphObject subclass with this objects underlying data.
### asArray {#asarray}
`asArray()`
Returns the raw representation (associative arrays, nested) of this objects underlying data.
### getProperty {#getproperty}
`getProperty(string $name, string $type = 'Facebook\GraphObject')`
Gets the value of a named key for this graph object. If the value is a scalar (string, number, etc.) it will be returned. If it's an associative array, it will be returned as a GraphObject cast to the appropriate subclass type if provided.
### getPropertyAsArray {#getproparray}
`getPropertyAsArray()`
Gets the contents of a named array property on this graph object. If the values are scalar (strings, numbers, etc.) they will be returned as-is. If the values are associative arrays, they will be returned as GraphObjects cast to the appropriate subclass type if provided.
### getPropertyNames
`getPropertyNames()`
Returns an array with the names of all properties present on this graph object.
</card>
<card>
## GraphUser Instance Methods {#user-instance-methods}
### getId {#getid}
`getId()`
Returns the `id` property for the user as a string if present.
### getName {#getname}
`getName()`
Returns the `name` property for the user as a string if present.
### getFirstName {#getfirstname}
`getFirstName()`
Returns the `first_name` property for the user as a string if present.
### getMiddleName {#getmiddlename}
`getMiddleName()`
Returns the `middle_name` property for the user as a string if present.
### getLastName {#getlastname}
`getLastName()`
Returns the `last_name` property for the user as a string if present.
### getLink {#getlink}
`getLink()`
Returns the `link` property for the user as a string if present.
### getUsername {#getusername}
`getUsername()`
Returns the `username` property for the user as a string if present.
### getBirthday {#getbirthday}
`getBirthday()`
Returns the `birthday` property for the user as a `\DateTime` if present.
### getLocation {#getlocation}
`getLocation()`
Returns the `location` property for the user as a `Facebook\GraphLocation` if present.'
</card>
<card>
## GraphLocation Instance Methods {#location-instance-methods}
### getStreet {#getstreet}
`getStreet()`
Returns the `street` property for the location as a string if present.
### getCity {#getcity}
`getCity()`
Returns the `city` property for the location as a string if present.
### getState {#getstate}
`getState()`
Returns the `state` property for the location as a string if present.
### getCountry {#getcountry}
`getCountry()`
Returns the `country` property for the location as a string if present.
### getZip {#getzip}
`getZip()`
Returns the `zip` property for the user as a location if present.
### getLatitude {#getlat}
`getLatitude()`
Returns the `latitude` property for the location as a float if present.
### getLongitude {#getlon}
`getLongitude()`
Returns the `latitude` property for the location as a float if present.
</card>
<card>
## GraphSessionInfo Instance Methods {#sessioninfo-instance-methods}
### getAppId {#getappid}
`getAppId()`
Returns the `app_id` property for the session as a string if present.
### getApplication {#getapp}
`getApplication()`
Returns the `application` property for the session as a string if present.
### getExpiresAt {#getexpires}
`getExpiresAt()`
Returns the `expires_at` property for the session as a \DateTime if present.
### getIsValid {#getvalid}
`getIsValid()`
Returns the `is_valid` property for the session as a boolean if present.
### getIssuedAt {#getissued}
`getIssuedAt()`
Returns the `issued_at` property for the session as a \DateTime if present.
### getScopes {#getscopes}
`getScopes()`
Returns the `scopes` property for the session as an array if present.
### getId {#getuid}
`getId()`
Returns the `user_id` property for the session as a string if present.
</card>

View File

@ -0,0 +1,44 @@
<card>
# Post Links Using the Graph API
This example covers posting a link to the current user's timeline using the Graph API and Facebook SDK for PHP.
It assumes that you've already set your default app id and secret, and acquired a `FacebookSession` using an access token or one of the login helper classes found [here](/docs/php). You must have requested the `publish_actions` scope when logging in the user for this to work.
For more information, see the documentation for [`GraphObject`](/docs/php/GraphObject), [`FacebookRequest`](/docs/php/FacebookRequest), and [`FacebookRequestException`](/docs/php/FacebookRequestException).
</card>
<card>
~~~~
use Facebook\FacebookRequest;
use Facebook\GraphObject;
use Facebook\FacebookRequestException;
if($session) {
try {
$response = (new FacebookRequest(
$session, 'POST', '/me/feed', array(
'link' => 'www.example.com',
'message' => 'User provided message'
)
))->execute()->getGraphObject();
echo "Posted with id: " . $response->getProperty('id');
} catch(FacebookRequestException $e) {
echo "Exception occured, code: " . $e->getCode();
echo " with message: " . $e->getMessage();
}
}
~~~~
Note that the 'message' field must come from the user, as pre-filled content is forbidden by the [Platform Policies](https://developers.intern.facebook.com/policy/#control) (2.3).
</card>

View File

@ -0,0 +1,38 @@
<card>
# Retrieve User Profile via the Graph API
This example covers getting profile information for the current user and printing their name, using the Graph API and the Facebook SDK for PHP.
It assumes that you've already set your default app id and secret, and acquired a `FacebookSession` using an access token or one of the login helper classes found [here](/docs/php).
For more information, see the documentation for [`GraphObject`](/docs/php/GraphObject), [`GraphUser`](/docs/php/GraphObject/#user-instance-methods), [`FacebookRequest`](/docs/php/FacebookRequest), and [`FacebookRequestException`](/docs/php/FacebookRequestException).
</card>
<card>
~~~~
use Facebook\FacebookRequest;
use Facebook\GraphUser;
use Facebook\FacebookRequestException;
if($session) {
try {
$user_profile = (new FacebookRequest(
$session, 'GET', '/me'
))->execute()->getGraphObject(GraphUser::className());
echo "Name: " . $user_profile->getName();
} catch(FacebookRequestException $e) {
echo "Exception occured, code: " . $e->getCode();
echo " with message: " . $e->getMessage();
}
}
~~~~
</card>

View File

@ -0,0 +1,136 @@
<card>
# Getting started with the Facebook SDK for PHP
The Facebook SDK for PHP provides developers with a modern, native library for accessing the Graph API and taking advantage of Facebook Login. Usually this means you're developing with PHP for a Facebook Canvas app, building your own website, or adding server-side functionality to an app that already uses the [Facebook SDK for JavaScript](/docs/reference/javascript/).
</card>
<card>
## Download the SDK {#download}
%FB(devsite:markdown-wiki:info-card {
content: "The Facebook SDK for PHP v4 requires PHP 5.4 or greater.",
type: 'warning',
})
If you're using [Composer](https://getcomposer.org/) as a package manager for PHP, which we recommend, installing the SDK is as easy as adding a require entry for the Facebook SDK for PHP to the composer.json file in the root of your project:
~~~~
{
"require" : {
"facebook/php-sdk-v4" : "~4.0.12"
}
}
~~~~
Then run composer with the install parameter, and it will download the newest version. If you're using the autoloader as part of Composer, the Facebook namespace will be available for use without adding require statements for all of the files.
If you're not using Composer, you can download the SDK from our GitHub:
%FB(devsite:markdown-wiki:button {
text: 'Download the PHP SDK',
href: 'https://github.com/facebook/facebook-php-sdk-v4/archive/4.0-dev.zip',
size: 'large',
use: 'special',
})
</card>
<card>
## Initializing {#init}
You will need to have configured a Facebook App, which you can obtain from the [App Dashboard](http://developers.facebook.com/apps).
Then, initialize the SDK with your app ID and secret:
~~~
session_start(); // For the Facebook SDK to work correctly, you need to start a PHP session
FacebookSession::setDefaultApplication('YOUR_APP_ID', 'YOUR_APP_SECRET');
~~~
</card>
<card>
## Authentication and authorization {#authentication}
The SDK can be used to support login your site using a Facebook account. On the server-side, the SDK provides helper classes for the most common scenarios.
For most websites, you'll use the [FacebookRedirectLoginHelper](/docs/php/FacebookRedirectLoginHelper). Generate the login URL to redirect visitors to with the `getLoginUrl()` method, redirect them, and then process the response from Facebook with the `getSessionFromRedirect()` method, which returns a `FacebookSession`.
~~~~
$helper = new FacebookRedirectLoginHelper('your redirect URL here');
$loginUrl = $helper->getLoginUrl();
// Use the login url on a link or button to redirect to Facebook for authentication
~~~~
~~~~
$helper = new FacebookRedirectLoginHelper('your redirect URL here');
try {
$session = $helper->getSessionFromRedirect();
} catch(FacebookRequestException $ex) {
// When Facebook returns an error
} catch(\Exception $ex) {
// When validation fails or other local issues
}
if ($session) {
// Logged in
}
~~~~
If your app is on Facebook Canvas, use the `getSession()` method on [FacebookCanvasLoginHelper](/docs/php/FacebookCanvasLoginHelper) to get a `FacebookSession` for the user.
~~~~
$helper = new FacebookCanvasLoginHelper();
try {
$session = $helper->getSession();
} catch(FacebookRequestException $ex) {
// When Facebook returns an error
} catch(\Exception $ex) {
// When validation fails or other local issues
}
if ($session) {
// Logged in
}
~~~~
If you're already using the Facebook SDK for JavaScript to authenticate users, you can also log them in on the server side with the [FacebookJavaScriptLoginHelper](/docs/php/FacebookJavaScriptLoginHelper). The `getSession()` method will return a `FacebookSession`.
~~~~
$helper = new FacebookJavaScriptLoginHelper();
try {
$session = $helper->getSession();
} catch(FacebookRequestException $ex) {
// When Facebook returns an error
} catch(\Exception $ex) {
// When validation fails or other local issues
}
if ($session) {
// Logged in
}
~~~~
You can also create a `FacebookSession` with an access token you've acquired through some other means, by passing it to the constructor.
~~~~
$session = new FacebookSession('access token here');
~~~~
</card>
<card>
## Making Requests to the Graph API {#making-requests}
Once you have a `FacebookSession` you can begin making calls to the Graph API with [FacebookRequest](/docs/php/FacebookRequest).
~~~~
$request = new FacebookRequest($session, 'GET', '/me');
$response = $request->execute();
$graphObject = $response->getGraphObject();
~~~~
You can also chain these methods:
~~~~
$me = (new FacebookRequest(
$session, 'GET', '/me'
))->execute()->getGraphObject(GraphUser::className());
~~~~
For more details, see the examples and API reference for all of these classes listed on the [landing page for the Facebook SDK for PHP](/docs/reference/php).
</card>

View File

@ -0,0 +1,45 @@
<card>
# Facebook SDK for PHP
The Facebook SDK for PHP provides developers with a modern, native library for accessing the Graph API and taking advantage of Facebook Login. Usually this means you're developing with PHP for a Facebook Canvas app, building your own website, or adding server-side functionality to an app that already uses the [Facebook SDK for JavaScript](/docs/reference/javascript/).
Take a look through our guide, [Getting Started with the Facebook SDK for PHP](/docs/php/gettingstarted), and then check out some of the examples below.
</card>
<card>
## Examples {#examples}
* [Retrieve a users profile](/docs/php/howto/profilewithgraphapi)
* [Post a link to a users feed](/docs/php/howto/postwithgraphapi)
* [Upload a photo to a users profile](/docs/php/howto/uploadphoto)
</card>
<card>
## API Reference - Facebook namespace {#reference}
[Facebook\FacebookSession](/docs/php/FacebookSession)
An access token backed session used when making requests against the Graph API.
[Facebook\FacebookRequest](/docs/php/FacebookRequest)
Makes requests against the Graph API.
[Facebook\FacebookResponse](/docs/php/FacebookResponse)
A successful response from the Graph API resulting from a FacebookRequest.
[Facebook\FacebookRequestException](/docs/php/FacebookRequestException)
An error returned by the Graph API during a FacebookRequest. Subclasses: FacebookClientException, FacebookServerException, FacebookAuthorizationException, FacebookPermissionException, FacebookThrottleException, FacebookOtherException.
[Facebook\GraphObject](/docs/php/GraphObject)
Represents an object returned by the Graph API. Subclasses: GraphUser, GraphLocation, GraphSessionInfo
[Facebook\FacebookRedirectLoginHelper](/docs/php/FacebookRedirectLoginHelper)
A helper class for getting a FacebookSession using the OAuth protocol.
[Facebook\FacebookCanvasLoginHelper](/docs/php/FacebookCanvasLoginHelper)
A helper class for getting a FacebookSession from Facebook Canvas.
[Facebook\FacebookJavaScriptLoginHelper](/docs/php/FacebookJavaScriptLoginHelper)
A helper class for getting a FacebookSession from the Facebook SDK for JavaScript.
</card>

View File

@ -0,0 +1,49 @@
<card>
# Upload Photos to a User's Profile
This example covers uploading a photo to the current User's profile using the Graph API and the Facebook SDK for PHP.
It assumes that you've already set your default app id and secret, and acquired a `FacebookSession` using an access token or one of the login helper classes found [here](/docs/php). You must have requested the `publish_actions` scope when logging in the user for this to work.
For more information, see the documentation for [`GraphObject`](/docs/php/GraphObject), [`FacebookRequest`](/docs/php/FacebookRequest), and [`FacebookRequestException`](/docs/php/FacebookRequestException).
</card>
<card>
~~~~
use Facebook\FacebookRequest;
use Facebook\GraphObject;
use Facebook\FacebookRequestException;
if($session) {
try {
// Upload to a user's profile. The photo will be in the
// first album in the profile. You can also upload to
// a specific album by using /ALBUM_ID as the path
$response = (new FacebookRequest(
$session, 'POST', '/me/photos', array(
'source' => new CURLFile('path/to/file.name', 'image/png'),
'message' => 'User provided message'
)
))->execute()->getGraphObject();
// If you're not using PHP 5.5 or later, change the file reference to:
// 'source' => '@/path/to/file.name'
echo "Posted with id: " . $response->getProperty('id');
} catch(FacebookRequestException $e) {
echo "Exception occured, code: " . $e->getCode();
echo " with message: " . $e->getMessage();
}
}
~~~~
Note that the 'message' field must come from the user, as pre-filled content is forbidden by the [Platform Policies](https://developers.intern.facebook.com/policy/#control) (2.3).
</card>

View File

@ -0,0 +1,19 @@
<phpunit
colors="true"
stderr="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
stopOnFailure="false"
bootstrap="tests/bootstrap.php">
<testsuites>
<testsuite name="Facebook PHP SDK v4 Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src/Facebook</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,379 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook\Entities;
use Facebook\FacebookRequest;
use Facebook\FacebookRequestException;
use Facebook\FacebookSession;
use Facebook\GraphSessionInfo;
/**
* Class AccessToken
* @package Facebook
*/
class AccessToken
{
/**
* The access token.
*
* @var string
*/
protected $accessToken;
/**
* A unique ID to identify a client.
*
* @var string
*/
protected $machineId;
/**
* Date when token expires.
*
* @var \DateTime|null
*/
protected $expiresAt;
/**
* Create a new access token entity.
*
* @param string $accessToken
* @param int $expiresAt
* @param string|null machineId
*/
public function __construct($accessToken, $expiresAt = 0, $machineId = null)
{
$this->accessToken = $accessToken;
if ($expiresAt) {
$this->setExpiresAtFromTimeStamp($expiresAt);
}
$this->machineId = $machineId;
}
/**
* Setter for expires_at.
*
* @param int $timeStamp
*/
protected function setExpiresAtFromTimeStamp($timeStamp)
{
$dt = new \DateTime();
$dt->setTimestamp($timeStamp);
$this->expiresAt = $dt;
}
/**
* Getter for expiresAt.
*
* @return \DateTime|null
*/
public function getExpiresAt()
{
return $this->expiresAt;
}
/**
* Getter for machineId.
*
* @return string|null
*/
public function getMachineId()
{
return $this->machineId;
}
/**
* Determines whether or not this is a long-lived token.
*
* @return bool
*/
public function isLongLived()
{
if ($this->expiresAt) {
return $this->expiresAt->getTimestamp() > time() + (60 * 60 * 2);
}
return false;
}
/**
* Checks the validity of the access token.
*
* @param string|null $appId Application ID to use
* @param string|null $appSecret App secret value to use
* @param string|null $machineId
*
* @return boolean
*/
public function isValid($appId = null, $appSecret = null, $machineId = null)
{
$accessTokenInfo = $this->getInfo($appId, $appSecret);
$machineId = $machineId ?: $this->machineId;
return static::validateAccessToken($accessTokenInfo, $appId, $machineId);
}
/**
* Ensures the provided GraphSessionInfo object is valid,
* throwing an exception if not. Ensures the appId matches,
* that the machineId matches if it's being used,
* that the token is valid and has not expired.
*
* @param GraphSessionInfo $tokenInfo
* @param string|null $appId Application ID to use
* @param string|null $machineId
*
* @return boolean
*/
public static function validateAccessToken(GraphSessionInfo $tokenInfo,
$appId = null, $machineId = null)
{
$targetAppId = FacebookSession::_getTargetAppId($appId);
$appIdIsValid = $tokenInfo->getAppId() == $targetAppId;
$machineIdIsValid = $tokenInfo->getProperty('machine_id') == $machineId;
$accessTokenIsValid = $tokenInfo->isValid();
$accessTokenIsStillAlive = true;
// Not all access tokens return an expiration. E.g. an app access token.
if ($tokenInfo->getExpiresAt() instanceof \DateTime) {
$accessTokenIsStillAlive = $tokenInfo->getExpiresAt()->getTimestamp() >= time();
}
return $appIdIsValid && $machineIdIsValid && $accessTokenIsValid && $accessTokenIsStillAlive;
}
/**
* Get a valid access token from a code.
*
* @param string $code
* @param string|null $appId
* @param string|null $appSecret
* @param string|null $machineId
*
* @return AccessToken
*/
public static function getAccessTokenFromCode($code, $appId = null, $appSecret = null, $machineId = null)
{
$params = array(
'code' => $code,
'redirect_uri' => '',
);
if ($machineId) {
$params['machine_id'] = $machineId;
}
return static::requestAccessToken($params, $appId, $appSecret);
}
/**
* Get a valid code from an access token.
*
* @param AccessToken|string $accessToken
* @param string|null $appId
* @param string|null $appSecret
*
* @return AccessToken
*/
public static function getCodeFromAccessToken($accessToken, $appId = null, $appSecret = null)
{
$accessToken = (string) $accessToken;
$params = array(
'access_token' => $accessToken,
'redirect_uri' => '',
);
return static::requestCode($params, $appId, $appSecret);
}
/**
* Exchanges a short lived access token with a long lived access token.
*
* @param string|null $appId
* @param string|null $appSecret
*
* @return AccessToken
*/
public function extend($appId = null, $appSecret = null)
{
$params = array(
'grant_type' => 'fb_exchange_token',
'fb_exchange_token' => $this->accessToken,
);
return static::requestAccessToken($params, $appId, $appSecret);
}
/**
* Request an access token based on a set of params.
*
* @param array $params
* @param string|null $appId
* @param string|null $appSecret
*
* @return AccessToken
*
* @throws FacebookRequestException
*/
public static function requestAccessToken(array $params, $appId = null, $appSecret = null)
{
$response = static::request('/oauth/access_token', $params, $appId, $appSecret);
$data = $response->getResponse();
/**
* @TODO fix this malarkey - getResponse() should always return an object
* @see https://github.com/facebook/facebook-php-sdk-v4/issues/36
*/
if (is_array($data)) {
if (isset($data['access_token'])) {
$expiresAt = isset($data['expires']) ? time() + $data['expires'] : 0;
return new static($data['access_token'], $expiresAt);
}
} elseif($data instanceof \stdClass) {
if (isset($data->access_token)) {
$expiresAt = isset($data->expires_in) ? time() + $data->expires_in : 0;
$machineId = isset($data->machine_id) ? (string) $data->machine_id : null;
return new static((string) $data->access_token, $expiresAt, $machineId);
}
}
throw FacebookRequestException::create(
$response->getRawResponse(),
$data,
401
);
}
/**
* Request a code from a long lived access token.
*
* @param array $params
* @param string|null $appId
* @param string|null $appSecret
*
* @return string
*
* @throws FacebookRequestException
*/
public static function requestCode(array $params, $appId = null, $appSecret = null)
{
$response = static::request('/oauth/client_code', $params, $appId, $appSecret);
$data = $response->getResponse();
if (isset($data->code)) {
return (string) $data->code;
}
throw FacebookRequestException::create(
$response->getRawResponse(),
$data,
401
);
}
/**
* Send a request to Graph with an app access token.
*
* @param string $endpoint
* @param array $params
* @param string|null $appId
* @param string|null $appSecret
*
* @return \Facebook\FacebookResponse
*
* @throws FacebookRequestException
*/
protected static function request($endpoint, array $params, $appId = null, $appSecret = null)
{
$targetAppId = FacebookSession::_getTargetAppId($appId);
$targetAppSecret = FacebookSession::_getTargetAppSecret($appSecret);
if (!isset($params['client_id'])) {
$params['client_id'] = $targetAppId;
}
if (!isset($params['client_secret'])) {
$params['client_secret'] = $targetAppSecret;
}
// The response for this endpoint is not JSON, so it must be handled
// differently, not as a GraphObject.
$request = new FacebookRequest(
FacebookSession::newAppSession($targetAppId, $targetAppSecret),
'GET',
$endpoint,
$params
);
return $request->execute();
}
/**
* Get more info about an access token.
*
* @param string|null $appId
* @param string|null $appSecret
*
* @return GraphSessionInfo
*/
public function getInfo($appId = null, $appSecret = null)
{
$params = array('input_token' => $this->accessToken);
$request = new FacebookRequest(
FacebookSession::newAppSession($appId, $appSecret),
'GET',
'/debug_token',
$params
);
$response = $request->execute()->getGraphObject(GraphSessionInfo::className());
// Update the data on this token
if ($response->getExpiresAt()) {
$this->expiresAt = $response->getExpiresAt();
}
return $response;
}
/**
* Returns the access token as a string.
*
* @return string
*/
public function __toString()
{
return $this->accessToken;
}
/**
* Returns true if the access token is an app session token.
*
* @return bool
*/
public function isAppSession()
{
return strpos($this->accessToken, '|') !== false;
}
}

View File

@ -0,0 +1,397 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook\Entities;
use Facebook\FacebookSDKException;
use Facebook\FacebookSession;
/**
* Class SignedRequest
* @package Facebook
*/
class SignedRequest
{
/**
* @var string
*/
public $rawSignedRequest;
/**
* @var array
*/
public $payload;
/**
* Instantiate a new SignedRequest entity.
*
* @param string|null $rawSignedRequest The raw signed request.
* @param string|null $state random string to prevent CSRF.
* @param string|null $appSecret
*/
public function __construct($rawSignedRequest = null, $state = null, $appSecret = null)
{
if (!$rawSignedRequest) {
return;
}
$this->rawSignedRequest = $rawSignedRequest;
$this->payload = static::parse($rawSignedRequest, $state, $appSecret);
}
/**
* Returns the raw signed request data.
*
* @return string|null
*/
public function getRawSignedRequest()
{
return $this->rawSignedRequest;
}
/**
* Returns the parsed signed request data.
*
* @return array|null
*/
public function getPayload()
{
return $this->payload;
}
/**
* Returns a property from the signed request data if available.
*
* @param string $key
* @param mixed|null $default
*
* @return mixed|null
*/
public function get($key, $default = null)
{
if (isset($this->payload[$key])) {
return $this->payload[$key];
}
return $default;
}
/**
* Returns user_id from signed request data if available.
*
* @return string|null
*/
public function getUserId()
{
return $this->get('user_id');
}
/**
* Checks for OAuth data in the payload.
*
* @return boolean
*/
public function hasOAuthData()
{
return isset($this->payload['oauth_token']) || isset($this->payload['code']);
}
/**
* Creates a signed request from an array of data.
*
* @param array $payload
* @param string|null $appSecret
*
* @return string
*/
public static function make(array $payload, $appSecret = null)
{
$payload['algorithm'] = 'HMAC-SHA256';
$payload['issued_at'] = time();
$encodedPayload = static::base64UrlEncode(json_encode($payload));
$hashedSig = static::hashSignature($encodedPayload, $appSecret);
$encodedSig = static::base64UrlEncode($hashedSig);
return $encodedSig.'.'.$encodedPayload;
}
/**
* Validates and decodes a signed request and returns
* the payload as an array.
*
* @param string $signedRequest
* @param string|null $state
* @param string|null $appSecret
*
* @return array
*/
public static function parse($signedRequest, $state = null, $appSecret = null)
{
list($encodedSig, $encodedPayload) = static::split($signedRequest);
// Signature validation
$sig = static::decodeSignature($encodedSig);
$hashedSig = static::hashSignature($encodedPayload, $appSecret);
static::validateSignature($hashedSig, $sig);
// Payload validation
$data = static::decodePayload($encodedPayload);
static::validateAlgorithm($data);
if ($state) {
static::validateCsrf($data, $state);
}
return $data;
}
/**
* Validates the format of a signed request.
*
* @param string $signedRequest
*
* @throws FacebookSDKException
*/
public static function validateFormat($signedRequest)
{
if (strpos($signedRequest, '.') !== false) {
return;
}
throw new FacebookSDKException(
'Malformed signed request.', 606
);
}
/**
* Decodes a raw valid signed request.
*
* @param string $signedRequest
*
* @returns array
*/
public static function split($signedRequest)
{
static::validateFormat($signedRequest);
return explode('.', $signedRequest, 2);
}
/**
* Decodes the raw signature from a signed request.
*
* @param string $encodedSig
*
* @returns string
*
* @throws FacebookSDKException
*/
public static function decodeSignature($encodedSig)
{
$sig = static::base64UrlDecode($encodedSig);
if ($sig) {
return $sig;
}
throw new FacebookSDKException(
'Signed request has malformed encoded signature data.', 607
);
}
/**
* Decodes the raw payload from a signed request.
*
* @param string $encodedPayload
*
* @returns array
*
* @throws FacebookSDKException
*/
public static function decodePayload($encodedPayload)
{
$payload = static::base64UrlDecode($encodedPayload);
if ($payload) {
$payload = json_decode($payload, true);
}
if (is_array($payload)) {
return $payload;
}
throw new FacebookSDKException(
'Signed request has malformed encoded payload data.', 607
);
}
/**
* Validates the algorithm used in a signed request.
*
* @param array $data
*
* @throws FacebookSDKException
*/
public static function validateAlgorithm(array $data)
{
if (isset($data['algorithm']) && $data['algorithm'] === 'HMAC-SHA256') {
return;
}
throw new FacebookSDKException(
'Signed request is using the wrong algorithm.', 605
);
}
/**
* Hashes the signature used in a signed request.
*
* @param string $encodedData
* @param string|null $appSecret
*
* @return string
*
* @throws FacebookSDKException
*/
public static function hashSignature($encodedData, $appSecret = null)
{
$hashedSig = hash_hmac(
'sha256', $encodedData, FacebookSession::_getTargetAppSecret($appSecret), $raw_output = true
);
if ($hashedSig) {
return $hashedSig;
}
throw new FacebookSDKException(
'Unable to hash signature from encoded payload data.', 602
);
}
/**
* Validates the signature used in a signed request.
*
* @param string $hashedSig
* @param string $sig
*
* @throws FacebookSDKException
*/
public static function validateSignature($hashedSig, $sig)
{
$intSignatureLength = mb_strlen($sig);
if (mb_strlen($hashedSig) === $intSignatureLength) {
$validate = 0;
for ($i = 0; $i < $intSignatureLength; $i++) {
$validate |= ord($hashedSig[$i]) ^ ord($sig[$i]);
}
if ($validate === 0) {
return;
}
}
throw new FacebookSDKException(
'Signed request has an invalid signature.', 602
);
}
/**
* Validates a signed request against CSRF.
*
* @param array $data
* @param string $state
*
* @throws FacebookSDKException
*/
public static function validateCsrf(array $data, $state)
{
if (isset($data['state'])) {
$savedLen = strlen($state);
$givenLen = strlen($data['state']);
if ($savedLen == $givenLen) {
$result = 0;
for ($i = 0; $i < $savedLen; $i++) {
$result |= ord($state[$i]) ^ ord($data['state'][$i]);
}
if ($result === 0) {
return;
}
}
}
throw new FacebookSDKException(
'Signed request did not pass CSRF validation.', 604
);
}
/**
* Base64 decoding which replaces characters:
* + instead of -
* / instead of _
* @link http://en.wikipedia.org/wiki/Base64#URL_applications
*
* @param string $input base64 url encoded input
*
* @return string decoded string
*/
public static function base64UrlDecode($input)
{
$urlDecodedBase64 = strtr($input, '-_', '+/');
static::validateBase64($urlDecodedBase64);
return base64_decode($urlDecodedBase64);
}
/**
* Base64 encoding which replaces characters:
* + instead of -
* / instead of _
* @link http://en.wikipedia.org/wiki/Base64#URL_applications
*
* @param string $input string to encode
*
* @return string base64 url encoded input
*/
public static function base64UrlEncode($input)
{
return strtr(base64_encode($input), '+/', '-_');
}
/**
* Validates a base64 string.
*
* @param string $input base64 value to validate
*
* @throws FacebookSDKException
*/
public static function validateBase64($input)
{
$pattern = '/^[a-zA-Z0-9\/\r\n+]*={0,2}$/';
if (preg_match($pattern, $input)) {
return;
}
throw new FacebookSDKException(
'Signed request contains malformed base64 encoding.', 608
);
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookAuthorizationException
* @package Facebook
*/
class FacebookAuthorizationException extends FacebookRequestException
{
}

View File

@ -0,0 +1,60 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookCanvasLoginHelper
* @package Facebook
* @author Fosco Marotto <fjm@fb.com>
* @author David Poll <depoll@fb.com>
*/
class FacebookCanvasLoginHelper extends FacebookSignedRequestFromInputHelper
{
/**
* Returns the app data value.
*
* @return mixed|null
*/
public function getAppData()
{
return $this->signedRequest ? $this->signedRequest->get('app_data') : null;
}
/**
* Get raw signed request from POST.
*
* @return string|null
*/
public function getRawSignedRequest()
{
$rawSignedRequest = $this->getRawSignedRequestFromPost();
if ($rawSignedRequest) {
return $rawSignedRequest;
}
return null;
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookClientException
* @package Facebook
*/
class FacebookClientException extends FacebookRequestException
{
}

View File

@ -0,0 +1,45 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookJavaScriptLoginHelper
* @package Facebook
* @author Fosco Marotto <fjm@fb.com>
* @author David Poll <depoll@fb.com>
*/
class FacebookJavaScriptLoginHelper extends FacebookSignedRequestFromInputHelper
{
/**
* Get raw signed request from the cookie.
*
* @return string|null
*/
public function getRawSignedRequest()
{
return $this->getRawSignedRequestFromCookie();
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookOtherException
* @package Facebook
*/
class FacebookOtherException extends FacebookRequestException
{
}

View File

@ -0,0 +1,102 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookPageTabHelper
* @package Facebook
* @author Fosco Marotto <fjm@fb.com>
*/
class FacebookPageTabHelper extends FacebookCanvasLoginHelper
{
/**
* @var array|null
*/
protected $pageData;
/**
* Initialize the helper and process available signed request data.
*
* @param string|null $appId
* @param string|null $appSecret
*/
public function __construct($appId = null, $appSecret = null)
{
parent::__construct($appId, $appSecret);
if (!$this->signedRequest) {
return;
}
$this->pageData = $this->signedRequest->get('page');
}
/**
* Returns a value from the page data.
*
* @param string $key
* @param mixed|null $default
*
* @return mixed|null
*/
public function getPageData($key, $default = null)
{
if (isset($this->pageData[$key])) {
return $this->pageData[$key];
}
return $default;
}
/**
* Returns true if the page is liked by the user.
*
* @return boolean
*/
public function isLiked()
{
return $this->getPageData('liked') === true;
}
/**
* Returns true if the user is an admin.
*
* @return boolean
*/
public function isAdmin()
{
return $this->getPageData('admin') === true;
}
/**
* Returns the page id if available.
*
* @return string|null
*/
public function getPageId()
{
return $this->getPageData('id');
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookPermissionException
* @package Facebook
*/
class FacebookPermissionException extends FacebookRequestException
{
}

View File

@ -0,0 +1,74 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookPermissions
* @package Facebook
*/
final class FacebookPermissions
{
// Basic permissions
const PUBLIC_PROFILE = 'public_profile';
const USER_FRIENDS = 'user_friends';
const EMAIL = 'email';
// Extended Profile Properties
const USER_ABOUT_ME = 'user_about_me';
const USER_ACTIONS_BOOKS = 'user_actions.books';
const USER_ACTIONS_FITNESS = 'user_actions.fitness';
const USER_ACTIONS_MUSIC = 'user_actions.music';
const USER_ACTIONS_NEWS = 'user_actions.news';
const USER_ACTIONS_VIDEO = 'user_actions.video';
const USER_ACTIVITIES = 'user_activities';
const USER_BIRTHDAY = 'user_birthday';
const USER_EDUCATION_HISTORY = 'user_education_history';
const USER_EVENTS = 'user_events';
const USER_GAMES_ACTIVITY = 'user_games_activity';
const USER_GROUPS = 'user_groups';
const USER_HOMETOWN = 'user_hometown';
const USER_INTERESTS = 'user_interests';
const USER_LIKES = 'user_likes';
const USER_LOCATION = 'user_location';
const USER_PHOTOS = 'user_photos';
const USER_POSTS = 'user_posts';
const USER_RELATIONSHIPS = 'user_relationships';
const USER_RELATIONSHIP_DETAILS = 'user_relationship_details';
const USER_RELIGION_POLITICS = 'user_religion_politics';
const USER_STATUS = 'user_status';
const USER_TAGGED_PLACES = 'user_tagged_places';
const USER_VIDEOS = 'user_videos';
const USER_WEBSITE = 'user_website';
const USER_WORK_HISTORY = 'user_work_history';
// Extended Permissions
const READ_FRIENDLISTS = 'read_friendlists';
const READ_INSIGHTS = 'read_insights';
const READ_MAILBOX = 'read_mailbox';
const READ_PAGE_MAILBOXES = 'read_page_mailboxes';
const READ_STREAM = 'read_stream';
const MANAGE_NOTIFICATIONS = 'manage_notifications';
const MANAGE_PAGES = 'manage_pages';
const PUBLISH_PAGES = 'publish_pages';
const PUBLISH_ACTIONS = 'publish_actions';
const RSVP_EVENT = 'rsvp_event';
}

View File

@ -0,0 +1,344 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookRedirectLoginHelper
* @package Facebook
* @author Fosco Marotto <fjm@fb.com>
* @author David Poll <depoll@fb.com>
*/
class FacebookRedirectLoginHelper
{
/**
* @var string The application id
*/
private $appId;
/**
* @var string The application secret
*/
private $appSecret;
/**
* @var string The redirect URL for the application
*/
private $redirectUrl;
/**
* @var string Prefix to use for session variables
*/
private $sessionPrefix = 'FBRLH_';
/**
* @var string State token for CSRF validation
*/
protected $state;
/**
* @var boolean Toggle for PHP session status check
*/
protected $checkForSessionStatus = true;
/**
* Constructs a RedirectLoginHelper for a given appId and redirectUrl.
*
* @param string $redirectUrl The URL Facebook should redirect users to
* after login
* @param string $appId The application id
* @param string $appSecret The application secret
*/
public function __construct($redirectUrl, $appId = null, $appSecret = null)
{
$this->appId = FacebookSession::_getTargetAppId($appId);
$this->appSecret = FacebookSession::_getTargetAppSecret($appSecret);
$this->redirectUrl = $redirectUrl;
}
/**
* Stores CSRF state and returns a URL to which the user should be sent to
* in order to continue the login process with Facebook. The
* provided redirectUrl should invoke the handleRedirect method.
*
* @param array $scope List of permissions to request during login
* @param string $version Optional Graph API version if not default (v2.0)
* @param boolean $displayAsPopup Indicate if the page will be displayed as a popup
* @param bool|string $authType 'reauthenticate' or 'https', true is equivalent to 'reauthenticate',
* false or invalid value will not add auth type parameter
*
* @return string
*/
public function getLoginUrl(array $scope = array(), $version = null, $displayAsPopup = false, $authType = false)
{
$version = ($version ?: FacebookRequest::GRAPH_API_VERSION);
$this->state = $this->random(16);
$this->storeState($this->state);
$params = array(
'client_id' => $this->appId,
'redirect_uri' => $this->redirectUrl,
'state' => $this->state,
'sdk' => 'php-sdk-' . FacebookRequest::VERSION,
'scope' => implode(',', $scope)
);
if (in_array($authType, array(true, 'reauthenticate', 'https'), true)) {
$params['auth_type'] = $authType === true ? 'reauthenticate' : $authType;
}
if ($displayAsPopup)
{
$params['display'] = 'popup';
}
return 'https://www.facebook.com/' . $version . '/dialog/oauth?' .
http_build_query($params, null, '&');
}
/**
* Returns a URL to which the user should be sent to re-request permissions.
*
* @param array $scope List of permissions to re-request
* @param string $version Optional Graph API version if not default (v2.0)
*
* @return string
*/
public function getReRequestUrl(array $scope = array(), $version = null)
{
$version = ($version ?: FacebookRequest::GRAPH_API_VERSION);
$this->state = $this->random(16);
$this->storeState($this->state);
$params = array(
'client_id' => $this->appId,
'redirect_uri' => $this->redirectUrl,
'state' => $this->state,
'sdk' => 'php-sdk-' . FacebookRequest::VERSION,
'auth_type' => 'rerequest',
'scope' => implode(',', $scope)
);
return 'https://www.facebook.com/' . $version . '/dialog/oauth?' .
http_build_query($params, null, '&');
}
/**
* Returns the URL to send the user in order to log out of Facebook.
*
* @param FacebookSession $session The session that will be logged out
* @param string $next The url Facebook should redirect the user to after
* a successful logout
*
* @return string
*
* @throws FacebookSDKException
*/
public function getLogoutUrl(FacebookSession $session, $next)
{
if ($session->getAccessToken()->isAppSession()) {
throw new FacebookSDKException(
'Cannot generate a Logout URL with an App Session.', 722
);
}
$params = array(
'next' => $next,
'access_token' => $session->getToken()
);
return 'https://www.facebook.com/logout.php?' . http_build_query($params, null, '&');
}
/**
* Handles a response from Facebook, including a CSRF check, and returns a
* FacebookSession.
*
* @return FacebookSession|null
*/
public function getSessionFromRedirect()
{
if ($this->isValidRedirect()) {
$params = array(
'client_id' => FacebookSession::_getTargetAppId($this->appId),
'redirect_uri' => $this->redirectUrl,
'client_secret' =>
FacebookSession::_getTargetAppSecret($this->appSecret),
'code' => $this->getCode()
);
$response = (new FacebookRequest(
FacebookSession::newAppSession($this->appId, $this->appSecret),
'GET',
'/oauth/access_token',
$params
))->execute()->getResponse();
// Graph v2.3 and greater return objects on the /oauth/access_token endpoint
$accessToken = null;
if (is_object($response) && isset($response->access_token)) {
$accessToken = $response->access_token;
} elseif (is_array($response) && isset($response['access_token'])) {
$accessToken = $response['access_token'];
}
if (isset($accessToken)) {
return new FacebookSession($accessToken);
}
}
return null;
}
/**
* Check if a redirect has a valid state.
*
* @return bool
*/
protected function isValidRedirect()
{
$savedState = $this->loadState();
if (!$this->getCode() || !isset($_GET['state'])) {
return false;
}
$givenState = $_GET['state'];
$savedLen = mb_strlen($savedState);
$givenLen = mb_strlen($givenState);
if ($savedLen !== $givenLen) {
return false;
}
$result = 0;
for ($i = 0; $i < $savedLen; $i++) {
$result |= ord($savedState[$i]) ^ ord($givenState[$i]);
}
return $result === 0;
}
/**
* Return the code.
*
* @return string|null
*/
protected function getCode()
{
return isset($_GET['code']) ? $_GET['code'] : null;
}
/**
* Stores a state string in session storage for CSRF protection.
* Developers should subclass and override this method if they want to store
* this state in a different location.
*
* @param string $state
*
* @throws FacebookSDKException
*/
protected function storeState($state)
{
if ($this->checkForSessionStatus === true
&& session_status() !== PHP_SESSION_ACTIVE) {
throw new FacebookSDKException(
'Session not active, could not store state.', 720
);
}
$_SESSION[$this->sessionPrefix . 'state'] = $state;
}
/**
* Loads a state string from session storage for CSRF validation. May return
* null if no object exists. Developers should subclass and override this
* method if they want to load the state from a different location.
*
* @return string|null
*
* @throws FacebookSDKException
*/
protected function loadState()
{
if ($this->checkForSessionStatus === true
&& session_status() !== PHP_SESSION_ACTIVE) {
throw new FacebookSDKException(
'Session not active, could not load state.', 721
);
}
if (isset($_SESSION[$this->sessionPrefix . 'state'])) {
$this->state = $_SESSION[$this->sessionPrefix . 'state'];
return $this->state;
}
return null;
}
/**
* Generate a cryptographically secure pseudrandom number
*
* @param integer $bytes - number of bytes to return
*
* @return string
*
* @throws FacebookSDKException
*
* @todo Support Windows platforms
*/
public function random($bytes)
{
if (!is_numeric($bytes)) {
throw new FacebookSDKException(
'random() expects an integer'
);
}
if ($bytes < 1) {
throw new FacebookSDKException(
'random() expects an integer greater than zero'
);
}
$buf = '';
// http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/
if (!ini_get('open_basedir')
&& is_readable('/dev/urandom')) {
$fp = fopen('/dev/urandom', 'rb');
if ($fp !== FALSE) {
$buf = fread($fp, $bytes);
fclose($fp);
if($buf !== FALSE) {
return bin2hex($buf);
}
}
}
if (function_exists('mcrypt_create_iv')) {
$buf = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
if ($buf !== FALSE) {
return bin2hex($buf);
}
}
while (strlen($buf) < $bytes) {
$buf .= md5(uniqid(mt_rand(), true), true);
// We are appending raw binary
}
return bin2hex(substr($buf, 0, $bytes));
}
/**
* Disables the session_status() check when using $_SESSION
*/
public function disableSessionStatusCheck()
{
$this->checkForSessionStatus = false;
}
}

View File

@ -0,0 +1,325 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
use Facebook\HttpClients\FacebookHttpable;
use Facebook\HttpClients\FacebookCurlHttpClient;
use Facebook\HttpClients\FacebookStreamHttpClient;
/**
* Class FacebookRequest
* @package Facebook
* @author Fosco Marotto <fjm@fb.com>
* @author David Poll <depoll@fb.com>
*/
class FacebookRequest
{
/**
* @const string Version number of the Facebook PHP SDK.
*/
const VERSION = '4.0.23';
/**
* @const string Default Graph API version for requests
*/
const GRAPH_API_VERSION = 'v2.3';
/**
* @const string Graph API URL
*/
const BASE_GRAPH_URL = 'https://graph.facebook.com';
/**
* @const string Graph API URL
*/
const BASE_VIDEO_GRAPH_URL = 'https://graph-video.facebook.com';
/**
* @var FacebookSession The session used for this request
*/
private $session;
/**
* @var string The HTTP method for the request
*/
private $method;
/**
* @var string The path for the request
*/
private $path;
/**
* @var array The parameters for the request
*/
private $params;
/**
* @var string The Graph API version for the request
*/
private $version;
/**
* @var string ETag sent with the request
*/
private $etag;
/**
* @var FacebookHttpable HTTP client handler
*/
private static $httpClientHandler;
/**
* @var int The number of calls that have been made to Graph.
*/
public static $requestCount = 0;
/**
* getSession - Returns the associated FacebookSession.
*
* @return FacebookSession
*/
public function getSession()
{
return $this->session;
}
/**
* getPath - Returns the associated path.
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* getParameters - Returns the associated parameters.
*
* @return array
*/
public function getParameters()
{
return $this->params;
}
/**
* getMethod - Returns the associated method.
*
* @return string
*/
public function getMethod()
{
return $this->method;
}
/**
* getETag - Returns the ETag sent with the request.
*
* @return string
*/
public function getETag()
{
return $this->etag;
}
/**
* setHttpClientHandler - Returns an instance of the HTTP client
* handler
*
* @param \Facebook\HttpClients\FacebookHttpable
*/
public static function setHttpClientHandler(FacebookHttpable $handler)
{
static::$httpClientHandler = $handler;
}
/**
* getHttpClientHandler - Returns an instance of the HTTP client
* data handler
*
* @return FacebookHttpable
*/
public static function getHttpClientHandler()
{
if (static::$httpClientHandler) {
return static::$httpClientHandler;
}
return function_exists('curl_init') ? new FacebookCurlHttpClient() : new FacebookStreamHttpClient();
}
/**
* FacebookRequest - Returns a new request using the given session. optional
* parameters hash will be sent with the request. This object is
* immutable.
*
* @param FacebookSession $session
* @param string $method
* @param string $path
* @param array|null $parameters
* @param string|null $version
* @param string|null $etag
*/
public function __construct(
FacebookSession $session, $method, $path, $parameters = null, $version = null, $etag = null
)
{
$this->session = $session;
$this->method = $method;
$this->path = $path;
if ($version) {
$this->version = $version;
} else {
$this->version = static::GRAPH_API_VERSION;
}
$this->etag = $etag;
$params = ($parameters ?: array());
if ($session
&& ! isset($params['access_token'])) {
$params['access_token'] = $session->getToken();
}
if (! isset($params['appsecret_proof'])
&& FacebookSession::useAppSecretProof()) {
$params['appsecret_proof'] = $this->getAppSecretProof(
$params['access_token']
);
}
$this->params = $params;
}
/**
* Returns the base Graph URL.
*
* @return string
*/
protected function getRequestURL()
{
$pathElements = explode('/', $this->path);
$lastInPath = end($pathElements);
if ($lastInPath == 'videos' && $this->method === 'POST') {
$baseUrl = static::BASE_VIDEO_GRAPH_URL;
} else {
$baseUrl = static::BASE_GRAPH_URL;
}
return $baseUrl . '/' . $this->version . $this->path;
}
/**
* execute - Makes the request to Facebook and returns the result.
*
* @return FacebookResponse
*
* @throws FacebookSDKException
* @throws FacebookRequestException
*/
public function execute()
{
$url = $this->getRequestURL();
$params = $this->getParameters();
if ($this->method === 'GET') {
$url = self::appendParamsToUrl($url, $params);
$params = array();
}
$connection = self::getHttpClientHandler();
$connection->addRequestHeader('User-Agent', 'fb-php-' . self::VERSION);
$connection->addRequestHeader('Accept-Encoding', '*'); // Support all available encodings.
// ETag
if (null !== $this->etag) {
$connection->addRequestHeader('If-None-Match', $this->etag);
}
// Should throw `FacebookSDKException` exception on HTTP client error.
// Don't catch to allow it to bubble up.
$result = $connection->send($url, $this->method, $params);
static::$requestCount++;
$etagHit = 304 === $connection->getResponseHttpStatusCode();
$headers = $connection->getResponseHeaders();
$etagReceived = isset($headers['ETag']) ? $headers['ETag'] : null;
$decodedResult = json_decode($result);
if ($decodedResult === null) {
$out = array();
parse_str($result, $out);
return new FacebookResponse($this, $out, $result, $etagHit, $etagReceived);
}
if (isset($decodedResult->error)) {
throw FacebookRequestException::create(
$result,
$decodedResult->error,
$connection->getResponseHttpStatusCode()
);
}
return new FacebookResponse($this, $decodedResult, $result, $etagHit, $etagReceived);
}
/**
* Generate and return the appsecret_proof value for an access_token
*
* @param string $token
*
* @return string
*/
public function getAppSecretProof($token)
{
return hash_hmac('sha256', $token, FacebookSession::_getTargetAppSecret());
}
/**
* appendParamsToUrl - Gracefully appends params to the URL.
*
* @param string $url
* @param array $params
*
* @return string
*/
public static function appendParamsToUrl($url, array $params = array())
{
if (!$params) {
return $url;
}
if (strpos($url, '?') === false) {
return $url . '?' . http_build_query($params, null, '&');
}
list($path, $query_string) = explode('?', $url, 2);
parse_str($query_string, $query_array);
// Favor params from the original URL over $params
$params = array_merge($params, $query_array);
return $path . '?' . http_build_query($params, null, '&');
}
}

View File

@ -0,0 +1,222 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookRequestException
* @package Facebook
* @author Fosco Marotto <fjm@fb.com>
* @author David Poll <depoll@fb.com>
*/
class FacebookRequestException extends FacebookSDKException
{
/**
* @var int Status code for the response causing the exception
*/
private $statusCode;
/**
* @var string Raw response
*/
private $rawResponse;
/**
* @var array Decoded response
*/
private $responseData;
/**
* Creates a FacebookRequestException.
*
* @param string $rawResponse The raw response from the Graph API
* @param array $responseData The decoded response from the Graph API
* @param int $statusCode
*/
public function __construct($rawResponse, $responseData, $statusCode)
{
$this->rawResponse = $rawResponse;
$this->statusCode = $statusCode;
$this->responseData = self::convertToArray($responseData);
parent::__construct(
$this->get('message', 'Unknown Exception'), $this->get('code', -1), null
);
}
/**
* Process an error payload from the Graph API and return the appropriate
* exception subclass.
*
* @param string $raw the raw response from the Graph API
* @param array $data the decoded response from the Graph API
* @param int $statusCode the HTTP response code
*
* @return FacebookRequestException
*/
public static function create($raw, $data, $statusCode)
{
$data = self::convertToArray($data);
if (!isset($data['error']['code']) && isset($data['code'])) {
$data = array('error' => $data);
}
$code = (isset($data['error']['code']) ? (int) $data['error']['code'] : null);
if (isset($data['error']['error_subcode'])) {
switch ($data['error']['error_subcode']) {
// Other authentication issues
case 458:
case 459:
case 460:
case 463:
case 464:
case 467:
return new FacebookAuthorizationException($raw, $data, $statusCode);
break;
}
}
switch ($code) {
// Login status or token expired, revoked, or invalid
case 100:
case 102:
case 190:
return new FacebookAuthorizationException($raw, $data, $statusCode);
break;
// Server issue, possible downtime
case 1:
case 2:
return new FacebookServerException($raw, $data, $statusCode);
break;
// API Throttling
case 4:
case 17:
case 341:
return new FacebookThrottleException($raw, $data, $statusCode);
break;
// Duplicate Post
case 506:
return new FacebookClientException($raw, $data, $statusCode);
break;
}
// Missing Permissions
if ($code === 10 || ($code >= 200 && $code <= 299)) {
return new FacebookPermissionException($raw, $data, $statusCode);
}
// OAuth authentication error
if (isset($data['error']['type'])
and $data['error']['type'] === 'OAuthException') {
return new FacebookAuthorizationException($raw, $data, $statusCode);
}
// All others
return new FacebookOtherException($raw, $data, $statusCode);
}
/**
* Checks isset and returns that or a default value.
*
* @param string $key
* @param mixed $default
*
* @return mixed
*/
private function get($key, $default = null)
{
if (isset($this->responseData['error'][$key])) {
return $this->responseData['error'][$key];
}
return $default;
}
/**
* Returns the HTTP status code
*
* @return int
*/
public function getHttpStatusCode()
{
return $this->statusCode;
}
/**
* Returns the sub-error code
*
* @return int
*/
public function getSubErrorCode()
{
return $this->get('error_subcode', -1);
}
/**
* Returns the error type
*
* @return string
*/
public function getErrorType()
{
return $this->get('type', '');
}
/**
* Returns the raw response used to create the exception.
*
* @return string
*/
public function getRawResponse()
{
return $this->rawResponse;
}
/**
* Returns the decoded response used to create the exception.
*
* @return array
*/
public function getResponse()
{
return $this->responseData;
}
/**
* Converts a stdClass object to an array
*
* @param mixed $object
*
* @return array
*/
private static function convertToArray($object)
{
if ($object instanceof \stdClass) {
return get_object_vars($object);
}
return $object;
}
}

View File

@ -0,0 +1,206 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookResponse
* @package Facebook
* @author Fosco Marotto <fjm@fb.com>
* @author David Poll <depoll@fb.com>
*/
class FacebookResponse
{
/**
* @var FacebookRequest The request which produced this response
*/
private $request;
/**
* @var array The decoded response from the Graph API
*/
private $responseData;
/**
* @var string The raw response from the Graph API
*/
private $rawResponse;
/**
* @var bool Indicates whether sent ETag matched the one on the FB side
*/
private $etagHit;
/**
* @var string ETag received with the response. `null` in case of ETag hit.
*/
private $etag;
/**
* Creates a FacebookResponse object for a given request and response.
*
* @param FacebookRequest $request
* @param array $responseData JSON Decoded response data
* @param string $rawResponse Raw string response
* @param bool $etagHit Indicates whether sent ETag matched the one on the FB side
* @param string|null $etag ETag received with the response. `null` in case of ETag hit.
*/
public function __construct($request, $responseData, $rawResponse, $etagHit = false, $etag = null)
{
$this->request = $request;
$this->responseData = $responseData;
$this->rawResponse = $rawResponse;
$this->etagHit = $etagHit;
$this->etag = $etag;
}
/**
* Returns the request which produced this response.
*
* @return FacebookRequest
*/
public function getRequest()
{
return $this->request;
}
/**
* Returns the decoded response data.
*
* @return array
*/
public function getResponse()
{
return $this->responseData;
}
/**
* Returns the raw response
*
* @return string
*/
public function getRawResponse()
{
return $this->rawResponse;
}
/**
* Returns true if ETag matched the one sent with a request
*
* @return bool
*/
public function isETagHit()
{
return $this->etagHit;
}
/**
* Returns the ETag
*
* @return string
*/
public function getETag()
{
return $this->etag;
}
/**
* Gets the result as a GraphObject. If a type is specified, returns the
* strongly-typed subclass of GraphObject for the data.
*
* @param string $type
*
* @return mixed
*/
public function getGraphObject($type = 'Facebook\GraphObject') {
return (new GraphObject($this->responseData))->cast($type);
}
/**
* Returns an array of GraphObject returned by the request. If a type is
* specified, returns the strongly-typed subclass of GraphObject for the data.
*
* @param string $type
*
* @return mixed
*/
public function getGraphObjectList($type = 'Facebook\GraphObject') {
$out = array();
$data = $this->responseData->data;
$dataLength = count($data);
for ($i = 0; $i < $dataLength; $i++) {
$out[] = (new GraphObject($data[$i]))->cast($type);
}
return $out;
}
/**
* If this response has paginated data, returns the FacebookRequest for the
* next page, or null.
*
* @return FacebookRequest|null
*/
public function getRequestForNextPage()
{
return $this->handlePagination('next');
}
/**
* If this response has paginated data, returns the FacebookRequest for the
* previous page, or null.
*
* @return FacebookRequest|null
*/
public function getRequestForPreviousPage()
{
return $this->handlePagination('previous');
}
/**
* Returns the FacebookRequest for the previous or next page, or null.
*
* @param string $direction
*
* @return FacebookRequest|null
*/
private function handlePagination($direction) {
if (isset($this->responseData->paging->$direction)) {
$url = parse_url($this->responseData->paging->$direction);
parse_str($url['query'], $params);
if (isset($params['type']) && strpos($this->request->getPath(), $params['type']) !== false){
unset($params['type']);
}
return new FacebookRequest(
$this->request->getSession(),
$this->request->getMethod(),
$this->request->getPath(),
$params
);
} else {
return null;
}
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookSDKException
* @package Facebook
*/
class FacebookSDKException extends \Exception
{
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookServerException
* @package Facebook
*/
class FacebookServerException extends FacebookRequestException
{
}

View File

@ -0,0 +1,367 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
use Facebook\Entities\AccessToken;
use Facebook\Entities\SignedRequest;
/**
* Class FacebookSession
* @package Facebook
* @author Fosco Marotto <fjm@fb.com>
* @author David Poll <depoll@fb.com>
*/
class FacebookSession
{
/**
* @var string
*/
private static $defaultAppId;
/**
* @var string
*/
private static $defaultAppSecret;
/**
* @var AccessToken The AccessToken entity for this connection.
*/
private $accessToken;
/**
* @var SignedRequest
*/
private $signedRequest;
/**
* @var bool
*/
protected static $useAppSecretProof = true;
/**
* When creating a Session from an access_token, use:
* var $session = new FacebookSession($accessToken);
* This will validate the token and provide a Session object ready for use.
* It will throw a SessionException in case of error.
*
* @param AccessToken|string $accessToken
* @param SignedRequest $signedRequest The SignedRequest entity
*/
public function __construct($accessToken, SignedRequest $signedRequest = null)
{
$this->accessToken = $accessToken instanceof AccessToken ? $accessToken : new AccessToken($accessToken);
$this->signedRequest = $signedRequest;
}
/**
* Returns the access token.
*
* @return string
*/
public function getToken()
{
return (string) $this->accessToken;
}
/**
* Returns the access token entity.
*
* @return AccessToken
*/
public function getAccessToken()
{
return $this->accessToken;
}
/**
* Returns the SignedRequest entity.
*
* @return SignedRequest
*/
public function getSignedRequest()
{
return $this->signedRequest;
}
/**
* Returns the signed request payload.
*
* @return null|array
*/
public function getSignedRequestData()
{
return $this->signedRequest ? $this->signedRequest->getPayload() : null;
}
/**
* Returns a property from the signed request data if available.
*
* @param string $key
*
* @return null|mixed
*/
public function getSignedRequestProperty($key)
{
return $this->signedRequest ? $this->signedRequest->get($key) : null;
}
/**
* Returns user_id from signed request data if available.
*
* @return null|string
*/
public function getUserId()
{
return $this->signedRequest ? $this->signedRequest->getUserId() : null;
}
// @TODO Remove getSessionInfo() in 4.1: can be accessed from AccessToken directly
/**
* getSessionInfo - Makes a request to /debug_token with the appropriate
* arguments to get debug information about the sessions token.
*
* @param string|null $appId
* @param string|null $appSecret
*
* @return GraphSessionInfo
*/
public function getSessionInfo($appId = null, $appSecret = null)
{
return $this->accessToken->getInfo($appId, $appSecret);
}
// @TODO Remove getLongLivedSession() in 4.1: can be accessed from AccessToken directly
/**
* getLongLivedSession - Returns a new Facebook session resulting from
* extending a short-lived access token. If this session is not
* short-lived, returns $this.
*
* @param string|null $appId
* @param string|null $appSecret
*
* @return FacebookSession
*/
public function getLongLivedSession($appId = null, $appSecret = null)
{
$longLivedAccessToken = $this->accessToken->extend($appId, $appSecret);
return new static($longLivedAccessToken, $this->signedRequest);
}
// @TODO Remove getExchangeToken() in 4.1: can be accessed from AccessToken directly
/**
* getExchangeToken - Returns an exchange token string which can be sent
* back to clients and exchanged for a device-linked access token.
*
* @param string|null $appId
* @param string|null $appSecret
*
* @return string
*/
public function getExchangeToken($appId = null, $appSecret = null)
{
return AccessToken::getCodeFromAccessToken($this->accessToken, $appId, $appSecret);
}
// @TODO Remove validate() in 4.1: can be accessed from AccessToken directly
/**
* validate - Ensures the current session is valid, throwing an exception if
* not. Fetches token info from Facebook.
*
* @param string|null $appId Application ID to use
* @param string|null $appSecret App secret value to use
* @param string|null $machineId
*
* @return boolean
*
* @throws FacebookSDKException
*/
public function validate($appId = null, $appSecret = null, $machineId = null)
{
if ($this->accessToken->isValid($appId, $appSecret, $machineId)) {
return true;
}
// @TODO For v4.1 this should not throw an exception, but just return false.
throw new FacebookSDKException(
'Session has expired, or is not valid for this app.', 601
);
}
// @TODO Remove validateSessionInfo() in 4.1: can be accessed from AccessToken directly
/**
* validateTokenInfo - Ensures the provided GraphSessionInfo object is valid,
* throwing an exception if not. Ensures the appId matches,
* that the token is valid and has not expired.
*
* @param GraphSessionInfo $tokenInfo
* @param string|null $appId Application ID to use
* @param string|null $machineId
*
* @return boolean
*
* @throws FacebookSDKException
*/
public static function validateSessionInfo(GraphSessionInfo $tokenInfo,
$appId = null,
$machineId = null)
{
if (AccessToken::validateAccessToken($tokenInfo, $appId, $machineId)) {
return true;
}
// @TODO For v4.1 this should not throw an exception, but just return false.
throw new FacebookSDKException(
'Session has expired, or is not valid for this app.', 601
);
}
/**
* newSessionFromSignedRequest - Returns a FacebookSession for a
* given signed request.
*
* @param SignedRequest $signedRequest
*
* @return FacebookSession
*/
public static function newSessionFromSignedRequest(SignedRequest $signedRequest)
{
if ($signedRequest->get('code')
&& !$signedRequest->get('oauth_token')) {
return self::newSessionAfterValidation($signedRequest);
}
$accessToken = $signedRequest->get('oauth_token');
$expiresAt = $signedRequest->get('expires', 0);
$accessToken = new AccessToken($accessToken, $expiresAt);
return new static($accessToken, $signedRequest);
}
/**
* newSessionAfterValidation - Returns a FacebookSession for a
* validated & parsed signed request.
*
* @param SignedRequest $signedRequest
*
* @return FacebookSession
*/
protected static function newSessionAfterValidation(SignedRequest $signedRequest)
{
$code = $signedRequest->get('code');
$accessToken = AccessToken::getAccessTokenFromCode($code);
return new static($accessToken, $signedRequest);
}
/**
* newAppSession - Returns a FacebookSession configured with a token for the
* application which can be used for publishing and requesting app-level
* information.
*
* @param string|null $appId Application ID to use
* @param string|null $appSecret App secret value to use
*
* @return FacebookSession
*/
public static function newAppSession($appId = null, $appSecret = null)
{
$targetAppId = static::_getTargetAppId($appId);
$targetAppSecret = static::_getTargetAppSecret($appSecret);
return new FacebookSession(
$targetAppId . '|' . $targetAppSecret
);
}
/**
* setDefaultApplication - Will set the static default appId and appSecret
* to be used for API requests.
*
* @param string $appId Application ID to use by default
* @param string $appSecret App secret value to use by default
*/
public static function setDefaultApplication($appId, $appSecret)
{
self::$defaultAppId = $appId;
self::$defaultAppSecret = $appSecret;
}
/**
* _getTargetAppId - Will return either the provided app Id or the default,
* throwing if neither are populated.
*
* @param string $appId
*
* @return string
*
* @throws FacebookSDKException
*/
public static function _getTargetAppId($appId = null) {
$target = ($appId ?: self::$defaultAppId);
if (!$target) {
throw new FacebookSDKException(
'You must provide or set a default application id.', 700
);
}
return $target;
}
/**
* _getTargetAppSecret - Will return either the provided app secret or the
* default, throwing if neither are populated.
*
* @param string $appSecret
*
* @return string
*
* @throws FacebookSDKException
*/
public static function _getTargetAppSecret($appSecret = null) {
$target = ($appSecret ?: self::$defaultAppSecret);
if (!$target) {
throw new FacebookSDKException(
'You must provide or set a default application secret.', 701
);
}
return $target;
}
/**
* Enable or disable sending the appsecret_proof with requests.
*
* @param bool $on
*/
public static function enableAppSecretProof($on = true)
{
static::$useAppSecretProof = ($on ? true : false);
}
/**
* Get whether or not appsecret_proof should be sent with requests.
*
* @return bool
*/
public static function useAppSecretProof()
{
return static::$useAppSecretProof;
}
}

View File

@ -0,0 +1,167 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
use Facebook\Entities\SignedRequest;
/**
* Class FacebookSignedRequestFromInputHelper
* @package Facebook
*/
abstract class FacebookSignedRequestFromInputHelper
{
/**
* @var \Facebook\Entities\SignedRequest|null
*/
protected $signedRequest;
/**
* @var string the app id
*/
protected $appId;
/**
* @var string the app secret
*/
protected $appSecret;
/**
* @var string|null Random string to prevent CSRF.
*/
public $state = null;
/**
* Initialize the helper and process available signed request data.
*
* @param string|null $appId
* @param string|null $appSecret
*/
public function __construct($appId = null, $appSecret = null)
{
$this->appId = FacebookSession::_getTargetAppId($appId);
$this->appSecret = FacebookSession::_getTargetAppSecret($appSecret);
$this->instantiateSignedRequest();
}
/**
* Instantiates a new SignedRequest entity.
*
* @param string|null $rawSignedRequest
*/
public function instantiateSignedRequest($rawSignedRequest = null)
{
$rawSignedRequest = $rawSignedRequest ?: $this->getRawSignedRequest();
if (!$rawSignedRequest) {
return;
}
$this->signedRequest = new SignedRequest($rawSignedRequest, $this->state, $this->appSecret);
}
/**
* Instantiates a FacebookSession from the signed request from input.
*
* @return FacebookSession|null
*/
public function getSession()
{
if ($this->signedRequest && $this->signedRequest->hasOAuthData()) {
return FacebookSession::newSessionFromSignedRequest($this->signedRequest);
}
return null;
}
/**
* Returns the SignedRequest entity.
*
* @return \Facebook\Entities\SignedRequest|null
*/
public function getSignedRequest()
{
return $this->signedRequest;
}
/**
* Returns the user_id if available.
*
* @return string|null
*/
public function getUserId()
{
return $this->signedRequest ? $this->signedRequest->getUserId() : null;
}
/**
* Get raw signed request from input.
*
* @return string|null
*/
abstract public function getRawSignedRequest();
/**
* Get raw signed request from GET input.
*
* @return string|null
*/
public function getRawSignedRequestFromGet()
{
if (isset($_GET['signed_request'])) {
return $_GET['signed_request'];
}
return null;
}
/**
* Get raw signed request from POST input.
*
* @return string|null
*/
public function getRawSignedRequestFromPost()
{
if (isset($_POST['signed_request'])) {
return $_POST['signed_request'];
}
return null;
}
/**
* Get raw signed request from cookie set from the Javascript SDK.
*
* @return string|null
*/
public function getRawSignedRequestFromCookie()
{
$strCookieKey = 'fbsr_' . $this->appId;
if (isset($_COOKIE[$strCookieKey])) {
return $_COOKIE[$strCookieKey];
}
return null;
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class FacebookThrottleException
* @package Facebook
*/
class FacebookThrottleException extends FacebookRequestException
{
}

View File

@ -0,0 +1,173 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class GraphAlbum
* @package Facebook
* @author Daniele Grosso <daniele.grosso@gmail.com>
*/
class GraphAlbum extends GraphObject
{
/**
* Returns the ID for the album.
*
* @return string|null
*/
public function getId()
{
return $this->getProperty('id');
}
/**
* Returns whether the viewer can upload photos to this album.
*
* @return boolean|null
*/
public function canUpload()
{
return $this->getProperty('can_upload');
}
/**
* Returns the number of photos in this album.
*
* @return int|null
*/
public function getCount()
{
return $this->getProperty('count');
}
/**
* Returns the ID of the album's cover photo.
*
* @return string|null
*/
public function getCoverPhoto()
{
return $this->getProperty('cover_photo');
}
/**
* Returns the time the album was initially created.
*
* @return \DateTime|null
*/
public function getCreatedTime()
{
$value = $this->getProperty('created_time');
if ($value) {
return new \DateTime($value);
}
return null;
}
/**
* Returns the time the album was updated.
*
* @return \DateTime|null
*/
public function getUpdatedTime()
{
$value = $this->getProperty('updated_time');
if ($value) {
return new \DateTime($value);
}
return null;
}
/**
* Returns the description of the album.
*
* @return string|null
*/
public function getDescription()
{
return $this->getProperty('description');
}
/**
* Returns profile that created the album.
*
* @return GraphUser|null
*/
public function getFrom()
{
return $this->getProperty('from', GraphUser::className());
}
/**
* Returns a link to this album on Facebook.
*
* @return string|null
*/
public function getLink()
{
return $this->getProperty('link');
}
/**
* Returns the textual location of the album.
*
* @return string|null
*/
public function getLocation()
{
return $this->getProperty('location');
}
/**
* Returns the title of the album.
*
* @return string|null
*/
public function getName()
{
return $this->getProperty('name');
}
/**
* Returns the privacy settings for the album.
*
* @return string|null
*/
public function getPrivacy()
{
return $this->getProperty('privacy');
}
/**
* Returns the type of the album. enum{profile, mobile, wall, normal, album}
*
* @return string|null
*/
public function getType()
{
return $this->getProperty('type');
}
//TODO: public function getPlace() that should return GraphPage
}

View File

@ -0,0 +1,105 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class GraphLocation
* @package Facebook
* @author Fosco Marotto <fjm@fb.com>
* @author David Poll <depoll@fb.com>
*/
class GraphLocation extends GraphObject
{
/**
* Returns the street component of the location
*
* @return string|null
*/
public function getStreet()
{
return $this->getProperty('street');
}
/**
* Returns the city component of the location
*
* @return string|null
*/
public function getCity()
{
return $this->getProperty('city');
}
/**
* Returns the state component of the location
*
* @return string|null
*/
public function getState()
{
return $this->getProperty('state');
}
/**
* Returns the country component of the location
*
* @return string|null
*/
public function getCountry()
{
return $this->getProperty('country');
}
/**
* Returns the zipcode component of the location
*
* @return string|null
*/
public function getZip()
{
return $this->getProperty('zip');
}
/**
* Returns the latitude component of the location
*
* @return float|null
*/
public function getLatitude()
{
return $this->getProperty('latitude');
}
/**
* Returns the street component of the location
*
* @return float|null
*/
public function getLongitude()
{
return $this->getProperty('longitude');
}
}

View File

@ -0,0 +1,171 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class GraphObject
* @package Facebook
* @author Fosco Marotto <fjm@fb.com>
* @author David Poll <depoll@fb.com>
*/
class GraphObject
{
/**
* @var array - Holds the raw associative data for this object
*/
protected $backingData;
/**
* Creates a GraphObject using the data provided.
*
* @param array $raw
*/
public function __construct($raw)
{
if ($raw instanceof \stdClass) {
$raw = get_object_vars($raw);
}
$this->backingData = $raw;
if (isset($this->backingData['data']) && count($this->backingData) === 1) {
if ($this->backingData['data'] instanceof \stdClass) {
$this->backingData = get_object_vars($this->backingData['data']);
} else {
$this->backingData = $this->backingData['data'];
}
}
}
/**
* cast - Return a new instance of a FacebookGraphObject subclass for this
* objects underlying data.
*
* @param string $type The GraphObject subclass to cast to
*
* @return GraphObject
*
* @throws FacebookSDKException
*/
public function cast($type)
{
if ($this instanceof $type) {
return $this;
}
if (is_subclass_of($type, GraphObject::className())) {
return new $type($this->backingData);
} else {
throw new FacebookSDKException(
'Cannot cast to an object that is not a GraphObject subclass', 620
);
}
}
/**
* asArray - Return a key-value associative array for the given graph object.
*
* @return array
*/
public function asArray()
{
return $this->backingData;
}
/**
* getProperty - Gets the value of the named property for this graph object,
* cast to the appropriate subclass type if provided.
*
* @param string $name The property to retrieve
* @param string $type The subclass of GraphObject, optionally
*
* @return mixed
*/
public function getProperty($name, $type = 'Facebook\GraphObject')
{
if (isset($this->backingData[$name])) {
$value = $this->backingData[$name];
if (is_scalar($value)) {
return $value;
} else {
return (new GraphObject($value))->cast($type);
}
} else {
return null;
}
}
/**
* getPropertyAsArray - Get the list value of a named property for this graph
* object, where each item has been cast to the appropriate subclass type
* if provided.
*
* Calling this for a property that is not an array, the behavior
* is undefined, so dont do this.
*
* @param string $name The property to retrieve
* @param string $type The subclass of GraphObject, optionally
*
* @return array
*/
public function getPropertyAsArray($name, $type = 'Facebook\GraphObject')
{
$target = array();
if (isset($this->backingData[$name]['data'])) {
$target = $this->backingData[$name]['data'];
} else if (isset($this->backingData[$name])
&& !is_scalar($this->backingData[$name])) {
$target = $this->backingData[$name];
}
$out = array();
foreach ($target as $key => $value) {
if (is_scalar($value)) {
$out[$key] = $value;
} else {
$out[$key] = (new GraphObject($value))->cast($type);
}
}
return $out;
}
/**
* getPropertyNames - Returns a list of all properties set on the object.
*
* @return array
*/
public function getPropertyNames()
{
return array_keys($this->backingData);
}
/**
* Returns the string class name of the GraphObject or subclass.
*
* @return string
*/
public static function className()
{
return get_called_class();
}
}

View File

@ -0,0 +1,64 @@
<?php
/**
* Copyright 2014 Facebook, Inc.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to
* use, copy, modify, and distribute this software in source code or binary
* form for use in connection with the web services and APIs provided by
* Facebook.
*
* As with any software that integrates with the Facebook platform, your use
* of this software is subject to the Facebook Developer Principles and
* Policies [http://developers.facebook.com/policy/]. This copyright 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.
*
*/
namespace Facebook;
/**
* Class GraphPage
* @package Facebook
* @author Artur Luiz <artur@arturluiz.com.br>
*/
class GraphPage extends GraphObject
{
/**
* Returns the ID for the user's page as a string if present.
*
* @return string|null
*/
public function getId()
{
return $this->getProperty('id');
}
/**
* Returns the Category for the user's page as a string if present.
*
* @return string|null
*/
public function getCategory()
{
return $this->getProperty('category');
}
/**
* Returns the Name of the user's page as a string if present.
*
* @return string|null
*/
public function getName()
{
return $this->getProperty('name');
}
}

Some files were not shown because too many files have changed in this diff Show More