PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents / 4.5.4
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents v4.5.4
4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 trunk 1.0.0 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.2.0 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.14 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.10 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 4.3.1 4.4.0 4.4.1 4.4.10 4.4.11 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5.0 4.5.1
embedpress / EmbedPress / ThirdParty / Googlecalendar / Embedpress_Google_Helper.php
embedpress / EmbedPress / ThirdParty / Googlecalendar Last commit date
assets 1 month ago Embedpress_Google_Helper.php 1 month ago GoogleClient.php 4 years ago
Embedpress_Google_Helper.php
991 lines
1 <?php
2
3 if ( !class_exists( 'EmbedPress_GoogleClient') ) {
4 require_once 'GoogleClient.php';
5 }
6 if ( !defined( 'EPGC_NOTICES_VERIFY_SUCCESS') ) {
7 define('EPGC_NOTICES_VERIFY_SUCCESS', 'Verify OK!');
8 define('EPGC_NOTICES_REVOKE_SUCCESS', 'Access revoked. This plugin does not have access to your calendars anymore.');
9 define('EPGC_NOTICES_REMOVE_SUCCESS', sprintf('Plugin data removed. Make sure to also manually revoke access to your calendars in the Google <a target="__blank" href="%s">Permissions</a> page!', 'https://myaccount.google.com/permissions'));
10 define('EPGC_NOTICES_CALENDARLIST_UPDATE_SUCCESS', 'Calendars updated.');
11 define('EPGC_NOTICES_COLORLIST_UPDATE_SUCCESS', 'Colors updated.');
12 define('EPGC_NOTICES_CACHE_DELETED', 'Cache deleted.');
13
14 define('EPGC_ERRORS_CLIENT_SECRET_MISSING', 'No client secret.');
15 define('EPGC_ERRORS_CLIENT_SECRET_INVALID', 'Invalid client secret.');
16 define('EPGC_ERRORS_ACCESS_TOKEN_MISSING', 'No access token.');
17 define('EPGC_ERRORS_REFRESH_TOKEN_MISSING', sprintf('Your refresh token is missing!<br><br>This can only be solved by manually revoking this plugin&#39;s access in the Google <a target="__blank" href="%s">Permissions</a> page and remove all plugin data.', 'https://myaccount.google.com/permissions'));
18 define('EPGC_ERRORS_ACCESS_REFRESH_TOKEN_MISSING', 'No access and refresh tokens.');
19 define('EPGC_ERRORS_REDIRECT_URI_MISSING', 'URI <code>%s</code> missing in the client secret file. Adjust your Google project and upload the new client secret file.');
20 define('EPGC_ERRORS_INVALID_FORMAT', 'Invalid format');
21 define('EPGC_ERRORS_NO_CALENDARS', 'No calendars');
22 define('EPGC_ERRORS_NO_SELECTED_CALENDARS', 'No selected calendars');
23 define('EPGC_ERRORS_TOKEN_AND_API_KEY_MISSING', 'Access token and API key are missing.');
24 define('EPGC_TRANSIENT_PREFIX', 'pgc_ev_');
25 define('EPGC_ENQUEUE_ACTION_PRIORITY', 11);
26 define( 'EPGC_REDIRECT_URL', admin_url('admin.php?page=embedpress&page_type=google-calendar'));
27 }
28 if (!defined('EPGC_EVENTS_MAX_RESULTS')) {
29 define('EPGC_EVENTS_MAX_RESULTS', 250);
30 }
31
32 if (!defined('EPGC_EVENTS_DEFAULT_TITLE')) {
33 define('EPGC_EVENTS_DEFAULT_TITLE', '');
34 }
35
36
37 if (!defined('EPGC_ASSET_URL')) {
38 define('EPGC_ASSET_URL', plugin_dir_url(__FILE__) .'assets/');
39 }
40
41 class Embedpress_Google_Helper {
42
43 public static function print_calendar_list($calendarList = []) {
44 if ( empty( $calendarList) ) {
45 $calendarList = static::getDecoded( 'epgc_calendarlist' ); //settings_selected_calendar_ids_json_cb
46 }
47 if ( ! empty( $calendarList ) ) {
48 $selectedCalendarIds = get_option( 'epgc_selected_calendar_ids' ); // array
49 if ( empty( $selectedCalendarIds ) ) {
50 $selectedCalendarIds = [];
51 }
52 ?>
53 <ul>
54 <?php foreach ( $calendarList as $calendar ) { ?>
55 <?php
56 $calendarId = $calendar['id'];
57 $htmlId = md5( $calendarId );
58 ?>
59 <p class="epgc-calendar-filter">
60 <input id="<?php echo $htmlId; ?>" type="checkbox" name="epgc_selected_calendar_ids[]"
61 <?php if ( in_array( $calendarId, $selectedCalendarIds ) ) {
62 echo ' checked ';
63 } ?>
64 value="<?php echo esc_attr( $calendarId ); ?>"/>
65 <label for="<?php echo $htmlId; ?>">
66 <span class="epgc-calendar-color" style="background-color:<?php echo esc_attr( $calendar['backgroundColor'] ); ?>"></span>
67 <?php echo esc_html( $calendar['summary'] ); ?><?php if ( ! empty( $calendar['primary'] ) ) {
68 echo ' (primary)';
69 } ?>
70 </label>
71 <br>ID: <?php echo esc_html( $calendarId ); ?>
72 </p>
73 <?php } ?>
74 </ul>
75 <?php
76 $refreshToken = get_option( "epgc_refresh_token" );
77 if ( empty( $refreshToken ) ) {
78 static::show_notice( EPGC_ERRORS_REFRESH_TOKEN_MISSING, 'error', false );
79 }
80 } else {
81 ?>
82 <p><?php _e( 'No calendar was found.', 'embedpress' ); ?></p>
83 <?php
84 }
85 }
86
87 /**
88 * Helper function to return array from option (that should be a JSON string).
89 * @return array or $default = null
90 */
91 public static function getDecoded($optionName, $default = null) {
92 $item = get_option($optionName);
93 // $item should be a JSON string.
94 if (!empty($item)) {
95 return json_decode($item, true);
96 }
97 return $default;
98 }
99
100
101 public static function show_notice($notice, $type, $dismissable) {
102 ?>
103 <div class="notice notice-<?php echo esc_attr($type); echo $dismissable ? ' is-dismissible' : ''; ?>">
104 <p><?php echo $notice; ?></p>
105 </div>
106 <?php
107 }
108
109 public static function ajax_get_calendar() {
110
111 // Nonces are user-session-bound, so a nonce minted for a logged-in author
112 // won't verify for an anonymous visitor served the same cached page HTML —
113 // the request would 403 even though the public calendar data is harmless
114 // to expose. Enforce the nonce only for authenticated requests; anon hits
115 // fall back to API-key/public-mode handling below.
116 if ( is_user_logged_in() ) {
117 check_ajax_referer('epgc_nonce');
118 }
119
120 // Capability gate only applies to OAuth mode — public API-key mode must
121 // stay reachable for anonymous visitors of the public calendar widget.
122 $hasAccessToken = get_option('epgc_access_token');
123
124 if ( ! empty( $hasAccessToken ) && is_user_logged_in() && ! current_user_can( 'read' ) ) {
125 wp_send_json_error( [ 'error' => 'Unauthorized' ], 403 );
126 }
127
128 try {
129
130 if (empty($_POST['start']) || empty($_POST['end'])) {
131 throw new Exception(EPGC_ERRORS_INVALID_FORMAT);
132 }
133
134 // Start and end are in ISO8601 string format with timezone offset (e.g. 2018-09-01T12:30:00-05:00)
135 $start = $_POST['start'];
136 $end = $_POST['end'];
137
138 $thisCalendarids = [];
139 $postedCalendarIds = [];
140 if (array_key_exists('thisCalendarids', $_POST) && !empty($_POST['thisCalendarids'])) {
141 $postedCalendarIds = array_map('trim', explode(',', $_POST['thisCalendarids']));
142 }
143 $privateSettingsCalendarListIds = array_map(function($item) {
144 return $item['id'];
145 }, static::getDecoded('epgc_calendarlist', []));
146 if (!empty($hasAccessToken)) {
147 // OAuth path: only IDs that are both known AND selected may pass.
148 // If the synced calendar list is missing (e.g. token saved but sync
149 // failed), refuse rather than fall through — otherwise a logged-in
150 // subscriber could submit `primary` and reach the admin's calendar
151 // via the stored bearer token.
152 $privateSettingsSelectedCalendarListIds = get_option('epgc_selected_calendar_ids');
153 if (empty($privateSettingsCalendarListIds) || empty($privateSettingsSelectedCalendarListIds)) {
154 throw new Exception(EPGC_ERRORS_NO_SELECTED_CALENDARS);
155 }
156 foreach ($postedCalendarIds as $calId) {
157 if (in_array($calId, $privateSettingsCalendarListIds, true) && in_array($calId, $privateSettingsSelectedCalendarListIds, true)) {
158 $thisCalendarids[] = $calId;
159 }
160 }
161 if (empty($thisCalendarids)) {
162 throw new Exception(EPGC_ERRORS_NO_SELECTED_CALENDARS);
163 }
164 } else {
165 // API-key (public) path: IDs come from widget/shortcode attributes
166 // and can only resolve to public Google calendars.
167 $thisCalendarids = $postedCalendarIds;
168 }
169
170 $cacheTime = get_option('epgc_cache_time'); // empty == no cache!
171
172 // We can have mutiple calendars with different calendar selections,
173 // so key should be including calendar selection.
174 $transientKey = EPGC_TRANSIENT_PREFIX . $start . $end . md5(implode('-', $thisCalendarids));
175
176 $transientItems = !empty($cacheTime) ? get_transient($transientKey) : false;
177
178 $calendarListByKey = static::get_calendars_by_key($thisCalendarids);
179
180 if ($transientItems !== false) {
181 wp_send_json(['items' => $transientItems, 'calendars' => $calendarListByKey]);
182 wp_die();
183 }
184
185 $colorList = false; // false means not queried yet / otherwise [] or filled []
186
187 $results = [];
188
189 $optParams = array(
190 'maxResults' => EPGC_EVENTS_MAX_RESULTS,
191 'orderBy' => 'startTime',
192 'singleEvents' => 'true',
193 'timeMin' => $start,
194 'timeMax' => $end,
195 );
196 if (!empty($_POST['timeZone'])) {
197 $optParams['timeZone'] = $_POST['timeZone'];
198 }
199
200 $hasAccessToken = get_option('epgc_access_token');
201
202 if (!empty($hasAccessToken)) {
203
204 $client = static::getGoogleClient(true);
205 if ($client->isAccessTokenExpired()) {
206 if (!$client->getRefreshTOken()) {
207 throw new Exception(EPGC_ERRORS_REFRESH_TOKEN_MISSING);
208 }
209 $client->refreshAccessToken();
210 }
211 $service = new EmbedPress_GoogleCalendarClient($client);
212
213 foreach ($thisCalendarids as $calendarId) {
214 $results[$calendarId] = $service->getEvents($calendarId, $optParams);
215 }
216
217 } elseif (!empty(get_option('epgc_api_key'))) {
218
219 $referer = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
220 $apiKey = get_option('epgc_api_key');
221 $service = new EmbedPress_GoogleCalendarClient(null);
222 foreach ($thisCalendarids as $calendarId) {
223 $results[$calendarId] = $service->getEventsPublic($calendarId, $optParams, $apiKey, $referer);
224 }
225
226 } else {
227 // No API key and no OAuth2 token
228 throw new Exception(EPGC_ERRORS_TOKEN_AND_API_KEY_MISSING);
229 }
230
231 $items = [];
232 foreach ($results as $calendarId => $events) {
233 foreach ($events as $item) {
234 $newItem = [
235 'title' => empty($item['summary']) ? EPGC_EVENTS_DEFAULT_TITLE : $item['summary'],
236 'htmlLink' => $item['htmlLink'],
237 'description' => !empty($item['description']) ? $item['description'] : '',
238 'calId' => $calendarId,
239 'creator' => !empty($item['creator']) ? $item['creator'] : [],
240 'attendees' => !empty($item['attendees']) ? $item['attendees'] : [],
241 'attachments' => !empty($item['attachments']) ? $item['attachments'] : [],
242 'location' => !empty($item['location']) ? $item['location'] : ''
243 ];
244 if (!empty($item['start']['date'])) {
245 $newItem['allDay'] = true;
246 $newItem['start'] = $item['start']['date'];
247 $newItem['end'] = $item['end']['date'];
248 // $newItem['timeZone'] = $item['start']['timeZone']; // TODO? end timezone also exists...
249 } else {
250 $newItem['start'] = $item['start']['dateTime'];
251 $newItem['end'] = $item['end']['dateTime'];
252 // $newItem['timeZone'] = $item['start']['timeZone']; // TODO? end timezone also exists...
253 }
254 if (!empty($item['colorId'])) {
255 if ($colorList === false) {
256 $colorList = static::getDecoded('pgc_colorlist', []);
257 }
258 if (array_key_exists('event', $colorList) && array_key_exists($item['colorId'], $colorList['event'])) {
259 $newItem['bColor'] = $colorList['event'][$item['colorId']]['background'];
260 $newItem['fColor'] = $colorList['event'][$item['colorId']]['foreground'];
261 }
262 }
263
264 $items[] = $newItem;
265 }
266 }
267
268 if (!empty($cacheTime)) {
269 set_transient($transientKey, $items, $cacheTime * MINUTE_IN_SECONDS);
270 }
271
272 wp_send_json(['items' => $items, 'calendars' => $calendarListByKey]);
273 wp_die();
274 } catch (EmbedPress_GoogleClient_RequestException $ex) {
275 wp_send_json([
276 'error' => $ex->getMessage(),
277 'errorCode' => $ex->getCode(),
278 'errorDescription' => $ex->getDescription()]);
279 wp_die();
280 } catch (Exception $ex) {
281 wp_send_json([
282 'error' => $ex->getMessage(),
283 'errorCode' => $ex->getCode()]);
284 wp_die();
285 }
286 }
287
288 public static function get_calendars_by_key($calendarIds) {
289
290 $publicCalendarList = get_option('pgc_public_calendarlist');
291 if (empty($publicCalendarList)) {
292 $publicCalendarList = [];
293 }
294 $privateCalendarList = static::getDecoded('epgc_calendarlist', []);
295 if (empty($privateCalendarList)) {
296 $privateCalendarList = [];
297 }
298 $calendarList = $publicCalendarList + $privateCalendarList;
299 $keyedCalendarList = [];
300 foreach ($calendarList as $cal) {
301 $keyedCalendarList[$cal['id']] = $cal;
302 }
303
304 $calendarListByKey = [];
305 foreach ($calendarIds as $calId) {
306 $cal = array_key_exists($calId, $keyedCalendarList) ? $keyedCalendarList[$calId] : [
307 'summary' => $calId,
308 'backgroundColor' => 'rgb(121, 134, 203)'
309 ];
310 $calendarListByKey[$calId] = [
311 'summary' => $cal['summary'],
312 'backgroundColor' => $cal['backgroundColor']
313 ];
314 }
315
316 return $calendarListByKey;
317 }
318 /**
319 * Helper function that returns a valid Google Client.
320 * @return Embedpress_GoogleClient instance
321 * @param bool $withTokens If true, also get tokens.
322 * @throws Exception.
323 */
324 public static function getGoogleClient($withTokens = false) {
325
326 $authConfig = get_option('epgc_client_secret');
327 if (empty($authConfig)) {
328 throw new Exception(EPGC_ERRORS_CLIENT_SECRET_MISSING);
329 }
330 $authConfig = static::getDecoded('epgc_client_secret');
331 if (empty($authConfig)) {
332 throw new Exception(EPGC_ERRORS_CLIENT_SECRET_INVALID);
333 }
334
335 $c = new Embedpress_GoogleClient($authConfig);
336 $c->setScope('https://www.googleapis.com/auth/calendar.readonly');
337 if (!self::check_redirect_uri($authConfig)) {
338 throw new Exception(sprintf(EPGC_ERRORS_REDIRECT_URI_MISSING, EPGC_REDIRECT_URL));
339 }
340 $c->setRedirectUri(EPGC_REDIRECT_URL);
341 $c->setTokenCallback(function($accessTokenInfo, $refreshToken) {
342 update_option('epgc_access_token', json_encode($accessTokenInfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), false);
343 if (!empty($refreshToken)) {
344 update_option('epgc_refresh_token', $refreshToken, false);
345 }
346 });
347
348 if ($withTokens) {
349 $accessToken = static::getDecoded('epgc_access_token');
350 if (empty($accessToken)) {
351 throw new Exception(EPGC_ERRORS_ACCESS_TOKEN_MISSING);
352 }
353 $c->setAccessTokenInfo($accessToken);
354 $refreshToken = get_option("epgc_refresh_token");
355 if (empty($refreshToken)) {
356 throw new Exception(EPGC_ERRORS_REFRESH_TOKEN_MISSING);
357 }
358 $c->setRefreshToken($refreshToken);
359 }
360
361 return $c;
362
363 }
364
365 /**
366 * Helper function to check if we have a valid redirect uri in the client secret.
367 * @return bool
368 */
369 public static function check_redirect_uri($decodedClientSecret) {
370 return !empty($decodedClientSecret)
371 && !empty($decodedClientSecret['web'])
372 && !empty($decodedClientSecret['web']['redirect_uris'])
373 && in_array(EPGC_REDIRECT_URL, $decodedClientSecret['web']['redirect_uris']);
374 }
375
376
377 /**
378 * Get a valid formatted client secret.
379 * @return array|false Secret Array, false if no exists, Exception for invalid one
380 **/
381 public static function get_valid_client_secret(&$error = '') {
382 $clientSecret = get_option('epgc_client_secret');
383 if (empty($clientSecret)) {
384 return false;
385 }
386 $clientSecret = static::getDecoded('epgc_client_secret');
387 if (empty($clientSecret)
388 || empty($clientSecret['web'])
389 || empty($clientSecret['web']['client_secret'])
390 || empty($clientSecret['web']['client_id']))
391 {
392 $error = EPGC_ERRORS_CLIENT_SECRET_INVALID;
393 } elseif (!self::check_redirect_uri($clientSecret))
394 {
395 $error = sprintf(EPGC_ERRORS_REDIRECT_URI_MISSING, admin_url('options-general.php?page=pgc'));
396 }
397 return $clientSecret;
398 }
399
400 public static function delete_calendar_cache() {
401 global $wpdb;
402 $wpdb->query("DELETE FROM " . $wpdb->options
403 . " WHERE option_name LIKE '_transient_timeout_" . EPGC_TRANSIENT_PREFIX . "%' OR option_name LIKE '_transient_" . EPGC_TRANSIENT_PREFIX . "%'");
404 }
405
406 /**
407 * Helper function to delete all plugin options.
408 */
409 public static function delete_options($which) { // which = all, public, private
410 if ($which === 'all' || $which === 'private') {
411 delete_option('epgc_access_token');
412 delete_option('epgc_refresh_token');
413 delete_option('epgc_selected_calendar_ids');
414 delete_option('epgc_calendarlist');
415 delete_option('epgc_client_secret');
416 }
417 if ($which === 'all' || $which === 'public') {
418 delete_option('epgc_api_key');
419 }
420 if ($which === 'all') {
421 delete_option('epgc_cache_time');
422 }
423 }
424
425 public static function uninstall() {
426 try {
427 $client = static::getGoogleClient();
428 $accessToken = static::getDecoded('epgc_access_token');
429 if (!empty($accessToken)) {
430 $client->setAccessTokenInfo($accessToken);
431 }
432 $refreshToken = get_option("epgc_refresh_token");
433 if (!empty($refreshToken)) {
434 $client->setRefreshToken($refreshToken);
435 }
436 if (empty($accessToken) && empty($refreshToken)) {
437 throw new Exception(EPGC_ERRORS_ACCESS_REFRESH_TOKEN_MISSING);
438 }
439 $client->revoke();
440 } catch (Exception $ex) {
441 // Too bad...
442 } finally {
443 // Clear all plugin data
444 static::delete_plugin_data();
445 }
446 }
447
448 /**
449 * Helper function to delete all plugin data.
450 */
451 public static function delete_plugin_data($which = 'all') {
452 self::delete_calendar_cache();
453 self::delete_options($which);
454 }
455 public static function removable_query_args($removable_query_args) {
456 $removable_query_args[] = 'epgcnotice';
457 return $removable_query_args;
458 }
459
460 public static function notices_init() {
461 if (!empty($_GET['epgcnotice'])) {
462 $epgcnotices = get_option('epgc_notices_' . get_current_user_id());
463 if (empty($epgcnotices)) {
464 return;
465 }
466 delete_option('epgc_notices_' . get_current_user_id());
467 add_action('admin_notices', function() use ($epgcnotices) {
468 foreach ($epgcnotices as $notice) {
469 ?>
470 <div class="notice notice-<?php echo esc_attr($notice['type']); ?> is-dismissible">
471 <p><?php echo esc_html($notice['content']); ?></p>
472 </div>
473 <?php
474 }
475 });
476 }
477 }
478
479 /**
480 * Helper function to add notice messages.
481 * @param bool $redirect Redirect if true.
482 */
483 public static function add_notice($content, $type = 'success', $redirect = false) {
484 $epgcnotices = get_option('epgc_notices_' . get_current_user_id());
485 if (empty($epgcnotices)) {
486 $epgcnotices = [];
487 }
488 $epgcnotices[] = [
489 'content' => $content,
490 'type' => $type
491 ];
492 update_option('epgc_notices_' . get_current_user_id(), $epgcnotices, false);
493 if ($redirect) {
494 wp_redirect(EPGC_REDIRECT_URL ."&epgcnotice=true");
495 }
496 }
497
498 /**
499 * Helper function die with different kind of errors.
500 */
501 public static function embedpress_die($error = null) {
502 $backLink = '<br><br><a href="' . admin_url('admin.php?page=embedpress&page_type=google-calendar') . '">' . __('Back', 'embedpress') . '</a>';
503 if (empty($error)) {
504 wp_die(__('Unknown error', 'embedpress') . $backLink);
505 }
506 if ($error instanceof Exception) {
507 $s = [];
508 if ($error->getCode()) {
509 $x[] = $error->getCode();
510 }
511 $s[] = $error->getMessage();
512 if ($error instanceof Embedpress_GoogleClient_RequestException) {
513 if ($error->getDescription()) {
514 $s[] = $error->getDescription();
515 }
516 }
517 wp_die(implode("<br>", $s) . $backLink);
518 } elseif (is_array($error)) {
519 wp_die(implode("<br>", $error) . $backLink);
520 } elseif (is_string($error)) {
521 wp_die($error . $backLink);
522 } else {
523 wp_die(__('Unknown error format', 'embedpress') . $backLink);
524 }
525 }
526
527 /**
528 * Helper function to return pretty printed JSON string.
529 * @return string
530 */
531 public static function getPrettyJSONString($jsonObject) {
532 return str_replace(" ", " ", json_encode($jsonObject, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
533 }
534
535 public static function sort_calendars(&$items) {
536 // Set locale to UTF-8 variant if this is not the case.
537 if (strpos(setlocale(LC_COLLATE, 0), '.UTF-8') === false) {
538 // If we set this to a non existing locale it will be the default locale after this call.
539 setlocale(LC_COLLATE, get_locale() . '.UTF-8');
540 }
541 usort($items, function($a, $b) {
542 return strcoll($a['summary'], $b['summary']);
543 });
544 }
545 public static function shortcode($atts = [], $content = null) {
546
547 // When we have no attributes, $atts is an empty string
548 if (!is_array($atts)) {
549 $atts = [];
550 }
551
552 foreach ($atts as &$value) {
553 $value = esc_attr($value);
554 }
555 unset($value); // Unset reference
556
557 // Assets are now handled by AssetManager
558 // Calendar-specific assets are loaded when calendar shortcode is used
559 $defaultConfig = [
560 'header' => [
561 'left' => 'prev,next today',
562 'center' => 'title',
563 'right' => 'dayGridMonth,timeGridWeek,listWeek'
564 ]
565 ];
566 $userConfig = $defaultConfig; // copy
567 $userFilter = 'top';
568 $userEventPopup = 'true';
569 $userEventLink = 'true';
570 $userHidePassed = 'false';
571 $userHideFuture = 'false';
572 $userEventDescription = 'true';
573 $userEventLocation = 'true';
574 $userEventAttendees = 'false';
575 $userEventAttachments = 'false';
576 $userEventCreator = 'false';
577 $userEventCalendarname = 'false';
578 $calendarIds = '';
579 $uncheckedCalendarIds = ''; // in filter
580 // Get all non-fullcalendar known properties
581 foreach ($atts as $key => $value) {
582 if ($key === 'public') {
583 // This existsed in old versions, but we don't want it in our shortcode output, so skip it.
584 continue;
585 }
586 if ($key === 'filter') {
587 $userFilter = $value === 'true' ? 'top' : $value;
588 continue;
589 }
590 if ($key === 'eventpopup') {
591 $userEventPopup = $value;
592 continue;
593 }
594 if ($key === 'eventlink') {
595 $userEventLink = $value;
596 continue;
597 }
598 if ($key === 'hidepassed') {
599 $userHidePassed = $value;
600 continue;
601 }
602 if ($key === 'hidefuture') {
603 $userHideFuture = $value;
604 continue;
605 }
606 if ($key === 'eventdescription') {
607 $userEventDescription = $value;
608 continue;
609 }
610 if ($key === 'eventattachments') {
611 $userEventAttachments = $value;
612 continue;
613 }
614 if ($key === 'eventattendees') {
615 $userEventAttendees = $value;
616 continue;
617 }
618 if ($key === 'eventlocation') {
619 $userEventLocation = $value;
620 continue;
621 }
622 if ($key === 'eventcreator') {
623 $userEventCreator = $value;
624 continue;
625 }
626 if ($key === 'eventcalendarname') {
627 $userEventCalendarname = $value;
628 continue;
629 }
630 if ($key === 'uncheckedcalendarids' && !empty($value)) {
631 $uncheckedCalendarIds = $value; // comma separated string
632 continue;
633 }
634
635 if ($key === 'calendarids') {
636 if (!empty($value)) {
637 $calendarIds = $value; // comma separated string
638 }
639 continue;
640 }
641 if ($key === 'fullcalendarconfig') {
642 // A JSON string that we can directly send to FullCalendar
643 $userConfig = json_decode($value, true);
644 } else {
645 // Fullcalendar properties that get passed to fullCalendar instance.
646 $parts = explode('-', $key);
647 $partsCount = count($parts);
648 if ($partsCount > 1) {
649 $currentUserConfigLayer = &$userConfig;
650 for ($i = 0; $i < $partsCount; $i++) {
651 $part = $parts[$i];
652 if ($i + 1 === $partsCount) {
653 if ($value === 'true') {
654 $value = true;
655 } elseif ($value === 'false') {
656 $value = $value;
657 }
658 $currentUserConfigLayer[$part] = $value;
659 } else {
660 if (!array_key_exists($part, $currentUserConfigLayer)) {
661 $currentUserConfigLayer[$part] = [];
662 }
663 $currentUserConfigLayer = &$currentUserConfigLayer[$part];
664 }
665 }
666 } else {
667 $userConfig[$key] = $value;
668 }
669 }
670 }
671
672 $dataCalendarIds = '';
673 if (!empty($calendarIds)) {
674 $dataCalendarIds = 'data-calendarids=\'' . json_encode(array_map('trim', explode(',', $calendarIds))) . '\'';
675 } else {
676 $privateSettingsSelectedCalendarListIds = get_option('epgc_selected_calendar_ids', []);
677 if (!empty($privateSettingsSelectedCalendarListIds)) {
678 $dataCalendarIds = 'data-calendarids=\'' . json_encode($privateSettingsSelectedCalendarListIds) . '\'';
679 }
680 }
681
682 $dataUnchekedCalendarIds = '';
683 if (!empty($uncheckedCalendarIds)) {
684 $dataUnchekedCalendarIds = 'data-uncheckedcalendarids=\'' . json_encode(array_map('trim', explode(',', $uncheckedCalendarIds))) . '\'';
685 }
686
687 $filterHTML = '<div class="epgc-calendar-filter" ' . $dataUnchekedCalendarIds . '></div>';
688
689 // Embed nonce + ajax URL directly on the wrapper so the front-end JS keeps
690 // working even when an optimizer/cache plugin strips the inline
691 // wp_localize_script() block that normally exposes `embedpressCalendarData`.
692 $wrapperData = ' data-nonce="' . esc_attr(wp_create_nonce('epgc_nonce'))
693 . '" data-ajaxurl="' . esc_url(admin_url('admin-ajax.php')) . '"';
694
695 return '<div class="epgc-calendar-wrapper epgc-calendar-page"' . $wrapperData . '>' . ($userFilter === 'top' ? wp_kses_post($filterHTML) : '') . '<div '
696
697 . $dataCalendarIds . ' data-filter="' . esc_attr($userFilter) . '" data-eventpopup="' . esc_attr($userEventPopup) . '" data-eventlink="'
698 . esc_attr($userEventLink) . '" data-eventdescription="' . esc_attr($userEventDescription) . '" data-eventlocation="'
699 . esc_attr($userEventLocation) . '" data-eventattachments="' . esc_attr($userEventAttachments) . '" data-eventattendees="'
700 . esc_attr($userEventAttendees) . '" data-eventcreator="' . esc_attr($userEventCreator) . '" data-eventcalendarname="'
701 . esc_attr($userEventCalendarname) . '" data-hidefuture="' . esc_attr($userHideFuture) . '" data-hidepassed="'
702 . esc_attr($userHidePassed) . '" data-config="' . esc_attr(json_encode($userConfig)) . '" data-locale="'
703 . esc_attr(get_locale()) . '" class="epgc-calendar"></div>' . ($userFilter === 'bottom' ? $filterHTML : '') . '</div>';
704 }
705
706 public static function admin_post_calendarlist() {
707 try {
708 $client = static::getGoogleClient(true);
709 if ($client->isAccessTokenExpired()) {
710 if (!$client->getRefreshToken()) {
711 throw new Exception(EPGC_ERRORS_REFRESH_TOKEN_MISSING);
712 }
713 $client->refreshAccessToken();
714 }
715 $service = new Embedpress_GoogleCalendarClient($client);
716 $items = $service->getCalendarList();
717
718 self::sort_calendars($items);
719
720 update_option('epgc_calendarlist', self::getPrettyJSONString($items), false);
721 self::add_notice(PGC_NOTICES_CALENDARLIST_UPDATE_SUCCESS, 'success', true);
722 exit;
723 } catch (Exception $ex) {
724 self::embedpress_die($ex);
725 }
726 }
727 public static function admin_post_colorlist() {
728 try {
729 $client = static::getGoogleClient(true);
730 if ($client->isAccessTokenExpired()) {
731 if (!$client->getRefreshToken()) {
732 throw new Exception(PGC_ERRORS_REFRESH_TOKEN_MISSING);
733 }
734 $client->refreshAccessToken();
735 }
736 $service = new Embedpress_GoogleCalendarClient($client);
737 $items = $service->getColorList();
738 update_option('epgc_colorlist', self::getPrettyJSONString($items), false);
739 self::add_notice(EPGC_NOTICES_COLORLIST_UPDATE_SUCCESS, 'success', true);
740 exit;
741 } catch (Exception $ex) {
742 self::embedpress_die($ex);
743 }
744 }
745 public static function admin_post_deletecache() {
746 if ( ! isset( $_POST['epgc_deletecache_data'] ) || ! wp_verify_nonce( $_POST['epgc_deletecache_data'], 'epgc_deletecache' ) || !current_user_can('manage_options')) {
747 print 'Sorry, your nonce did not verify.';
748 exit;
749 } else {
750 self::delete_calendar_cache();
751 self::add_notice(PGC_NOTICES_CACHE_DELETED, 'success', true);
752 exit;
753 }
754 }
755 public static function admin_post_verify() {
756 try {
757 $client = static::getGoogleClient(true);
758 $client->refreshAccessToken();
759 self::add_notice(PGC_NOTICES_VERIFY_SUCCESS, 'success', true);
760 exit;
761 } catch (Exception $ex) {
762 self::embedpress_die($ex);
763 }
764 }
765 /**
766 * Register + enqueue the FullCalendar assets in the Gutenberg block editor
767 * so the Calendar block's ServerSideRender preview can hydrate. Without
768 * this, the editor pulls the server-rendered .epgc-calendar-wrapper HTML
769 * but main.js never loads to attach FullCalendar to it.
770 */
771 public static function enqueue_block_editor_assets() {
772 self::enqueue_scripts(); // registers everything
773 wp_enqueue_style('epgc');
774 wp_enqueue_script('epgc');
775 wp_enqueue_script('fullcalendar_moment_timezone');
776 wp_enqueue_script('fullcalendar_daygrid');
777 wp_enqueue_script('fullcalendar_timegrid');
778 wp_enqueue_script('fullcalendar_list');
779 wp_enqueue_script('fullcalendar_locales');
780 wp_enqueue_script('tippy');
781 }
782
783 public static function enqueue_scripts() {
784 // Dashicons are now handled by AssetManager
785 wp_register_style('fullcalendar', EPGC_ASSET_URL . 'lib/fullcalendar4/core/main.min.css', null, EMBEDPRESS_VERSION);
786 wp_register_style('fullcalendar_daygrid', EPGC_ASSET_URL . 'lib/fullcalendar4/daygrid/main.min.css', ['fullcalendar'], EMBEDPRESS_VERSION);
787 wp_register_style('fullcalendar_timegrid', EPGC_ASSET_URL . 'lib/fullcalendar4/timegrid/main.min.css', ['fullcalendar_daygrid'], EMBEDPRESS_VERSION);
788 wp_register_style('fullcalendar_list', EPGC_ASSET_URL . 'lib/fullcalendar4/list/main.min.css', ['fullcalendar'], EMBEDPRESS_VERSION);
789 wp_register_style('epgc', EPGC_ASSET_URL . 'css/epgc.css', ['fullcalendar_timegrid'], EMBEDPRESS_VERSION);
790 wp_register_style('tippy_light', EPGC_ASSET_URL . 'lib/tippy/light-border.css', null, EMBEDPRESS_VERSION);
791
792 //wp_enqueue_style( 'fullcalendar');
793 //wp_enqueue_style( 'fullcalendar_daygrid');
794 //wp_enqueue_style( 'fullcalendar_timegrid');
795 //wp_enqueue_style( 'fullcalendar_list');
796 //wp_enqueue_style( 'epgc');
797 //wp_enqueue_style( 'tippy_light');
798
799
800 wp_register_script('popper',EPGC_ASSET_URL . 'lib/popper.min.js', null, EMBEDPRESS_VERSION, true);
801 wp_register_script('tippy',EPGC_ASSET_URL . 'lib/tippy/tippy-bundle.umd.min.js', ['popper'], EMBEDPRESS_VERSION, true);
802 wp_register_script('my_moment',EPGC_ASSET_URL . 'lib/moment/moment-with-locales.min.js', null, EMBEDPRESS_VERSION, true);
803 wp_register_script('my_moment_timezone',EPGC_ASSET_URL . 'lib/moment/moment-timezone-with-data.min.js', ['my_moment'], EMBEDPRESS_VERSION, true);
804 wp_register_script('fullcalendar',EPGC_ASSET_URL . 'lib/fullcalendar4/core/main.min.js', ['my_moment_timezone'], EMBEDPRESS_VERSION, true);
805 wp_register_script('fullcalendar_moment',EPGC_ASSET_URL . 'lib/fullcalendar4/moment/main.min.js', ['fullcalendar'], EMBEDPRESS_VERSION, true);
806 wp_register_script('fullcalendar_moment_timezone',EPGC_ASSET_URL . 'lib/fullcalendar4/moment-timezone/main.min.js', ['fullcalendar_moment'], EMBEDPRESS_VERSION, true);
807 wp_register_script('fullcalendar_daygrid',EPGC_ASSET_URL . 'lib/fullcalendar4/daygrid/main.min.js', ['fullcalendar'], EMBEDPRESS_VERSION, true);
808 wp_register_script('fullcalendar_timegrid',EPGC_ASSET_URL . 'lib/fullcalendar4/timegrid/main.min.js', ['fullcalendar_daygrid'], EMBEDPRESS_VERSION, true);
809 wp_register_script('fullcalendar_list',EPGC_ASSET_URL . 'lib/fullcalendar4/list/main.min.js', ['fullcalendar'], EMBEDPRESS_VERSION, true);
810 wp_register_script('fullcalendar_locales',EPGC_ASSET_URL . 'lib/fullcalendar4/core/locales-all.min.js',['fullcalendar'], EMBEDPRESS_VERSION, true);
811 wp_register_script('epgc', EPGC_ASSET_URL . 'js/main.js',['fullcalendar'], EMBEDPRESS_VERSION, true);
812
813 //wp_enqueue_script('popper');
814 //wp_enqueue_script('my_moment');
815 //wp_enqueue_script('my_moment_timezone');
816 //wp_enqueue_script('fullcalendar');
817 //wp_enqueue_script('fullcalendar_moment');
818 //wp_enqueue_script('fullcalendar_moment_timezone');
819 //wp_enqueue_script('fullcalendar_daygrid');
820 //wp_enqueue_script('fullcalendar_timegrid');
821 //wp_enqueue_script('fullcalendar_list');
822 //wp_enqueue_script('fullcalendar_locales');
823 //wp_enqueue_script('epgc');
824
825 // Localization is now handled by LocalizationManager
826 // This section is kept for backward compatibility but functionality has been moved
827
828 }
829
830 public static function remove_private_data() {
831 if ( ! isset( $_POST['epgc_remove_private_data'] ) || ! wp_verify_nonce( $_POST['epgc_remove_private_data'], 'epgc_remove_private' ) || !current_user_can('manage_options')) {
832 print 'Sorry, your nonce did not verify.';
833 exit;
834 } else {
835 self::delete_plugin_data('private');
836 self::add_notice(EPGC_NOTICES_REMOVE_SUCCESS, 'success', true);
837 exit;
838 }
839 }
840
841 public static function admin_post_remove() {
842
843 if ( ! isset( $_POST['epgc_remove_private_data'] ) || ! wp_verify_nonce( $_POST['epgc_remove_private_data'], 'epgc_remove_private' ) || !current_user_can('manage_options')) {
844 print 'Sorry, your nonce did not verify.';
845 exit;
846 } else {
847
848 self::delete_plugin_data();
849 self::add_notice(EPGC_NOTICES_REMOVE_SUCCESS, 'success', true);
850 exit;
851 }
852
853 }
854 public static function admin_post_revoke() {
855 try {
856 $client = self::getGoogleClient();
857 $accessToken = self::getDecoded('epgc_access_token');
858 if (!empty($accessToken)) {
859 $client->setAccessTokenInfo($accessToken);
860 }
861 $refreshToken = get_option("epgc_refresh_token");
862 if (!empty($refreshToken)) {
863 $client->setRefreshToken($refreshToken);
864 }
865 if (empty($accessToken) && empty($refreshToken)) {
866 throw new Exception(EPGC_ERRORS_ACCESS_REFRESH_TOKEN_MISSING);
867 }
868 $client->revoke();
869 // Clear access and refresh tokens
870 self::delete_plugin_data('private');
871 self::add_notice(EPGC_NOTICES_REVOKE_SUCCESS, 'success', true);
872 exit;
873 } catch (Exception $ex) {
874 self::embedpress_die($ex);
875 }
876 }
877 public static function admin_post_authorize() {
878 if ( ! isset( $_POST['epgc_authorize_data'] ) || ! wp_verify_nonce( $_POST['epgc_authorize_data'], 'epgc_authorize' ) || !current_user_can('manage_options')) {
879 print 'Sorry, your nonce did not verify.';
880 exit;
881 } else {
882 try {
883 $client = self::getGoogleClient();
884 $client->authorize();
885 exit;
886 } catch (Exception $ex) {
887 self::embedpress_die($ex);
888 }
889 }
890
891 }
892
893 public static function fetch_calendar() {
894 if ( empty( $_GET['page']) || 'embedpress' !== $_GET['page'] ) {
895 return;
896 }
897
898 if ( !current_user_can( 'manage_options') ) {
899 return;
900 }
901 if (!empty($_GET['code'])) {
902 // Redirect from Google authorize with code that we can use to get access and refresh tokens.
903 try {
904 $client = self::getGoogleClient();
905 // This will also set the access and refresh tokens on the client
906 // and call the token callback we have set to save them in the options table.
907 $client->handleCodeRedirect();
908 $service = new Embedpress_GoogleCalendarClient($client);
909 $items = $service->getCalendarList();
910 self::sort_calendars($items);
911
912 update_option('epgc_calendarlist', self::getPrettyJSONString($items), false);
913 wp_redirect(EPGC_REDIRECT_URL);
914 exit;
915 } catch (Exception $ex) {
916 self::embedpress_die($ex);
917 }
918
919 }
920
921 $clientSecretError = '';
922 $clientSecret = self::get_valid_client_secret($clientSecretError);
923
924 $accessToken = self::getDecoded('epgc_access_token');
925
926 if (empty($clientSecret) || !empty($clientSecretError)) {
927 update_option('epgc_selected_calendar_ids', [], false);
928 }
929 if (!empty($accessToken)) {
930 // validate_selected_calendar_ids
931 }
932 if (empty($clientSecret) || !empty($clientSecretError)) {
933 // save new data from user input, show them input
934
935 } elseif (self::getDecoded('epgc_calendarlist')) {
936 // show calendar list
937 }
938
939 }
940 }
941
942
943
944
945
946
947 /**
948 * Add 'eepgcnotice' to the removable_query_args filter, so we can set this and
949 * WP will remove it for us. We use this for our custom admin notices. This way
950 * you can add parameters to the URL and check for them, but we won't see them
951 * in the URL.
952 */
953 add_filter('removable_query_args', [Embedpress_Google_Helper::class, 'removable_query_args']);
954
955 /**
956 * Check for 'epgcnotice' parameter and show admin notice if we have a option.
957 */
958 add_action('admin_init', [Embedpress_Google_Helper::class,'notices_init']);
959
960 /**
961 * Handle AJAX request from frontend.
962 */
963 add_action('wp_ajax_epgc_ajax_get_calendar', [Embedpress_Google_Helper::class, 'ajax_get_calendar']);
964 add_action('wp_ajax_nopriv_epgc_ajax_get_calendar', [Embedpress_Google_Helper::class, 'ajax_get_calendar']);
965
966 add_action('admin_post_epgc_calendarlist', [Embedpress_Google_Helper::class,'admin_post_calendarlist']);
967
968
969 add_action('admin_post_epgc_colorlist', [Embedpress_Google_Helper::class, 'admin_post_colorlist']);
970 add_action('admin_post_epgc_deletecache', [Embedpress_Google_Helper::class, 'admin_post_deletecache']);
971
972
973 /**
974 * Admin post action to verify if we have valid access and refresh token.
975 */
976 add_action('admin_post_epgc_verify', [Embedpress_Google_Helper::class, 'admin_post_verify']);
977
978 add_shortcode( 'embedpress_calendar', [Embedpress_Google_Helper::class, 'shortcode']);
979 add_action('wp_enqueue_scripts', [Embedpress_Google_Helper::class, 'enqueue_scripts'], EPGC_ENQUEUE_ACTION_PRIORITY);
980 add_action('enqueue_block_editor_assets', [Embedpress_Google_Helper::class, 'enqueue_block_editor_assets'], EPGC_ENQUEUE_ACTION_PRIORITY);
981
982 add_action('admin_post_epgc_remove_private', [Embedpress_Google_Helper::class, 'remove_private_data']);
983
984 /**
985 * Admin post action to authorize access.
986 */
987 add_action('admin_post_epgc_authorize', [Embedpress_Google_Helper::class, 'admin_post_authorize']);
988
989 add_action('admin_init', [Embedpress_Google_Helper::class, 'fetch_calendar']);
990
991