Warning: file_put_contents(reviews_cache.json): Failed to open stream: Permission denied in eval() (line 93 of sites/sundharamdentalenclave.com/modules/contrib/php/php.module(81) : eval()'d code).
eval() (Line: 81)
php_eval('<?php
$apiKey = 'AIzaSyAYMqai7CFJozCcKNJNhR6RFL2HdSwM5vA';
$placeId = 'ChIJcR-tu64HqTsRUdtllO_W1es';
function getGoogleReviews($placeId, $apiKey) {
$allReviews = [];
$sortOptions = ['newest', 'highest_rating', 'lowest_rating', 'most_relevant'];
$totalRating = 'N/A';
$totalReviewCount = 'N/A';
foreach ($sortOptions as $sort) {
$nextPageToken = null;
do {
$url = "https://maps.googleapis.com/maps/api/place/details/json?place_id={$placeId}&fields=rating,user_ratings_total,reviews&reviews_sort={$sort}&key={$apiKey}";
if ($nextPageToken) {
$url .= "&pagetoken=" . urlencode($nextPageToken);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if (curl_errno($ch)) {
error_log('Curl error: ' . curl_error($ch));
continue;
}
curl_close($ch);
$result = json_decode($response, true);
if (isset($result['result'])) {
// Store rating and total review count
if (!isset($totalRating) || $totalRating === 'N/A') {
$totalRating = $result['result']['rating'] ?? 'N/A';
}
if (!isset($totalReviewCount) || $totalReviewCount === 'N/A') {
$totalReviewCount = $result['result']['user_ratings_total'] ?? 'N/A';
}
// Process reviews
if (isset($result['result']['reviews'])) {
foreach ($result['result']['reviews'] as $review) {
// Use unique key to avoid duplicates
$key = $review['author_name'] . '_' . $review['time'];
if (!isset($allReviews[$key])) {
$allReviews[$key] = $review;
}
}
}
}
// Check for next page token
$nextPageToken = $result['next_page_token'] ?? null;
// Required delay when using page token
if ($nextPageToken) {
sleep(2); // Google requires a delay between requests
}
} while ($nextPageToken);
// Delay between different sort options to avoid rate limiting
sleep(1);
}
// Sort reviews by time (newest first)
uasort($allReviews, function($a, $b) {
return $b['time'] - $a['time'];
});
return [
'reviews' => array_values($allReviews),
'rating' => $totalRating,
'user_ratings_total' => $totalReviewCount
];
}
// Get all reviews
$reviewsData = getGoogleReviews($placeId, $apiKey);
// Extract data
$reviews = $reviewsData['reviews'];
$totalRating = $reviewsData['rating'];
$totalReviews = $reviewsData['user_ratings_total'];
$reviewCount = count($reviews);
// Cache the results to avoid hitting API limits
$cacheFile = 'reviews_cache.json';
if ($reviewCount > 0) {
file_put_contents($cacheFile, json_encode($reviewsData));
} else if (file_exists($cacheFile)) {
// Use cached data if current request failed
$cachedData = json_decode(file_get_contents($cacheFile), true);
$reviews = $cachedData['reviews'];
$totalRating = $cachedData['rating'];
$totalReviews = $cachedData['user_ratings_total'];
$reviewCount = count($reviews);
}
// Rest of your HTML/CSS/JavaScript code remains the same...
?>
Google Reviews Carousel
.reviews-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
position: relative;
}
.carousel {
position: relative;
width: 100%;
background-color: #f9f9f9;
padding: 20px;
border-radius: 10px;
overflow: hidden;
}
.carousel-inner {
display: flex;
width: 100%;
transition: transform 0.5s ease;
height: 350px;
}
.review-item {
flex: 0 0 100%;
width: 100%;
padding: 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
margin: 0;
position: relative;
}
.review-content {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.review-profile-photo {
width: 60px;
height: 60px;
border-radius: 50%;
margin-bottom: 10px;
object-fit: cover;
}
.review-author-name {
font-weight: bold;
margin: 10px 0 5px 0;
font-size: 16px;
}
.review-rating {
color: #ff9800;
margin: 5px 0;
font-size: 16px;
}
.review-text {
margin: 15px 0;
font-size: 14px;
line-height: 1.6;
max-width: 90%;
overflow-y: auto;
max-height: 150px;
padding: 0 10px;
}
.review-time {
font-size: 12px;
color: gray;
margin-top: auto;
}
.carousel-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: rgba(255, 255, 255, 0.9);
border: none;
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
z-index: 2;
transition: background-color 0.3s;
}
.carousel-button:hover {
background-color: white;
}
.prev-button {
left: 10px;
}
.next-button {
right: 10px;
}
.carousel-dots {
display: flex;
justify-content: center;
margin-top: 15px;
gap: 8px;
position: absolute;
bottom: 10px;
left: 0;
right: 0;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #ccc;
cursor: pointer;
transition: background-color 0.3s;
}
.dot.active {
background-color: #4CAF50;
}
.review-text::-webkit-scrollbar {
width: 4px;
}
.review-text::-webkit-scrollbar-track {
background: #f1f1f1;
}
.review-text::-webkit-scrollbar-thumb {
background: #888;
border-radius: 2px;
}
.review-text::-webkit-scrollbar-thumb:hover {
background: #555;
}
.place-summary {
text-align: center;
margin-bottom: 20px;
}
.loading-indicator {
text-align: center;
padding: 20px;
font-size: 18px;
color: #666;
}
Place Rating
Overall Rating: <strong><?= htmlspecialchars($totalRating) ?></strong>
Total Reviews: <strong><?= htmlspecialchars($totalReviews) ?></strong>
Showing <?= htmlspecialchars($reviewCount) ?> reviews
<?php if ($reviewCount > 0): ?>
">
<?php if ($reviewCount > 1): ?>
←
→
<?php endif; ?>
<?php foreach ($reviews as $review): ?>
"
class="review-profile-photo"
alt="Profile Photo of <?= htmlspecialchars($review['author_name']) ?>" />
<?= htmlspecialchars($review['author_name']) ?>
<?php
$rating = intval($review['rating']);
echo str_repeat('★', $rating) . str_repeat('☆', 5 - $rating);
?>
<?= nl2br(htmlspecialchars($review['text'])) ?>
<?= htmlspecialchars($review['relative_time_description']) ?>
<?php endforeach; ?>
<?php if ($reviewCount > 1): ?>
<?php endif; ?>
<?php else: ?>
No reviews found.
<?php endif; ?>
document.addEventListener('DOMContentLoaded', function() {
const carousel = document.querySelector('.carousel-inner');
const items = document.querySelectorAll('.review-item');
const totalItems = items.length;
if (totalItems <= 1) return;
const dotsContainer = document.querySelector('.carousel-dots');
const prevButton = document.querySelector('.prev-button');
const nextButton = document.querySelector('.next-button');
let currentIndex = 0;
let isTransitioning = false;
carousel.style.width = '100%';
carousel.style.position = 'relative';
items.forEach((item, index) => {
item.style.position = 'absolute';
item.style.left = `${index * 100}%`;
item.style.width = '100%';
item.style.transform = 'translateX(0)';
});
// Create dots
for (let i = 0; i < totalItems; i++) {
const dot = document.createElement('div');
dot.classList.add('dot');
if (i === 0) dot.classList.add('active');
dot.addEventListener('click', () => goToSlide(i));
dotsContainer.appendChild(dot);
}
function updateDots() {
document.querySelectorAll('.dot').forEach((dot, index) => {
dot.classList.toggle('active', index === currentIndex);
});
}
function goToSlide(index) {
if (isTransitioning) return;
isTransitioning = true;
currentIndex = index;
items.forEach((item) => {
item.style.transition = 'transform 0.5s ease-in-out';
item.style.transform = `translateX(-${currentIndex * 100}%)`;
});
updateDots();
setTimeout(() => {
isTransitioning = false;
}, 500);
}
function nextSlide() {
const nextIndex = (currentIndex + 1) % totalItems;
goToSlide(nextIndex);
}
function prevSlide() {
const prevIndex = (currentIndex - 1 + totalItems) % totalItems;
goToSlide(prevIndex);
}
nextButton.addEventListener('click', nextSlide);
prevButton.addEventListener('click', prevSlide);
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') nextSlide();
if (e.key === 'ArrowLeft') prevSlide();
});
let autoplayInterval = setInterval(nextSlide, 5000);
carousel.parentElement.addEventListener('mouseenter', () => {
clearInterval(autoplayInterval);
});
carousel.parentElement.addEventListener('mouseleave', () => {
autoplayInterval = setInterval(nextSlide, 5000);
});
let touchStartX = 0;
let touchEndX = 0;
carousel.addEventListener('touchstart', (e) => {
touchStartX = e.touches[0].clientX;
clearInterval(autoplayInterval);
}, false);
carousel.addEventListener('touchmove', (e) => {
touchEndX = e.touches[0].clientX;
}, false);
carousel.addEventListener('touchend', () => {
const difference = touchStartX - touchEndX;
if (Math.abs(difference) > 50) {
if (difference > 0) {
nextSlide();
} else {
prevSlide();
}
}
autoplayInterval = setInterval(nextSlide, 5000);
}, false);
});
') (Line: 26)
Drupal\php\Plugin\Filter\Php->process('<?php
$apiKey = 'AIzaSyAYMqai7CFJozCcKNJNhR6RFL2HdSwM5vA';
$placeId = 'ChIJcR-tu64HqTsRUdtllO_W1es';
function getGoogleReviews($placeId, $apiKey) {
$allReviews = [];
$sortOptions = ['newest', 'highest_rating', 'lowest_rating', 'most_relevant'];
$totalRating = 'N/A';
$totalReviewCount = 'N/A';
foreach ($sortOptions as $sort) {
$nextPageToken = null;
do {
$url = "https://maps.googleapis.com/maps/api/place/details/json?place_id={$placeId}&fields=rating,user_ratings_total,reviews&reviews_sort={$sort}&key={$apiKey}";
if ($nextPageToken) {
$url .= "&pagetoken=" . urlencode($nextPageToken);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if (curl_errno($ch)) {
error_log('Curl error: ' . curl_error($ch));
continue;
}
curl_close($ch);
$result = json_decode($response, true);
if (isset($result['result'])) {
// Store rating and total review count
if (!isset($totalRating) || $totalRating === 'N/A') {
$totalRating = $result['result']['rating'] ?? 'N/A';
}
if (!isset($totalReviewCount) || $totalReviewCount === 'N/A') {
$totalReviewCount = $result['result']['user_ratings_total'] ?? 'N/A';
}
// Process reviews
if (isset($result['result']['reviews'])) {
foreach ($result['result']['reviews'] as $review) {
// Use unique key to avoid duplicates
$key = $review['author_name'] . '_' . $review['time'];
if (!isset($allReviews[$key])) {
$allReviews[$key] = $review;
}
}
}
}
// Check for next page token
$nextPageToken = $result['next_page_token'] ?? null;
// Required delay when using page token
if ($nextPageToken) {
sleep(2); // Google requires a delay between requests
}
} while ($nextPageToken);
// Delay between different sort options to avoid rate limiting
sleep(1);
}
// Sort reviews by time (newest first)
uasort($allReviews, function($a, $b) {
return $b['time'] - $a['time'];
});
return [
'reviews' => array_values($allReviews),
'rating' => $totalRating,
'user_ratings_total' => $totalReviewCount
];
}
// Get all reviews
$reviewsData = getGoogleReviews($placeId, $apiKey);
// Extract data
$reviews = $reviewsData['reviews'];
$totalRating = $reviewsData['rating'];
$totalReviews = $reviewsData['user_ratings_total'];
$reviewCount = count($reviews);
// Cache the results to avoid hitting API limits
$cacheFile = 'reviews_cache.json';
if ($reviewCount > 0) {
file_put_contents($cacheFile, json_encode($reviewsData));
} else if (file_exists($cacheFile)) {
// Use cached data if current request failed
$cachedData = json_decode(file_get_contents($cacheFile), true);
$reviews = $cachedData['reviews'];
$totalRating = $cachedData['rating'];
$totalReviews = $cachedData['user_ratings_total'];
$reviewCount = count($reviews);
}
// Rest of your HTML/CSS/JavaScript code remains the same...
?>
Google Reviews Carousel
.reviews-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
position: relative;
}
.carousel {
position: relative;
width: 100%;
background-color: #f9f9f9;
padding: 20px;
border-radius: 10px;
overflow: hidden;
}
.carousel-inner {
display: flex;
width: 100%;
transition: transform 0.5s ease;
height: 350px;
}
.review-item {
flex: 0 0 100%;
width: 100%;
padding: 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
margin: 0;
position: relative;
}
.review-content {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.review-profile-photo {
width: 60px;
height: 60px;
border-radius: 50%;
margin-bottom: 10px;
object-fit: cover;
}
.review-author-name {
font-weight: bold;
margin: 10px 0 5px 0;
font-size: 16px;
}
.review-rating {
color: #ff9800;
margin: 5px 0;
font-size: 16px;
}
.review-text {
margin: 15px 0;
font-size: 14px;
line-height: 1.6;
max-width: 90%;
overflow-y: auto;
max-height: 150px;
padding: 0 10px;
}
.review-time {
font-size: 12px;
color: gray;
margin-top: auto;
}
.carousel-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: rgba(255, 255, 255, 0.9);
border: none;
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
z-index: 2;
transition: background-color 0.3s;
}
.carousel-button:hover {
background-color: white;
}
.prev-button {
left: 10px;
}
.next-button {
right: 10px;
}
.carousel-dots {
display: flex;
justify-content: center;
margin-top: 15px;
gap: 8px;
position: absolute;
bottom: 10px;
left: 0;
right: 0;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #ccc;
cursor: pointer;
transition: background-color 0.3s;
}
.dot.active {
background-color: #4CAF50;
}
.review-text::-webkit-scrollbar {
width: 4px;
}
.review-text::-webkit-scrollbar-track {
background: #f1f1f1;
}
.review-text::-webkit-scrollbar-thumb {
background: #888;
border-radius: 2px;
}
.review-text::-webkit-scrollbar-thumb:hover {
background: #555;
}
.place-summary {
text-align: center;
margin-bottom: 20px;
}
.loading-indicator {
text-align: center;
padding: 20px;
font-size: 18px;
color: #666;
}
Place Rating
Overall Rating: <strong><?= htmlspecialchars($totalRating) ?></strong>
Total Reviews: <strong><?= htmlspecialchars($totalReviews) ?></strong>
Showing <?= htmlspecialchars($reviewCount) ?> reviews
<?php if ($reviewCount > 0): ?>
">
<?php if ($reviewCount > 1): ?>
←
→
<?php endif; ?>
<?php foreach ($reviews as $review): ?>
"
class="review-profile-photo"
alt="Profile Photo of <?= htmlspecialchars($review['author_name']) ?>" />
<?= htmlspecialchars($review['author_name']) ?>
<?php
$rating = intval($review['rating']);
echo str_repeat('★', $rating) . str_repeat('☆', 5 - $rating);
?>
<?= nl2br(htmlspecialchars($review['text'])) ?>
<?= htmlspecialchars($review['relative_time_description']) ?>
<?php endforeach; ?>
<?php if ($reviewCount > 1): ?>
<?php endif; ?>
<?php else: ?>
No reviews found.
<?php endif; ?>
document.addEventListener('DOMContentLoaded', function() {
const carousel = document.querySelector('.carousel-inner');
const items = document.querySelectorAll('.review-item');
const totalItems = items.length;
if (totalItems <= 1) return;
const dotsContainer = document.querySelector('.carousel-dots');
const prevButton = document.querySelector('.prev-button');
const nextButton = document.querySelector('.next-button');
let currentIndex = 0;
let isTransitioning = false;
carousel.style.width = '100%';
carousel.style.position = 'relative';
items.forEach((item, index) => {
item.style.position = 'absolute';
item.style.left = `${index * 100}%`;
item.style.width = '100%';
item.style.transform = 'translateX(0)';
});
// Create dots
for (let i = 0; i < totalItems; i++) {
const dot = document.createElement('div');
dot.classList.add('dot');
if (i === 0) dot.classList.add('active');
dot.addEventListener('click', () => goToSlide(i));
dotsContainer.appendChild(dot);
}
function updateDots() {
document.querySelectorAll('.dot').forEach((dot, index) => {
dot.classList.toggle('active', index === currentIndex);
});
}
function goToSlide(index) {
if (isTransitioning) return;
isTransitioning = true;
currentIndex = index;
items.forEach((item) => {
item.style.transition = 'transform 0.5s ease-in-out';
item.style.transform = `translateX(-${currentIndex * 100}%)`;
});
updateDots();
setTimeout(() => {
isTransitioning = false;
}, 500);
}
function nextSlide() {
const nextIndex = (currentIndex + 1) % totalItems;
goToSlide(nextIndex);
}
function prevSlide() {
const prevIndex = (currentIndex - 1 + totalItems) % totalItems;
goToSlide(prevIndex);
}
nextButton.addEventListener('click', nextSlide);
prevButton.addEventListener('click', prevSlide);
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') nextSlide();
if (e.key === 'ArrowLeft') prevSlide();
});
let autoplayInterval = setInterval(nextSlide, 5000);
carousel.parentElement.addEventListener('mouseenter', () => {
clearInterval(autoplayInterval);
});
carousel.parentElement.addEventListener('mouseleave', () => {
autoplayInterval = setInterval(nextSlide, 5000);
});
let touchStartX = 0;
let touchEndX = 0;
carousel.addEventListener('touchstart', (e) => {
touchStartX = e.touches[0].clientX;
clearInterval(autoplayInterval);
}, false);
carousel.addEventListener('touchmove', (e) => {
touchEndX = e.touches[0].clientX;
}, false);
carousel.addEventListener('touchend', () => {
const difference = touchStartX - touchEndX;
if (Math.abs(difference) > 50) {
if (difference > 0) {
nextSlide();
} else {
prevSlide();
}
}
autoplayInterval = setInterval(nextSlide, 5000);
}, false);
});
', 'en') (Line: 118)
Drupal\filter\Element\ProcessedText::preRenderText(Array)
call_user_func_array(Array, Array) (Line: 111)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 858)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 421)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 240)
Drupal\Core\Render\Renderer->render(Array) (Line: 475)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 87)
__TwigTemplate_026cc902cae0f5d522facc0bb331f561->doDisplay(Array, Array) (Line: 394)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 367)
Twig\Template->display(Array) (Line: 379)
Twig\Template->render(Array) (Line: 38)
Twig\TemplateWrapper->render(Array) (Line: 39)
twig_render_template('sites/sundharamdentalenclave.com/themes/custom/gavias_daudo/templates/field.html.twig', Array) (Line: 348)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 480)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 493)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 240)
Drupal\Core\Render\Renderer->render(Array) (Line: 475)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 113)
__TwigTemplate_36fbc2cd1ae1f0b74cb0ddde48bb4cf3->doDisplay(Array, Array) (Line: 394)
Twig\Template->displayWithErrorHandling(Array, Array) (Line: 367)
Twig\Template->display(Array) (Line: 379)
Twig\Template->render(Array) (Line: 38)
Twig\TemplateWrapper->render(Array) (Line: 39)
twig_render_template('sites/sundharamdentalenclave.com/themes/custom/gavias_daudo/templates/node/node.html.twig', Array) (Line: 348)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 480)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 240)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 238)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 627)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 239)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 128)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 186)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 76)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 28)
Drupal\Core\StackMiddleware\ContentLength->handle(Object, 1, 1) (Line: 32)
Drupal\big_pipe\StackMiddleware\ContentLength->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 36)
Drupal\Core\StackMiddleware\AjaxPageState->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle(Object, 1, 1) (Line: 704)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)