TemplateLayouts
8 months ago
AirTable.php
1 year ago
Boomplay.php
1 year ago
Calendly.php
2 years ago
Canva.php
1 year ago
FITE.php
1 year ago
GettyImages.php
9 months ago
Giphy.php
2 years ago
GitHub.php
3 years ago
GoogleCalendar.php
8 months ago
GoogleDocs.php
2 years ago
GoogleDrive.php
2 years ago
GoogleMaps.php
2 years ago
GooglePhotos.php
7 months ago
Gumroad.php
2 years ago
InstagramFeed.php
6 months ago
LinkedIn.php
2 years ago
Meetup.php
8 months ago
NRKRadio.php
2 years ago
OneDrive.php
10 months ago
OpenSea.php
9 months ago
SelfHosted.php
2 years ago
Spreaker.php
1 year ago
TikTok.php
2 years ago
Twitch.php
2 years ago
Wrapper.php
3 years ago
X.php
2 years ago
Youtube.php
9 months ago
index.html
7 years ago
Meetup.php
788 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * Meetup.php |
| 5 | * |
| 6 | * @package Embera |
| 7 | * @author Michael Pratt <yo@michael-pratt.com> |
| 8 | * @link http://www.michael-pratt.com/ |
| 9 | * |
| 10 | * For the full copyright and license information, please view the LICENSE |
| 11 | * file that was distributed with this source code. |
| 12 | */ |
| 13 | |
| 14 | namespace EmbedPress\Providers; |
| 15 | |
| 16 | use EmbedPress\Includes\Classes\Helper; |
| 17 | use Embera\Provider\ProviderAdapter; |
| 18 | use Embera\Provider\ProviderInterface; |
| 19 | use Embera\Url; |
| 20 | |
| 21 | (defined('ABSPATH') && defined('EMBEDPRESS_IS_LOADED')) or die("No direct script access allowed."); |
| 22 | |
| 23 | /** |
| 24 | * Meetup Provider |
| 25 | * @link https://meetup.com |
| 26 | */ |
| 27 | class Meetup extends ProviderAdapter implements ProviderInterface |
| 28 | { |
| 29 | /** inline {@inheritdoc} */ |
| 30 | protected $shouldSendRequest = false; |
| 31 | /** inline {@inheritdoc} */ |
| 32 | protected $endpoint = 'https://api.meetup.com/oembed?format=json'; |
| 33 | |
| 34 | /** inline {@inheritdoc} */ |
| 35 | protected static $hosts = [ |
| 36 | 'meetup.com' |
| 37 | ]; |
| 38 | |
| 39 | /** inline {@inheritdoc} */ |
| 40 | protected $httpsSupport = true; |
| 41 | |
| 42 | /** inline {@inheritdoc} */ |
| 43 | protected $responsiveSupport = true; |
| 44 | |
| 45 | /** inline {@inheritdoc} */ |
| 46 | public function validateUrl(Url $url) |
| 47 | { |
| 48 | return (bool) ( |
| 49 | preg_match('~meetup\.com/(?:.+)~i', (string) $url) || |
| 50 | preg_match('~meetu\.ps/(?:\w+)/?$~i', (string) $url) |
| 51 | ); |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * Check if the URL is an RSS feed URL or a group URL that should be treated as RSS |
| 56 | */ |
| 57 | private function isRssUrl($url) |
| 58 | { |
| 59 | // Check if it's already an RSS URL |
| 60 | if (preg_match('~meetup\.com/.+/events/rss~i', $url)) { |
| 61 | return true; |
| 62 | } |
| 63 | |
| 64 | // Check if it's a group URL that should be converted to RSS |
| 65 | if (preg_match('~meetup\.com/[^/]+/?$~i', $url)) { |
| 66 | return true; |
| 67 | } |
| 68 | |
| 69 | // Check if it's an events URL that should be converted to RSS |
| 70 | if (preg_match('~meetup\.com/[^/]+/events/?$~i', $url)) { |
| 71 | return true; |
| 72 | } |
| 73 | |
| 74 | return false; |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Check if pro features are enabled |
| 79 | */ |
| 80 | protected function isProFeaturesEnabled() |
| 81 | { |
| 82 | // Use the Helper class to check pro features |
| 83 | if (class_exists('\EmbedPress\Includes\Classes\Helper')) { |
| 84 | return Helper::is_pro_features_enabled(); |
| 85 | } |
| 86 | |
| 87 | // Fallback: check if pro plugin is active |
| 88 | return function_exists('is_embedpress_pro_active') && is_embedpress_pro_active(); |
| 89 | } |
| 90 | |
| 91 | /** |
| 92 | * Get pro upgrade message for RSS feeds using EmbedPress styling |
| 93 | */ |
| 94 | private function getProUpgradeMessage($response) |
| 95 | { |
| 96 | // Get the alert icon URL |
| 97 | $alert_icon_url = defined('EMBEDPRESS_URL_ASSETS') ? EMBEDPRESS_URL_ASSETS . 'images/alert.svg' : ''; |
| 98 | |
| 99 | $response['html'] = '<div class="embedpress-meetup-upgrade-notice" style=" |
| 100 | width: calc(100% - 30px); |
| 101 | max-width: 500px; |
| 102 | margin: 20px auto; |
| 103 | background: #fff; |
| 104 | border-radius: 20px; |
| 105 | padding: 30px; |
| 106 | display: flex; |
| 107 | flex-direction: column; |
| 108 | align-items: center; |
| 109 | text-align: center; |
| 110 | "> |
| 111 | ' . ($alert_icon_url ? '<img src="' . esc_url($alert_icon_url) . '" alt="" style="height: 100px; margin-bottom: 20px;">' : '') . ' |
| 112 | <h2 style=" |
| 113 | font-size: 32px; |
| 114 | font-weight: 450; |
| 115 | color: #131f4d; |
| 116 | margin-bottom: 15px; |
| 117 | margin-top: 0; |
| 118 | "> |
| 119 | ' . __('Meetup Events Feed', 'embedpress') . ' |
| 120 | </h2> |
| 121 | <p style=" |
| 122 | font-size: 14px; |
| 123 | font-weight: 400; |
| 124 | color: #7c8db5; |
| 125 | margin-top: 10px; |
| 126 | margin-bottom: 15px; |
| 127 | line-height: 1.5; |
| 128 | "> |
| 129 | ' . sprintf( |
| 130 | __('Display multiple Meetup events from RSS feeds is a premium feature. You need to upgrade to the <a href="%s" target="_blank">Premium</a> Version to use this feature.', 'embedpress'), |
| 131 | 'https://wpdeveloper.com/in/upgrade-embedpress' |
| 132 | ) . ' |
| 133 | </p> |
| 134 | <p style=" |
| 135 | font-size: 12px; |
| 136 | font-weight: 400; |
| 137 | color: #7c8db5; |
| 138 | margin: 0; |
| 139 | "> |
| 140 | ' . __('For single events, use the individual event URL instead of the RSS feed.', 'embedpress') . ' |
| 141 | </p> |
| 142 | </div> |
| 143 | |
| 144 | <style> |
| 145 | .embedpress-meetup-upgrade-notice p a { |
| 146 | text-decoration: underline; |
| 147 | font-weight: 700; |
| 148 | color: #131f4d; |
| 149 | } |
| 150 | .embedpress-meetup-upgrade-notice p a:hover { |
| 151 | color: #0f1a3a; |
| 152 | } |
| 153 | </style>'; |
| 154 | |
| 155 | return $response; |
| 156 | } |
| 157 | |
| 158 | /** inline {@inheritdoc} */ |
| 159 | public function normalizeUrl(Url $url) |
| 160 | { |
| 161 | $url->convertToHttps(); |
| 162 | $url->removeQueryString(); |
| 163 | |
| 164 | $url_string = (string) $url; |
| 165 | |
| 166 | // If it's a group URL without /events/rss, append it |
| 167 | if (preg_match('~meetup\.com/[^/]+/?$~i', $url_string)) { |
| 168 | // Don't append if it already has /events/rss |
| 169 | if (!preg_match('~meetup\.com/.+/events/rss~i', $url_string)) { |
| 170 | $url_string = rtrim($url_string, '/') . '/events/rss'; |
| 171 | $url = new Url($url_string); |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | // If it's an events URL without /rss, append it |
| 176 | if (preg_match('~meetup\.com/[^/]+/events/?$~i', $url_string)) { |
| 177 | // Don't append if it already has /rss |
| 178 | if (!preg_match('~meetup\.com/.+/events/rss~i', $url_string)) { |
| 179 | $url_string = rtrim($url_string, '/') . '/rss'; |
| 180 | $url = new Url($url_string); |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | return $url; |
| 185 | } |
| 186 | |
| 187 | public function getStaticResponse() |
| 188 | { |
| 189 | $meetup_website = 'https://meetup.com'; |
| 190 | $response = []; |
| 191 | $response['type'] = 'rich'; |
| 192 | $response['provider_name'] = 'Meetup'; |
| 193 | $response['provider_url'] = $meetup_website; |
| 194 | $response['url'] = $this->getUrl(); |
| 195 | |
| 196 | add_filter('safe_style_css', [$this, 'safe_style_css']); |
| 197 | $allowed_protocols = wp_allowed_protocols(); |
| 198 | $allowed_protocols[] = 'data'; |
| 199 | |
| 200 | |
| 201 | |
| 202 | // Check if this is an RSS feed URL |
| 203 | if ($this->isRssUrl($this->getUrl())) { |
| 204 | // Check if pro features are enabled for RSS feeds |
| 205 | if (!$this->isProFeaturesEnabled()) { |
| 206 | return $this->getProUpgradeMessage($response); |
| 207 | } |
| 208 | |
| 209 | // Delegate RSS feed handling to the pro plugin |
| 210 | if (class_exists('\Embedpress\Pro\Providers\Meetup')) { |
| 211 | $hash = 'mu_' . md5($this->getUrl() . serialize($this->config)); |
| 212 | $cache_duration = apply_filters('embedpress_meetup_rss_cache_duration', 3600 * 6); // 6 hour default |
| 213 | $filename = wp_get_upload_dir()['basedir'] . "/embedpress/{$hash}.txt"; |
| 214 | |
| 215 | return \Embedpress\Pro\Providers\Meetup::handleRssFeed($response, $filename, $this->getUrl(), $cache_duration, $this->config); |
| 216 | } else { |
| 217 | // Pro plugin not available, show upgrade message |
| 218 | return $this->getProUpgradeMessage($response); |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | // Get cached data or fetch new data |
| 223 | $event_data = $this->getCachedEventData(); |
| 224 | |
| 225 | if (!$event_data) { |
| 226 | $t = wp_remote_get($this->getUrl(), ['timeout' => 10]); |
| 227 | if (!is_wp_error($t)) { |
| 228 | if ($meetup_page_content = wp_remote_retrieve_body($t)) { |
| 229 | $dom = str_get_html($meetup_page_content); |
| 230 | $event_data = $this->extractEventDataFromDom($dom); |
| 231 | if ($event_data) { |
| 232 | $this->cacheEventData($event_data); |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | |
| 239 | if (!$event_data) { |
| 240 | $response['html'] = $this->getUrl(); |
| 241 | return $response; |
| 242 | } |
| 243 | |
| 244 | // Generate HTML from cached data |
| 245 | $response['html'] = $this->generateEventHtml($event_data, $allowed_protocols); |
| 246 | remove_filter('safe_style_css', [$this, 'safe_style_css']); |
| 247 | return $response; |
| 248 | } |
| 249 | |
| 250 | /** |
| 251 | * Get cached event data using transients |
| 252 | */ |
| 253 | private function getCachedEventData() |
| 254 | { |
| 255 | $url_hash = md5($this->getUrl()); |
| 256 | $data_transient_key = 'meetup_event_data_' . $url_hash; |
| 257 | |
| 258 | return get_transient($data_transient_key); |
| 259 | } |
| 260 | |
| 261 | /** |
| 262 | * Cache event data using transients |
| 263 | */ |
| 264 | private function cacheEventData($event_data, $expiration = 3600) |
| 265 | { |
| 266 | $url_hash = md5($this->getUrl()); |
| 267 | $data_transient_key = 'meetup_event_data_' . $url_hash; |
| 268 | |
| 269 | set_transient($data_transient_key, $event_data, $expiration); |
| 270 | } |
| 271 | |
| 272 | /** |
| 273 | * Extract event data from Next.js JSON or fallback to DOM parsing |
| 274 | */ |
| 275 | private function extractEventDataFromDom($dom) |
| 276 | { |
| 277 | if (empty($dom) || !is_object($dom)) { |
| 278 | return false; |
| 279 | } |
| 280 | |
| 281 | // First, try to extract from __NEXT_DATA__ JSON (new Meetup structure) |
| 282 | $next_data_script = $dom->find('script#__NEXT_DATA__', 0); |
| 283 | if ($next_data_script) { |
| 284 | $json_data = $next_data_script->innertext(); |
| 285 | $data = json_decode($json_data, true); |
| 286 | |
| 287 | if ($data && isset($data['props']['pageProps']['event'])) { |
| 288 | $event = $data['props']['pageProps']['event']; |
| 289 | return $this->extractFromNextData($event); |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | // Fallback to old DOM parsing method |
| 294 | $header_dom = $dom->find('div[data-event-label="top"]', 0); |
| 295 | $body_dom = $dom->find('div[data-event-label="body"]', 0); |
| 296 | $event_location_info = $dom->find('div[data-event-label="info"] .sticky', 0); |
| 297 | |
| 298 | if (empty($header_dom) || empty($body_dom) || empty($event_location_info)) { |
| 299 | return false; |
| 300 | } |
| 301 | |
| 302 | return $this->extractFromDomElements($header_dom, $body_dom, $event_location_info); |
| 303 | } |
| 304 | |
| 305 | /** |
| 306 | * Extract event data from Next.js __NEXT_DATA__ JSON |
| 307 | */ |
| 308 | private function extractFromNextData($event) |
| 309 | { |
| 310 | // Extract basic event info |
| 311 | $title = isset($event['title']) ? $event['title'] : ''; |
| 312 | $description = isset($event['description']) ? $event['description'] : ''; |
| 313 | $event_url = isset($event['eventUrl']) ? $event['eventUrl'] : $this->getUrl(); |
| 314 | |
| 315 | // Extract date/time |
| 316 | $date_time = ''; |
| 317 | if (isset($event['dateTime'])) { |
| 318 | $timestamp = strtotime($event['dateTime']); |
| 319 | $date_time = date('l, F j, Y · g:i A', $timestamp); |
| 320 | if (isset($event['endTime'])) { |
| 321 | $end_timestamp = strtotime($event['endTime']); |
| 322 | $date_time .= ' to ' . date('g:i A T', $end_timestamp); |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | // Extract hosts |
| 327 | $hosts_html = ''; |
| 328 | if (isset($event['eventHosts']) && is_array($event['eventHosts'])) { |
| 329 | $host_names = array(); |
| 330 | foreach ($event['eventHosts'] as $host) { |
| 331 | if (isset($host['name'])) { |
| 332 | $host_names[] = esc_html($host['name']); |
| 333 | } |
| 334 | } |
| 335 | if (!empty($host_names)) { |
| 336 | $hosts_html = '<div class="ep-event-hosts">Hosted by ' . implode(', ', $host_names) . '</div>'; |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | // Extract venue/location |
| 341 | $location_html = ''; |
| 342 | if (isset($event['eventType']) && $event['eventType'] === 'ONLINE') { |
| 343 | $location_html = '<div class="ep-event-location"><strong>📍 Online event</strong></div>'; |
| 344 | } elseif (isset($event['venue'])) { |
| 345 | $venue = $event['venue']; |
| 346 | $location_parts = array(); |
| 347 | if (!empty($venue['name'])) $location_parts[] = $venue['name']; |
| 348 | if (!empty($venue['address'])) $location_parts[] = $venue['address']; |
| 349 | if (!empty($venue['city'])) $location_parts[] = $venue['city']; |
| 350 | if (!empty($venue['state'])) $location_parts[] = $venue['state']; |
| 351 | |
| 352 | if (!empty($location_parts)) { |
| 353 | $location_html = '<div class="ep-event-location"><strong>📍 ' . esc_html(implode(', ', $location_parts)) . '</strong></div>'; |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | // Extract featured image |
| 358 | $image_html = ''; |
| 359 | if (isset($event['featuredEventPhoto']['source'])) { |
| 360 | $image_url = esc_url($event['featuredEventPhoto']['source']); |
| 361 | $image_html = '<div class="ep-event-single-image"><img src="' . $image_url . '" alt="' . esc_attr($title) . '" /></div>'; |
| 362 | } |
| 363 | |
| 364 | // Format description (convert markdown-style links to HTML) |
| 365 | $description_html = $this->formatDescription($description); |
| 366 | |
| 367 | return array( |
| 368 | 'date' => $date_time, |
| 369 | 'title' => $title, |
| 370 | 'content' => $image_html . '<div class="ep-event-description">' . $description_html . '</div>', |
| 371 | 'host_info' => $hosts_html, |
| 372 | 'event_location_info' => $location_html, |
| 373 | 'url' => $event_url |
| 374 | ); |
| 375 | } |
| 376 | |
| 377 | /** |
| 378 | * Format description text (convert markdown-style links to HTML) |
| 379 | */ |
| 380 | private function formatDescription($description) |
| 381 | { |
| 382 | // Convert markdown links [text](url) to HTML |
| 383 | $description = preg_replace('/\[([^\]]+)\]\(([^\)]+)\)/', '<a href="$2" target="_blank" rel="noopener">$1</a>', $description); |
| 384 | |
| 385 | // Convert **bold** to <strong> |
| 386 | $description = preg_replace('/\*\*([^\*]+)\*\*/', '<strong>$1</strong>', $description); |
| 387 | |
| 388 | // Convert line breaks to <br> |
| 389 | $description = nl2br($description); |
| 390 | |
| 391 | return $description; |
| 392 | } |
| 393 | |
| 394 | /** |
| 395 | * Extract event data from DOM elements (legacy method) |
| 396 | */ |
| 397 | private function extractFromDomElements($header_dom, $body_dom, $event_location_info) |
| 398 | { |
| 399 | |
| 400 | // Process location info images |
| 401 | $dewqijm = $event_location_info->find('.dewqijm', 0)->find('span', 0); |
| 402 | if (!empty($dewqijm)) { |
| 403 | $img = $dewqijm->find('noscript', 0)->innertext(); |
| 404 | $dewqijm->removeChild($dewqijm->find('img', 1)); |
| 405 | $dewqijm->find('noscript', 0)->remove(); |
| 406 | $dewqijm->outertext = $dewqijm->makeup() . $dewqijm->innertext . $img . '</span>'; |
| 407 | } |
| 408 | |
| 409 | $date = $this->embedpress_get_markup_from_node($header_dom->find('time', 0)); |
| 410 | $title = $this->embedpress_get_markup_from_node($header_dom->find('h1', 0)); |
| 411 | $emrv9za = $body_dom->find('div.emrv9za', 0); |
| 412 | $picture = $emrv9za->find('picture[data-testid="event-description-image"]', 0); |
| 413 | if (!empty($picture) && $picture->find('img', 0)) { |
| 414 | if ($picture->find('noscript', 0)) { |
| 415 | $picture->find('img', 0)->remove(); |
| 416 | $img = $picture->find('noscript', 0)->innertext(); |
| 417 | $img = str_replace('/_next/image/', 'https://www.meetup.com/_next/image/', $img); |
| 418 | $picture->find('noscript', 0)->remove(); |
| 419 | $span = $picture->find('div', 0)->find('span', 0); |
| 420 | $span->outertext = $span->makeup() . $span->innertext . $img . '</span>'; |
| 421 | } else { |
| 422 | $img = $picture->find('img', 0); |
| 423 | $src = $img->src; |
| 424 | if ($src && strpos($src, '/_next/image/') === 0) { |
| 425 | $img->src = 'https://www.meetup.com' . $img->src; |
| 426 | } else if (strpos($src, '//') === false && $srcset = $img->srcset) { |
| 427 | $img->src = $this->getLargestImage($srcset); |
| 428 | if (strpos($img->src, '//') === false) { |
| 429 | $img->src = 'https://www.meetup.com' . $img->src; |
| 430 | } |
| 431 | } |
| 432 | } |
| 433 | } |
| 434 | |
| 435 | $content = $this->embedpress_get_markup_from_node($emrv9za); |
| 436 | |
| 437 | $host_info = $header_dom->find('a[data-event-label="hosted-by"]', 0); |
| 438 | ob_start(); |
| 439 | echo $host_info; |
| 440 | $host_info = ob_get_clean(); |
| 441 | |
| 442 | ob_start(); |
| 443 | echo $event_location_info; |
| 444 | $event_location_info = ob_get_clean(); |
| 445 | |
| 446 | // Return structured data instead of generating HTML |
| 447 | return [ |
| 448 | 'date' => $date, |
| 449 | 'title' => $title, |
| 450 | 'content' => $content, |
| 451 | 'host_info' => $host_info, |
| 452 | 'event_location_info' => $event_location_info, |
| 453 | 'url' => $this->getUrl() |
| 454 | ]; |
| 455 | } |
| 456 | |
| 457 | /** |
| 458 | * Generate HTML from cached event data |
| 459 | */ |
| 460 | private function generateEventHtml($event_data, $allowed_protocols) |
| 461 | { |
| 462 | ob_start(); |
| 463 | ?> |
| 464 | <article class="embedpress-event embedpress-event--modern"> |
| 465 | <div class="ep-event-card"> |
| 466 | <header class="ep-event-header"> |
| 467 | <div class="ep-event-meta"> |
| 468 | <span class="ep-event--date"> |
| 469 | <svg class="ep-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| 470 | <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect> |
| 471 | <line x1="16" y1="2" x2="16" y2="6"></line> |
| 472 | <line x1="8" y1="2" x2="8" y2="6"></line> |
| 473 | <line x1="3" y1="10" x2="21" y2="10"></line> |
| 474 | </svg> |
| 475 | <?php echo esc_html($event_data['date']); ?> |
| 476 | </span> |
| 477 | <?php if (!empty($event_data['event_location_info'])): ?> |
| 478 | <div class="ep-event--location"> |
| 479 | <?php echo wp_kses($event_data['event_location_info'], 'post', $allowed_protocols); ?> |
| 480 | </div> |
| 481 | <?php endif; ?> |
| 482 | </div> |
| 483 | |
| 484 | <a class="ep-event-link" href="<?php echo esc_url($event_data['url']); ?>" target="_blank" rel="noopener noreferrer"> |
| 485 | <h2 class="ep-event--title"><?php echo esc_html($event_data['title']); ?></h2> |
| 486 | </a> |
| 487 | |
| 488 | <?php if (!empty($event_data['host_info'])): ?> |
| 489 | <div class="ep-event--host"> |
| 490 | <?php echo wp_kses_post($event_data['host_info']); ?> |
| 491 | </div> |
| 492 | <?php endif; ?> |
| 493 | </header> |
| 494 | |
| 495 | <section class="ep-event-content"> |
| 496 | <div class="ep-event--description"> |
| 497 | <?php echo wp_kses_post($event_data['content']); ?> |
| 498 | </div> |
| 499 | </section> |
| 500 | |
| 501 | <footer class="ep-event-footer"> |
| 502 | <a href="<?php echo esc_url($event_data['url']); ?>" target="_blank" rel="noopener noreferrer" class="ep-event-cta"> |
| 503 | View Event Details |
| 504 | <svg class="ep-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| 505 | <line x1="5" y1="12" x2="19" y2="12"></line> |
| 506 | <polyline points="12 5 19 12 12 19"></polyline> |
| 507 | </svg> |
| 508 | </a> |
| 509 | </footer> |
| 510 | </div> |
| 511 | </article> |
| 512 | |
| 513 | <style> |
| 514 | /* Modern Meetup Event Card Styles */ |
| 515 | .embedpress-event--modern { |
| 516 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; |
| 517 | margin: 24px 0; |
| 518 | max-width: 100%; |
| 519 | } |
| 520 | |
| 521 | .embedpress-event--modern .ep-event-card { |
| 522 | background: #ffffff; |
| 523 | border: 1px solid #e5e7eb; |
| 524 | border-radius: 12px; |
| 525 | overflow: hidden; |
| 526 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); |
| 527 | transition: all 0.3s ease; |
| 528 | } |
| 529 | |
| 530 | .embedpress-event--modern .ep-event-card:hover { |
| 531 | box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); |
| 532 | transform: translateY(-2px); |
| 533 | } |
| 534 | |
| 535 | /* Header Section */ |
| 536 | .embedpress-event--modern .ep-event-header { |
| 537 | padding: 24px; |
| 538 | border-bottom: 1px solid #f3f4f6; |
| 539 | } |
| 540 | |
| 541 | .embedpress-event--modern .ep-event-meta { |
| 542 | display: flex; |
| 543 | flex-wrap: wrap; |
| 544 | gap: 16px; |
| 545 | margin-bottom: 16px; |
| 546 | } |
| 547 | |
| 548 | .embedpress-event--modern .ep-event--date { |
| 549 | display: inline-flex; |
| 550 | align-items: center; |
| 551 | gap: 6px; |
| 552 | color: #6b7280; |
| 553 | font-size: 14px; |
| 554 | font-weight: 500; |
| 555 | padding: 6px 12px; |
| 556 | background: #f9fafb; |
| 557 | border-radius: 6px; |
| 558 | } |
| 559 | |
| 560 | .embedpress-event--modern .ep-event--location { |
| 561 | display: inline-flex; |
| 562 | align-items: center; |
| 563 | gap: 6px; |
| 564 | color: #059669; |
| 565 | font-size: 14px; |
| 566 | font-weight: 500; |
| 567 | padding: 6px 12px; |
| 568 | background: #ecfdf5; |
| 569 | border-radius: 6px; |
| 570 | } |
| 571 | |
| 572 | .embedpress-event--modern .ep-icon { |
| 573 | flex-shrink: 0; |
| 574 | width: 16px; |
| 575 | height: 16px; |
| 576 | } |
| 577 | |
| 578 | .embedpress-event--modern .ep-event-link { |
| 579 | text-decoration: none; |
| 580 | color: inherit; |
| 581 | display: block; |
| 582 | transition: color 0.2s ease; |
| 583 | } |
| 584 | |
| 585 | .embedpress-event--modern .ep-event-link:hover { |
| 586 | color: #007cba; |
| 587 | } |
| 588 | |
| 589 | .embedpress-event--modern .ep-event--title { |
| 590 | font-size: 24px; |
| 591 | font-weight: 700; |
| 592 | line-height: 1.3; |
| 593 | color: #111827; |
| 594 | margin: 0 0 16px 0; |
| 595 | transition: color 0.2s ease; |
| 596 | } |
| 597 | |
| 598 | .embedpress-event--modern .ep-event-link:hover .ep-event--title { |
| 599 | color: #007cba; |
| 600 | } |
| 601 | |
| 602 | .embedpress-event--modern .ep-event--host { |
| 603 | display: flex; |
| 604 | align-items: center; |
| 605 | gap: 8px; |
| 606 | color: #6b7280; |
| 607 | font-size: 14px; |
| 608 | } |
| 609 | |
| 610 | .embedpress-event--modern .ep-event--host::before { |
| 611 | content: "👤"; |
| 612 | font-size: 16px; |
| 613 | } |
| 614 | |
| 615 | /* Content Section */ |
| 616 | .embedpress-event--modern .ep-event-content { |
| 617 | padding: 24px; |
| 618 | } |
| 619 | |
| 620 | .embedpress-event--modern .ep-event--description { |
| 621 | color: #374151; |
| 622 | font-size: 15px; |
| 623 | line-height: 1.6; |
| 624 | } |
| 625 | |
| 626 | .embedpress-event--modern .ep-event-image { |
| 627 | margin-bottom: 16px; |
| 628 | border-radius: 8px; |
| 629 | overflow: hidden; |
| 630 | } |
| 631 | |
| 632 | .embedpress-event--modern .ep-event-image img { |
| 633 | width: 100%; |
| 634 | height: auto; |
| 635 | display: block; |
| 636 | transition: transform 0.3s ease; |
| 637 | } |
| 638 | |
| 639 | .embedpress-event--modern .ep-event-card:hover .ep-event-image img { |
| 640 | transform: scale(1.02); |
| 641 | } |
| 642 | |
| 643 | .embedpress-event--modern .ep-event-description p { |
| 644 | margin: 0 0 12px 0; |
| 645 | } |
| 646 | |
| 647 | .embedpress-event--modern .ep-event-description p:last-child { |
| 648 | margin-bottom: 0; |
| 649 | } |
| 650 | |
| 651 | .embedpress-event--modern .ep-event-description a { |
| 652 | color: #007cba; |
| 653 | text-decoration: underline; |
| 654 | transition: color 0.2s ease; |
| 655 | } |
| 656 | |
| 657 | .embedpress-event--modern .ep-event-description a:hover { |
| 658 | color: #005a87; |
| 659 | } |
| 660 | |
| 661 | .embedpress-event--modern .ep-event-description strong { |
| 662 | color: #111827; |
| 663 | font-weight: 600; |
| 664 | } |
| 665 | |
| 666 | /* Footer Section */ |
| 667 | .embedpress-event--modern .ep-event-footer { |
| 668 | padding: 20px 24px; |
| 669 | background: #f9fafb; |
| 670 | border-top: 1px solid #e5e7eb; |
| 671 | } |
| 672 | |
| 673 | .embedpress-event--modern .ep-event-cta { |
| 674 | display: inline-flex; |
| 675 | align-items: center; |
| 676 | gap: 8px; |
| 677 | padding: 10px 20px; |
| 678 | background: #007cba; |
| 679 | color: #ffffff; |
| 680 | font-size: 14px; |
| 681 | font-weight: 600; |
| 682 | border-radius: 6px; |
| 683 | text-decoration: none; |
| 684 | transition: all 0.2s ease; |
| 685 | } |
| 686 | |
| 687 | .embedpress-event--modern .ep-event-cta:hover { |
| 688 | background: #005a87; |
| 689 | transform: translateX(2px); |
| 690 | color: #ffffff; |
| 691 | } |
| 692 | |
| 693 | .embedpress-event--modern .ep-event-cta .ep-icon { |
| 694 | transition: transform 0.2s ease; |
| 695 | } |
| 696 | |
| 697 | .embedpress-event--modern .ep-event-cta:hover .ep-icon { |
| 698 | transform: translateX(3px); |
| 699 | } |
| 700 | |
| 701 | /* Responsive Design */ |
| 702 | @media (max-width: 640px) { |
| 703 | .embedpress-event--modern .ep-event-header, |
| 704 | .embedpress-event--modern .ep-event-content, |
| 705 | .embedpress-event--modern .ep-event-footer { |
| 706 | padding: 16px; |
| 707 | } |
| 708 | |
| 709 | .embedpress-event--modern .ep-event--title { |
| 710 | font-size: 20px; |
| 711 | } |
| 712 | |
| 713 | .embedpress-event--modern .ep-event-meta { |
| 714 | flex-direction: column; |
| 715 | gap: 8px; |
| 716 | } |
| 717 | |
| 718 | .embedpress-event--modern .ep-event-cta { |
| 719 | width: 100%; |
| 720 | justify-content: center; |
| 721 | } |
| 722 | } |
| 723 | </style> |
| 724 | |
| 725 | <?php |
| 726 | return ob_get_clean(); |
| 727 | } |
| 728 | |
| 729 | public function safe_style_css($styles) |
| 730 | { |
| 731 | $styles[] = 'position'; |
| 732 | $styles[] = 'display'; |
| 733 | $styles[] = 'opacity'; |
| 734 | $styles[] = 'box-sizing'; |
| 735 | $styles[] = 'left'; |
| 736 | $styles[] = 'bottom'; |
| 737 | $styles[] = 'right'; |
| 738 | $styles[] = 'top'; |
| 739 | return $styles; |
| 740 | } |
| 741 | |
| 742 | /** |
| 743 | * It checks for data in the node before returning. |
| 744 | * |
| 745 | * @param \simple_html_dom_node $node |
| 746 | * @param string $method |
| 747 | * @param string $attr_name |
| 748 | * |
| 749 | * @return string it returns data from the node if found or empty strings otherwise. |
| 750 | */ |
| 751 | public function embedpress_get_markup_from_node($node, $method = 'innertext', $attr_name = '') |
| 752 | { |
| 753 | if (!empty($node) && is_object($node)) { |
| 754 | if (!empty($attr_name)) { |
| 755 | return $node->getAttribute($attr_name); |
| 756 | } |
| 757 | if (!empty($method) && method_exists($node, $method)) { |
| 758 | return $node->{$method}(); |
| 759 | } |
| 760 | return ''; |
| 761 | } |
| 762 | return ''; |
| 763 | } |
| 764 | |
| 765 | function getLargestImage($srcsetString) |
| 766 | { |
| 767 | $images = array(); |
| 768 | // split on comma |
| 769 | $srcsetArray = explode(",", $srcsetString); |
| 770 | foreach ($srcsetArray as $srcString) { |
| 771 | // split on whitespace - optional descriptor |
| 772 | $imgArray = explode(" ", trim($srcString)); |
| 773 | // cast w or x descriptor as an Integer |
| 774 | $images[(int)$imgArray[1]] = $imgArray[0]; |
| 775 | } |
| 776 | // find the max |
| 777 | $maxIndex = max(array_keys($images)); |
| 778 | return $images[$maxIndex]; |
| 779 | } |
| 780 | |
| 781 | |
| 782 | |
| 783 | |
| 784 | |
| 785 | |
| 786 | |
| 787 | } |
| 788 |