All-in-One Event Calendar - Version 2.5.26

Version Description

Download this release

Release Info

Developer calvinyeh
Plugin Icon 128x128 All-in-One Event Calendar
Version 2.5.26
Comparing to
See all releases

Code changes from version 2.5.25 to 2.5.26

all-in-one-event-calendar.php CHANGED
@@ -5,7 +5,7 @@
5
  * Description: A calendar system with month, week, day, agenda views, upcoming events widget, color-coded categories, recurrence, and import/export of .ics feeds.
6
  * Author: Time.ly Network Inc.
7
  * Author URI: https://time.ly/
8
- * Version: 2.5.25
9
  * Text Domain: all-in-one-event-calendar
10
  * Domain Path: /language
11
  */
5
  * Description: A calendar system with month, week, day, agenda views, upcoming events widget, color-coded categories, recurrence, and import/export of .ics feeds.
6
  * Author: Time.ly Network Inc.
7
  * Author URI: https://time.ly/
8
+ * Version: 2.5.26
9
  * Text Domain: all-in-one-event-calendar
10
  * Domain Path: /language
11
  */
app/config/constants.php CHANGED
@@ -50,7 +50,7 @@ function ai1ec_initiate_constants( $ai1ec_base_dir, $ai1ec_base_url ) {
50
  // = Plugin Version =
51
  // ==================
52
  if ( ! defined( 'AI1EC_VERSION' ) ) {
53
- define( 'AI1EC_VERSION', '2.5.25' );
54
  }
55
 
56
  // ================
50
  // = Plugin Version =
51
  // ==================
52
  if ( ! defined( 'AI1EC_VERSION' ) ) {
53
+ define( 'AI1EC_VERSION', '2.5.26' );
54
  }
55
 
56
  // ================
app/model/api/api-abstract.php CHANGED
@@ -10,617 +10,617 @@
10
  */
11
  abstract class Ai1ec_Api_Abstract extends Ai1ec_App {
12
 
13
- const WP_OPTION_KEY = 'ai1ec_api_settings';
14
- const DEFAULT_TIMEOUT = 30;
15
-
16
- protected $_settings;
17
-
18
- /**
19
- * Post construction routine.
20
- *
21
- * Override this method to perform post-construction tasks.
22
- *
23
- * @return void Return from this method is ignored.
24
- */
25
- protected function _initialize() {
26
- $this->_settings = $this->_registry->get( 'model.settings' );
27
- }
28
-
29
- protected function get_ticketing_settings( $find_attribute = null, $default_value_attribute = null ) {
30
- $api_settings = get_option( self::WP_OPTION_KEY, null );
31
- if ( ! is_array( $api_settings ) ) {
32
- $api_settings = array(
33
- 'enabled' => $this->_settings->get( 'ticketing_enabled' ),
34
- 'message' => $this->_settings->get( 'ticketing_message' ),
35
- 'token' => $this->_settings->get( 'ticketing_token' ),
36
- 'calendar_id' => $this->_settings->get( 'ticketing_calendar_id' )
37
- );
38
- update_option( self::WP_OPTION_KEY, $api_settings );
39
- $this->_settings->set( 'ticketing_message' , '' );
40
- $this->_settings->set( 'ticketing_enabled' , false );
41
- $this->_settings->set( 'ticketing_token' , '' );
42
- $this->_settings->set( 'ticketing_calendar_id', null );
43
- }
44
- if ( is_null( $find_attribute ) ) {
45
- return $api_settings;
46
- } else {
47
- if ( isset( $api_settings[$find_attribute] ) ) {
48
- return $api_settings[$find_attribute];
49
- } else {
50
- return $default_value_attribute;
51
- }
52
- }
53
- }
54
-
55
- /**
56
- * @param String $message last message received from the Sign up or Sign in process
57
- * @param bool $enabled true or false is ticket is enabled
58
- * @param string $token autenthication token
59
- * @param int @calendar_id remote id of the calendar
60
- * @param string $account Email used to create the account
61
- */
62
- protected function save_ticketing_settings( $message, $enabled, $token, $calendar_id, $account ) {
63
- $api_settings = $this->get_ticketing_settings();
64
- $api_settings['message'] = $message;
65
- $api_settings['enabled'] = $enabled;
66
- $api_settings['token'] = $token;
67
- $api_settings['calendar_id'] = $calendar_id;
68
- $api_settings['account'] = $account;
69
- return update_option( self::WP_OPTION_KEY, $api_settings );
70
- }
71
-
72
- protected function clear_ticketing_settings() {
73
- delete_option( self::WP_OPTION_KEY );
74
-
75
- // Clear transient API data
76
- delete_transient( 'ai1ec_api_feeds_subscriptions' );
77
- delete_transient( 'ai1ec_api_subscriptions' );
78
- delete_transient( 'ai1ec_api_features' );
79
- delete_transient( 'ai1ec_api_checked' );
80
-
81
- $this->check_settings();
82
- }
83
-
84
- /**
85
- * Save the Payment settings localy (same saved on the API)
86
- * @param array Preferences to save
87
- */
88
- public function save_payment_settings( array $values ) {
89
- $api_settings = $this->get_ticketing_settings();
90
- if ( null !== $values ) {
91
- $api_settings['payment_settings'] = $values;
92
- } else {
93
- unset( $api_settings['payment_settings'] );
94
- }
95
- return update_option( self::WP_OPTION_KEY, $api_settings );
96
- }
97
-
98
- /**
99
- * Get the saved payments settings (the same saved on the API)
100
- */
101
- public function get_payment_settings() {
102
- return $this->get_ticketing_settings( 'payment_settings' );
103
- }
104
-
105
- /**
106
- * Check if the current WP instance has payments settings configured
107
- */
108
- public function has_payment_settings() {
109
- $payment_settings = $this->get_payment_settings();
110
- if ( null === $payment_settings ) {
111
- //code to migrate the settings save on ticketing api and
112
- //bring them to the core side
113
- $payment_settings = $this->get_payment_preferences();
114
- if ( is_object( $payment_settings ) ) {
115
- $payment_settings = (array) $payment_settings;
116
- }
117
- $this->save_payment_settings( (array) $payment_settings );
118
- }
119
- return ( null !== $payment_settings &&
120
- 'paypal' === $payment_settings['payment_method'] &&
121
- false === ai1ec_is_blank( $payment_settings['paypal_email'] ) ) ;
122
- }
123
-
124
-
125
- /**
126
- * @return object Response from API, or empty defaults
127
- */
128
- public function get_payment_preferences() {
129
- $calendar_id = $this->_get_ticket_calendar();
130
- $settings = null;
131
- if ( 0 < $calendar_id ) {
132
- $response = $this->request_api( 'GET', AI1EC_API_URL . "calendars/$calendar_id/payment",
133
- null, //no body
134
- true //decode response body
135
- );
136
- if ( $this->is_response_success( $response ) ) {
137
- $settings = $response->body;
138
- }
139
- }
140
- if ( is_null( $settings ) ) {
141
- return (object) array( 'payment_method'=>'paypal', 'paypal_email'=> '', 'first_name'=>'', 'last_name'=>'', 'currency'=> 'USD' );
142
- } else {
143
- if ( ! isset( $settings->currency ) ) {
144
- $settings->currency = 'USD';
145
- }
146
- return $settings;
147
- }
148
- }
149
-
150
-
151
- public function get_timely_token() {
152
- return $this->get_ticketing_settings( 'token' );
153
- }
154
-
155
- protected function save_calendar_id ( $calendar_id ) {
156
- $api_settings = $this->get_ticketing_settings();
157
- $api_settings['calendar_id'] = $calendar_id;
158
- return update_option( self::WP_OPTION_KEY, $api_settings );
159
- }
160
-
161
- /**
162
- * Get the header array with authorization token
163
- */
164
- protected function _get_headers( $custom_headers = null ) {
165
- $headers = array(
166
- 'content-type' => 'application/json'
167
- );
168
- $headers['Authorization'] = 'Basic ' . $this->get_ticketing_settings( 'token', '' );
169
- if ( null !== $custom_headers ) {
170
- foreach ( $custom_headers as $key => $value ) {
171
- if ( null === $value ) {
172
- unset( $headers[$key] );
173
- } else {
174
- $headers[$key] = $value;
175
- }
176
- }
177
- }
178
- return $headers;
179
- }
180
-
181
- /**
182
- * Create a standarized message to return
183
- * 1) If the API respond with http code 400 and with a JSON body, so, we will consider the API message to append in the base message.
184
- * 2) If the API does not responde with http code 400 or does not have a valid a JSON body, we will show the API URL and the http message error.
185
- */
186
- protected function _transform_error_message( $base_message, $response, $url, $ask_for_reload = false ) {
187
- $api_error = $this->get_api_error_msg( $response );
188
- $result = null;
189
- if ( false === ai1ec_is_blank( $api_error ) ) {
190
- $result = sprintf(
191
- __( '%s.<br/>Detail: %s.', AI1EC_PLUGIN_NAME ),
192
- $base_message, $api_error
193
- );
194
- } else {
195
- if ( is_wp_error( $response ) ) {
196
- $error_message = sprintf(
197
- __( 'API URL: %s.<br/>Detail: %s', AI1EC_PLUGIN_NAME ),
198
- $url,
199
- $response->get_error_message()
200
- );
201
- } else {
202
- $error_message = sprintf(
203
- __( 'API URL: %s.<br/>Detail: %s - %s', AI1EC_PLUGIN_NAME ),
204
- $url,
205
- wp_remote_retrieve_response_code( $response ),
206
- wp_remote_retrieve_response_message( $response )
207
- );
208
- $mailto = '<a href="mailto:labs@time.ly" target="_top">labs@time.ly</a>';
209
- if ( true === $ask_for_reload ) {
210
- $result = sprintf(
211
- __( '%s. Please reload this page to try again. If this error persists, please contact us at %s. In your report please include the information below.<br/>%s.', AI1EC_PLUGIN_NAME ),
212
- $base_message,
213
- $mailto,
214
- $error_message
215
- );
216
- } else {
217
- $result = sprintf(
218
- __( '%s. Please try again. If this error persists, please contact us at %s. In your report please include the information below.<br/>%s.', AI1EC_PLUGIN_NAME ),
219
- $base_message,
220
- $mailto,
221
- $error_message
222
- );
223
- }
224
- }
225
- }
226
- $result = trim( $result );
227
- $result = str_replace( '..', '.', $result );
228
- $result = str_replace( '.,', '.', $result );
229
- return $result;
230
- }
231
-
232
-
233
- /**
234
- * Search for the API message error
235
- */
236
- public function get_api_error_msg( $response ) {
237
- if ( isset( $response ) && false === is_wp_error( $response ) ) {
238
- $response_body = json_decode( $response['body'], true );
239
- if ( is_array( $response_body ) &&
240
- isset( $response_body['errors'] ) ) {
241
- $errors = $response_body['errors'];
242
- if ( false === is_array( $errors )) {
243
- $errors = array( $errors );
244
- }
245
- $messages = null;
246
- foreach ($errors as $key => $value) {
247
- if ( false === ai1ec_is_blank( $value ) ) {
248
- if ( is_array( $value ) ) {
249
- $value = implode ( ', ', $value );
250
- }
251
- $messages[] = $value;
252
- }
253
- }
254
- if ( null !== $messages && false === empty( $messages ) ) {
255
- return implode ( ', ', $messages);
256
- }
257
- }
258
- }
259
- return null;
260
- }
261
-
262
- /**
263
- * Get the ticket calendar from settings, if the calendar does not exists in
264
- * settings, then we will try to find on the API
265
- * @return string JSON.
266
- */
267
- protected function _get_ticket_calendar( $createIfNotExists = true ) {
268
- $ticketing_calendar_id = $this->get_ticketing_settings( 'calendar_id', 0 );
269
- if ( 0 < $ticketing_calendar_id ) {
270
- return $ticketing_calendar_id;
271
- } else {
272
- if ( ! $createIfNotExists ) {
273
- return 0;
274
- }
275
- // Try to find the calendar in the API
276
- $ticketing_calendar_id = $this->_find_user_calendar();
277
- if ( 0 < $ticketing_calendar_id ) {
278
- $this->save_calendar_id( $ticketing_calendar_id );
279
-
280
- return $ticketing_calendar_id;
281
- } else {
282
- // If the calendar doesn't exist in the API, create a new one
283
- $ticketing_calendar_id = $this->_create_calendar();
284
- if ( 0 < $ticketing_calendar_id ) {
285
- $this->save_calendar_id( $ticketing_calendar_id );
286
-
287
- return $ticketing_calendar_id;
288
- } else {
289
- return 0;
290
- }
291
- }
292
- }
293
- }
294
-
295
- /**
296
- * Find the existent calendar when the user is signing in
297
- */
298
- protected function _find_user_calendar() {
299
- $body = array(
300
- 'title' => get_bloginfo( 'name' ),
301
- 'url' => ai1ec_site_url()
302
- );
303
- $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars',
304
- json_encode( $body )
305
- );
306
- if ( $this->is_response_success( $response ) ) {
307
- if ( is_array( $response->body ) ) {
308
- return $response->body[0]->id;
309
- } else {
310
- return $response->body->id;
311
- }
312
- } else {
313
- return 0;
314
- }
315
- }
316
-
317
- /**
318
- * Create a calendar when the user is signup
319
- */
320
- protected function _create_calendar() {
321
- $body = array(
322
- 'title' => get_bloginfo( 'name' ),
323
- 'url' => ai1ec_site_url(),
324
- 'timezone' => $this->_settings->get( 'timezone_string' )
325
- );
326
- $response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars',
327
- json_encode( $body )
328
- );
329
- if ( $this->is_response_success( $response ) ) {
330
- return $response->body->id;
331
- } else {
332
- $this->log_error( $response, 'Created calendar' );
333
- return 0;
334
- }
335
- }
336
-
337
- /**
338
- * Check if the current WP instance is signed into the API
339
- */
340
- public function is_signed() {
341
- return ( true === $this->get_ticketing_settings( 'enabled', false ) );
342
- }
343
-
344
- public function check_settings( $force = false ) {
345
- $checked = get_transient( 'ai1ec_api_checked' );
346
-
347
- if ( false === $checked || $force ) {
348
- require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
349
-
350
- $failList = array();
351
- foreach ( Ai1ec_Api_Features::FEATURES as $key => $value ) {
352
- if ( empty( $value ) ) {
353
- continue;
354
- }
355
- if ( ( ! $this->is_signed() || ! $this->has_subscription_active( $key ) ) && call_user_func( 'is'.'_'.'pl'.'ug'.'in'.'_'.'ac'.'ti'.'ve', $value ) ) {
356
- $failList[] = $value;
357
- }
358
- }
359
-
360
- if ( count( $failList ) > 0 ) {
361
- call_user_func( 'de'.'act'.'iv'.'ate'.'_'.'pl'.'ug'.'ins', $failList );
362
-
363
- $message = 'Your ' .
364
- 'All-in-One Event Calendar ' .
365
- 'has the ' .
366
- 'following ' .
367
- 'plugins ' .
368
- 'installed ' .
369
- 'but they are ' .
370
- 'disabled. '.
371
- 'To keep ' .
372
- 'them ' .
373
- 'enabled'.
374
- ', simply '.
375
- 'keep ' .
376
- 'your calendar ' .
377
- 'logged in ' .
378
- 'to your '.
379
- 'Timely account.' .
380
- '<br /><br />';
381
-
382
- foreach ( $failList as $failed ) {
383
- $pieces = explode( '/', $failed );
384
- $message .= '- ' . $pieces[0] . '<br />';
385
- }
386
-
387
- $this->show_error( $message );
388
- }
389
-
390
- set_transient( 'ai1ec_api_checked', true, 5 * 60 );
391
- }
392
- }
393
-
394
- /**
395
- * Get the current email account
396
- */
397
- public function get_current_account() {
398
- return $this->get_ticketing_settings( 'account', '' );
399
- }
400
-
401
- /**
402
- * Get the current calendar id
403
- */
404
- public function get_current_calendar() {
405
- return $this->get_ticketing_settings( 'calendar_id', 0 );
406
- }
407
-
408
- /**
409
- * Get the last message return by Signup or Signup process
410
- */
411
- public function get_sign_message() {
412
- return $this->get_ticketing_settings( 'message', '' );
413
- }
414
-
415
- /**
416
- * Clear the last message return by Signup or Signup process
417
- */
418
- public function clear_sign_message() {
419
- $api_settings = $this->get_ticketing_settings();
420
- $api_settings['message'] = '';
421
- return update_option( self::WP_OPTION_KEY, $api_settings );
422
- }
423
-
424
- /**
425
- * @return array List of subscriptions and limits
426
- */
427
- protected function get_subscriptions( $force_refresh = false ) {
428
- $subscriptions = get_transient( 'ai1ec_api_subscriptions' );
429
-
430
- if ( false === $subscriptions || $force_refresh || ( defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG ) ) {
431
- $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/subscriptions',
432
- null,
433
- true
434
- );
435
- if ( $this->is_response_success( $response ) ) {
436
- $subscriptions = (array) $response->body;
437
- } else {
438
- $subscriptions = array();
439
- }
440
-
441
- // Save for 5 minutes
442
- $minutes = 5;
443
- set_transient( 'ai1ec_api_subscriptions', $subscriptions, $minutes * 60 );
444
- }
445
-
446
- return $subscriptions;
447
- }
448
-
449
- /**
450
- * Check if calendar should have a specific feature enabled
451
- */
452
- public function has_subscription_active( $feature ) {
453
- $subscriptions = $this->get_subscriptions();
454
-
455
- return array_key_exists( $feature, $subscriptions );
456
- }
457
-
458
- /**
459
- * Check if feature has reached its limit
460
- */
461
- public function subscription_has_reached_limit( $feature ) {
462
- $has_reached_limit = true;
463
-
464
- $provided = $this->subscription_get_quantity_limit( $feature );
465
- $used = $this->subscription_get_used_quantity( $feature );
466
-
467
- if ( $provided - $used > 0 ) {
468
- $has_reached_limit = false;
469
- }
470
-
471
- return $has_reached_limit;
472
- }
473
-
474
- /**
475
- * Get feature quantity limit
476
- */
477
- public function subscription_get_quantity_limit( $feature ) {
478
- $provided = 0;
479
-
480
- $subscriptions = $this->get_subscriptions();
481
-
482
- if ( array_key_exists( $feature, $subscriptions ) ) {
483
- $quantity = (array) $subscriptions[$feature];
484
-
485
- $provided = $quantity['provided'];
486
- }
487
-
488
- return $provided;
489
- }
490
-
491
- /**
492
- * Get feature used quantity
493
- */
494
- public function subscription_get_used_quantity( $feature ) {
495
- $used = 0;
496
-
497
- $subscriptions = $this->get_subscriptions();
498
-
499
- if ( array_key_exists( $feature, $subscriptions ) ) {
500
- $quantity = (array) $subscriptions[$feature];
501
-
502
- $used = $quantity['used'];
503
- }
504
-
505
- return $used;
506
- }
507
-
508
- /**
509
- * Make the request to the API endpons
510
- * @param $url The end part of the url to make the request.
511
- * $body The body to send the message
512
- * $method POST | GET | PUT, etc
513
- * or send a customized message to be showed in case of error
514
- * $decode_response_body TRUE (default) to decode the body response
515
- * @return stdClass with the the fields:
516
- * is_error TRUE or FALSE
517
- * error in case of is_error be true
518
- * body in case of is_error be false
519
- */
520
- protected function request_api( $method, $url, $body = null, $decode_response_body = true, $custom_headers = null ) {
521
- $request = array(
522
- 'method' => $method,
523
- 'headers' => $this->_get_headers( $custom_headers ),
524
- 'timeout' => self::DEFAULT_TIMEOUT
525
- );
526
- if ( ! is_null( $body ) ) {
527
- $request[ 'body' ] = $body;
528
- }
529
- $response = wp_remote_request( $url, $request );
530
- $result = new stdClass();
531
- $result->url = $url;
532
- $result->raw = $response;
533
- if ( is_wp_error( $response ) ) {
534
- $result->is_error = true;
535
- $result->error = $response->get_error_message();
536
- } else {
537
- $result->response_code = wp_remote_retrieve_response_code( $response );
538
- if ( 200 === $result->response_code ) {
539
- if ( true === $decode_response_body ) {
540
- $result->body = json_decode( $response['body'] );
541
- if ( false === is_null( $result->body ) ) {
542
- $result->is_error = false;
543
- } else {
544
- $result->is_error = true;
545
- $result->error = __( 'Error decoding the response', AI1EC_PLUGIN_NAME );
546
- unset( $result->body );
547
- }
548
- } else {
549
- $result->is_error = false;
550
- $result->body = $response['body'];
551
- }
552
- } else {
553
- $result->is_error = true;
554
- $result->error = wp_remote_retrieve_response_message( $response );
555
- }
556
- }
557
- return $result;
558
- }
559
-
560
- /**
561
- * Make a post request to the api
562
- * @param rest_endpoint Partial URL that can include {calendar_id} that will be replaced by the current calendar signed
563
- */
564
- public function call_api( $method, $endpoint, $body = null, $decode_response_body = true, $custom_headers = null ) {
565
- $calendar_id = $this->_get_ticket_calendar();
566
- if ( 0 >= $calendar_id ) {
567
- return false;
568
- }
569
- $url = AI1EC_API_URL . str_replace( '{calendar_id}', $calendar_id, $endpoint );
570
- $body = json_encode( $body );
571
- return $this->request_api( $method, $url, $body, $decode_response_body, $custom_headers );
572
- }
573
-
574
- /**
575
- * Save an error notification to be showed to the user on WP header of the page
576
- * @param $response The response got from request_api method.
577
- * $custom_error_message The custom message to show before the detailed message
578
- * @return full error message
579
- */
580
- protected function save_error_notification( $response, $custom_error_response ) {
581
- $error_message = $this->_transform_error_message(
582
- $custom_error_response,
583
- $response->raw,
584
- $response->url,
585
- true
586
- );
587
- $response->error_message = $error_message;
588
- $notification = $this->_registry->get( 'notification.admin' );
589
- $notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
590
- error_log( $custom_error_response . ': ' . $error_message . ' - raw error: ' . print_r( $response->raw, true ) );
591
- return $error_message;
592
- }
593
-
594
- /**
595
- * Save an error notification to be showed to the user on WP header of the page
596
- * @param $response The response got from request_api method.
597
- * $custom_error_message The custom message to show before the detailed message
598
- * @return full error message
599
- */
600
- protected function log_error( $response, $custom_error_response ) {
601
- $error_message = $this->_transform_error_message(
602
- $custom_error_response,
603
- $response->raw,
604
- $response->url,
605
- true
606
- );
607
- error_log( $custom_error_response . ': ' . $error_message . ' - raw error: ' . print_r( $response->raw, true ) );
608
- return $error_message;
609
- }
610
-
611
- protected function show_error( $error_message ) {
612
- $notification = $this->_registry->get( 'notification.admin' );
613
- $notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
614
- error_log( $error_message);
615
- return $error_message;
616
- }
617
-
618
- /**
619
- * Useful method to check if the response of request_api is a successful message
620
- */
621
- public function is_response_success( $response ) {
622
- return $response != null &&
623
- ( !isset( $response->is_error ) || ( isset( $response->is_error ) && false === $response->is_error ) );
624
- }
625
 
626
  }
10
  */
11
  abstract class Ai1ec_Api_Abstract extends Ai1ec_App {
12
 
13
+ const WP_OPTION_KEY = 'ai1ec_api_settings';
14
+ const DEFAULT_TIMEOUT = 30;
15
+
16
+ protected $_settings;
17
+
18
+ /**
19
+ * Post construction routine.
20
+ *
21
+ * Override this method to perform post-construction tasks.
22
+ *
23
+ * @return void Return from this method is ignored.
24
+ */
25
+ protected function _initialize() {
26
+ $this->_settings = $this->_registry->get( 'model.settings' );
27
+ }
28
+
29
+ protected function get_ticketing_settings( $find_attribute = null, $default_value_attribute = null ) {
30
+ $api_settings = get_option( self::WP_OPTION_KEY, null );
31
+ if ( ! is_array( $api_settings ) ) {
32
+ $api_settings = array(
33
+ 'enabled' => $this->_settings->get( 'ticketing_enabled' ),
34
+ 'message' => $this->_settings->get( 'ticketing_message' ),
35
+ 'token' => $this->_settings->get( 'ticketing_token' ),
36
+ 'calendar_id' => $this->_settings->get( 'ticketing_calendar_id' )
37
+ );
38
+ update_option( self::WP_OPTION_KEY, $api_settings );
39
+ $this->_settings->set( 'ticketing_message' , '' );
40
+ $this->_settings->set( 'ticketing_enabled' , false );
41
+ $this->_settings->set( 'ticketing_token' , '' );
42
+ $this->_settings->set( 'ticketing_calendar_id', null );
43
+ }
44
+ if ( is_null( $find_attribute ) ) {
45
+ return $api_settings;
46
+ } else {
47
+ if ( isset( $api_settings[$find_attribute] ) ) {
48
+ return $api_settings[$find_attribute];
49
+ } else {
50
+ return $default_value_attribute;
51
+ }
52
+ }
53
+ }
54
+
55
+ /**
56
+ * @param String $message last message received from the Sign up or Sign in process
57
+ * @param bool $enabled true or false is ticket is enabled
58
+ * @param string $token autenthication token
59
+ * @param int @calendar_id remote id of the calendar
60
+ * @param string $account Email used to create the account
61
+ */
62
+ protected function save_ticketing_settings( $message, $enabled, $token, $calendar_id, $account ) {
63
+ $api_settings = $this->get_ticketing_settings();
64
+ $api_settings['message'] = $message;
65
+ $api_settings['enabled'] = $enabled;
66
+ $api_settings['token'] = $token;
67
+ $api_settings['calendar_id'] = $calendar_id;
68
+ $api_settings['account'] = $account;
69
+ return update_option( self::WP_OPTION_KEY, $api_settings );
70
+ }
71
+
72
+ protected function clear_ticketing_settings() {
73
+ delete_option( self::WP_OPTION_KEY );
74
+
75
+ // Clear transient API data
76
+ delete_transient( 'ai1ec_api_feeds_subscriptions' );
77
+ delete_transient( 'ai1ec_api_subscriptions' );
78
+ delete_transient( 'ai1ec_api_features' );
79
+ delete_transient( 'ai1ec_api_checked' );
80
+
81
+ $this->check_settings();
82
+ }
83
+
84
+ /**
85
+ * Save the Payment settings localy (same saved on the API)
86
+ * @param array Preferences to save
87
+ */
88
+ public function save_payment_settings( array $values ) {
89
+ $api_settings = $this->get_ticketing_settings();
90
+ if ( null !== $values ) {
91
+ $api_settings['payment_settings'] = $values;
92
+ } else {
93
+ unset( $api_settings['payment_settings'] );
94
+ }
95
+ return update_option( self::WP_OPTION_KEY, $api_settings );
96
+ }
97
+
98
+ /**
99
+ * Get the saved payments settings (the same saved on the API)
100
+ */
101
+ public function get_payment_settings() {
102
+ return $this->get_ticketing_settings( 'payment_settings' );
103
+ }
104
+
105
+ /**
106
+ * Check if the current WP instance has payments settings configured
107
+ */
108
+ public function has_payment_settings() {
109
+ $payment_settings = $this->get_payment_settings();
110
+ if ( null === $payment_settings ) {
111
+ //code to migrate the settings save on ticketing api and
112
+ //bring them to the core side
113
+ $payment_settings = $this->get_payment_preferences();
114
+ if ( is_object( $payment_settings ) ) {
115
+ $payment_settings = (array) $payment_settings;
116
+ }
117
+ $this->save_payment_settings( (array) $payment_settings );
118
+ }
119
+ return ( null !== $payment_settings &&
120
+ 'paypal' === $payment_settings['payment_method'] &&
121
+ false === ai1ec_is_blank( $payment_settings['paypal_email'] ) ) ;
122
+ }
123
+
124
+
125
+ /**
126
+ * @return object Response from API, or empty defaults
127
+ */
128
+ public function get_payment_preferences() {
129
+ $calendar_id = $this->_get_ticket_calendar();
130
+ $settings = null;
131
+ if ( 0 < $calendar_id ) {
132
+ $response = $this->request_api( 'GET', AI1EC_API_URL . "calendars/$calendar_id/payment",
133
+ null, //no body
134
+ true //decode response body
135
+ );
136
+ if ( $this->is_response_success( $response ) ) {
137
+ $settings = $response->body;
138
+ }
139
+ }
140
+ if ( is_null( $settings ) ) {
141
+ return (object) array( 'payment_method'=>'paypal', 'paypal_email'=> '', 'first_name'=>'', 'last_name'=>'', 'currency'=> 'USD' );
142
+ } else {
143
+ if ( ! isset( $settings->currency ) ) {
144
+ $settings->currency = 'USD';
145
+ }
146
+ return $settings;
147
+ }
148
+ }
149
+
150
+
151
+ public function get_timely_token() {
152
+ return $this->get_ticketing_settings( 'token' );
153
+ }
154
+
155
+ protected function save_calendar_id ( $calendar_id ) {
156
+ $api_settings = $this->get_ticketing_settings();
157
+ $api_settings['calendar_id'] = $calendar_id;
158
+ return update_option( self::WP_OPTION_KEY, $api_settings );
159
+ }
160
+
161
+ /**
162
+ * Get the header array with authorization token
163
+ */
164
+ protected function _get_headers( $custom_headers = null ) {
165
+ $headers = array(
166
+ 'content-type' => 'application/json'
167
+ );
168
+ $headers['Authorization'] = 'Basic ' . $this->get_ticketing_settings( 'token', '' );
169
+ if ( null !== $custom_headers ) {
170
+ foreach ( $custom_headers as $key => $value ) {
171
+ if ( null === $value ) {
172
+ unset( $headers[$key] );
173
+ } else {
174
+ $headers[$key] = $value;
175
+ }
176
+ }
177
+ }
178
+ return $headers;
179
+ }
180
+
181
+ /**
182
+ * Create a standarized message to return
183
+ * 1) If the API respond with http code 400 and with a JSON body, so, we will consider the API message to append in the base message.
184
+ * 2) If the API does not responde with http code 400 or does not have a valid a JSON body, we will show the API URL and the http message error.
185
+ */
186
+ protected function _transform_error_message( $base_message, $response, $url, $ask_for_reload = false ) {
187
+ $api_error = $this->get_api_error_msg( $response );
188
+ $result = null;
189
+ if ( false === ai1ec_is_blank( $api_error ) ) {
190
+ $result = sprintf(
191
+ __( '%s.<br/>Detail: %s.', AI1EC_PLUGIN_NAME ),
192
+ $base_message, $api_error
193
+ );
194
+ } else {
195
+ if ( is_wp_error( $response ) ) {
196
+ $error_message = sprintf(
197
+ __( 'API URL: %s.<br/>Detail: %s', AI1EC_PLUGIN_NAME ),
198
+ $url,
199
+ $response->get_error_message()
200
+ );
201
+ } else {
202
+ $error_message = sprintf(
203
+ __( 'API URL: %s.<br/>Detail: %s - %s', AI1EC_PLUGIN_NAME ),
204
+ $url,
205
+ wp_remote_retrieve_response_code( $response ),
206
+ wp_remote_retrieve_response_message( $response )
207
+ );
208
+ $mailto = '<a href="mailto:labs@time.ly" target="_top">labs@time.ly</a>';
209
+ if ( true === $ask_for_reload ) {
210
+ $result = sprintf(
211
+ __( '%s. Please reload this page to try again. If this error persists, please contact us at %s. In your report please include the information below.<br/>%s.', AI1EC_PLUGIN_NAME ),
212
+ $base_message,
213
+ $mailto,
214
+ $error_message
215
+ );
216
+ } else {
217
+ $result = sprintf(
218
+ __( '%s. Please try again. If this error persists, please contact us at %s. In your report please include the information below.<br/>%s.', AI1EC_PLUGIN_NAME ),
219
+ $base_message,
220
+ $mailto,
221
+ $error_message
222
+ );
223
+ }
224
+ }
225
+ }
226
+ $result = trim( $result );
227
+ $result = str_replace( '..', '.', $result );
228
+ $result = str_replace( '.,', '.', $result );
229
+ return $result;
230
+ }
231
+
232
+
233
+ /**
234
+ * Search for the API message error
235
+ */
236
+ public function get_api_error_msg( $response ) {
237
+ if ( isset( $response ) && false === is_wp_error( $response ) ) {
238
+ $response_body = json_decode( $response['body'], true );
239
+ if ( is_array( $response_body ) &&
240
+ isset( $response_body['errors'] ) ) {
241
+ $errors = $response_body['errors'];
242
+ if ( false === is_array( $errors )) {
243
+ $errors = array( $errors );
244
+ }
245
+ $messages = null;
246
+ foreach ($errors as $key => $value) {
247
+ if ( false === ai1ec_is_blank( $value ) ) {
248
+ if ( is_array( $value ) ) {
249
+ $value = implode ( ', ', $value );
250
+ }
251
+ $messages[] = $value;
252
+ }
253
+ }
254
+ if ( null !== $messages && false === empty( $messages ) ) {
255
+ return implode ( ', ', $messages);
256
+ }
257
+ }
258
+ }
259
+ return null;
260
+ }
261
+
262
+ /**
263
+ * Get the ticket calendar from settings, if the calendar does not exists in
264
+ * settings, then we will try to find on the API
265
+ * @return string JSON.
266
+ */
267
+ protected function _get_ticket_calendar( $createIfNotExists = true ) {
268
+ $ticketing_calendar_id = $this->get_ticketing_settings( 'calendar_id', 0 );
269
+ if ( 0 < $ticketing_calendar_id ) {
270
+ return $ticketing_calendar_id;
271
+ } else {
272
+ if ( ! $createIfNotExists ) {
273
+ return 0;
274
+ }
275
+ // Try to find the calendar in the API
276
+ $ticketing_calendar_id = $this->_find_user_calendar();
277
+ if ( 0 < $ticketing_calendar_id ) {
278
+ $this->save_calendar_id( $ticketing_calendar_id );
279
+
280
+ return $ticketing_calendar_id;
281
+ } else {
282
+ // If the calendar doesn't exist in the API, create a new one
283
+ $ticketing_calendar_id = $this->_create_calendar();
284
+ if ( 0 < $ticketing_calendar_id ) {
285
+ $this->save_calendar_id( $ticketing_calendar_id );
286
+
287
+ return $ticketing_calendar_id;
288
+ } else {
289
+ return 0;
290
+ }
291
+ }
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Find the existent calendar when the user is signing in
297
+ */
298
+ protected function _find_user_calendar() {
299
+ $body = array(
300
+ 'title' => get_bloginfo( 'name' ),
301
+ 'url' => ai1ec_site_url()
302
+ );
303
+ $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars',
304
+ json_encode( $body )
305
+ );
306
+ if ( $this->is_response_success( $response ) ) {
307
+ if ( is_array( $response->body ) ) {
308
+ return $response->body[0]->id;
309
+ } else {
310
+ return $response->body->id;
311
+ }
312
+ } else {
313
+ return 0;
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Create a calendar when the user is signup
319
+ */
320
+ protected function _create_calendar() {
321
+ $body = array(
322
+ 'title' => get_bloginfo( 'name' ),
323
+ 'url' => ai1ec_site_url(),
324
+ 'timezone' => $this->_settings->get( 'timezone_string' )
325
+ );
326
+ $response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars',
327
+ json_encode( $body )
328
+ );
329
+ if ( $this->is_response_success( $response ) ) {
330
+ return $response->body->id;
331
+ } else {
332
+ $this->log_error( $response, 'Created calendar' );
333
+ return 0;
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Check if the current WP instance is signed into the API
339
+ */
340
+ public function is_signed() {
341
+ return ( true === $this->get_ticketing_settings( 'enabled', false ) );
342
+ }
343
+
344
+ public function check_settings( $force = false ) {
345
+ $checked = get_transient( 'ai1ec_api_checked' );
346
+
347
+ if ( false === $checked || $force ) {
348
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
349
+
350
+ $failList = array();
351
+ foreach ( Ai1ec_Api_Features::$features as $key => $value ) {
352
+ if ( empty( $value ) ) {
353
+ continue;
354
+ }
355
+ if ( ( ! $this->is_signed() || ! $this->has_subscription_active( $key ) ) && call_user_func( 'is'.'_'.'pl'.'ug'.'in'.'_'.'ac'.'ti'.'ve', $value ) ) {
356
+ $failList[] = $value;
357
+ }
358
+ }
359
+
360
+ if ( count( $failList ) > 0 ) {
361
+ call_user_func( 'de'.'act'.'iv'.'ate'.'_'.'pl'.'ug'.'ins', $failList );
362
+
363
+ $message = 'Your ' .
364
+ 'All-in-One Event Calendar ' .
365
+ 'has the ' .
366
+ 'following ' .
367
+ 'plugins ' .
368
+ 'installed ' .
369
+ 'but they are ' .
370
+ 'disabled. '.
371
+ 'To keep ' .
372
+ 'them ' .
373
+ 'enabled'.
374
+ ', simply '.
375
+ 'keep ' .
376
+ 'your calendar ' .
377
+ 'logged in ' .
378
+ 'to your '.
379
+ 'Timely account.' .
380
+ '<br /><br />';
381
+
382
+ foreach ( $failList as $failed ) {
383
+ $pieces = explode( '/', $failed );
384
+ $message .= '- ' . $pieces[0] . '<br />';
385
+ }
386
+
387
+ $this->show_error( $message );
388
+ }
389
+
390
+ set_transient( 'ai1ec_api_checked', true, 5 * 60 );
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Get the current email account
396
+ */
397
+ public function get_current_account() {
398
+ return $this->get_ticketing_settings( 'account', '' );
399
+ }
400
+
401
+ /**
402
+ * Get the current calendar id
403
+ */
404
+ public function get_current_calendar() {
405
+ return $this->get_ticketing_settings( 'calendar_id', 0 );
406
+ }
407
+
408
+ /**
409
+ * Get the last message return by Signup or Signup process
410
+ */
411
+ public function get_sign_message() {
412
+ return $this->get_ticketing_settings( 'message', '' );
413
+ }
414
+
415
+ /**
416
+ * Clear the last message return by Signup or Signup process
417
+ */
418
+ public function clear_sign_message() {
419
+ $api_settings = $this->get_ticketing_settings();
420
+ $api_settings['message'] = '';
421
+ return update_option( self::WP_OPTION_KEY, $api_settings );
422
+ }
423
+
424
+ /**
425
+ * @return array List of subscriptions and limits
426
+ */
427
+ protected function get_subscriptions( $force_refresh = false ) {
428
+ $subscriptions = get_transient( 'ai1ec_api_subscriptions' );
429
+
430
+ if ( false === $subscriptions || $force_refresh || ( defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG ) ) {
431
+ $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/subscriptions',
432
+ null,
433
+ true
434
+ );
435
+ if ( $this->is_response_success( $response ) ) {
436
+ $subscriptions = (array) $response->body;
437
+ } else {
438
+ $subscriptions = array();
439
+ }
440
+
441
+ // Save for 5 minutes
442
+ $minutes = 5;
443
+ set_transient( 'ai1ec_api_subscriptions', $subscriptions, $minutes * 60 );
444
+ }
445
+
446
+ return $subscriptions;
447
+ }
448
+
449
+ /**
450
+ * Check if calendar should have a specific feature enabled
451
+ */
452
+ public function has_subscription_active( $feature ) {
453
+ $subscriptions = $this->get_subscriptions();
454
+
455
+ return array_key_exists( $feature, $subscriptions );
456
+ }
457
+
458
+ /**
459
+ * Check if feature has reached its limit
460
+ */
461
+ public function subscription_has_reached_limit( $feature ) {
462
+ $has_reached_limit = true;
463
+
464
+ $provided = $this->subscription_get_quantity_limit( $feature );
465
+ $used = $this->subscription_get_used_quantity( $feature );
466
+
467
+ if ( $provided - $used > 0 ) {
468
+ $has_reached_limit = false;
469
+ }
470
+
471
+ return $has_reached_limit;
472
+ }
473
+
474
+ /**
475
+ * Get feature quantity limit
476
+ */
477
+ public function subscription_get_quantity_limit( $feature ) {
478
+ $provided = 0;
479
+
480
+ $subscriptions = $this->get_subscriptions();
481
+
482
+ if ( array_key_exists( $feature, $subscriptions ) ) {
483
+ $quantity = (array) $subscriptions[$feature];
484
+
485
+ $provided = $quantity['provided'];
486
+ }
487
+
488
+ return $provided;
489
+ }
490
+
491
+ /**
492
+ * Get feature used quantity
493
+ */
494
+ public function subscription_get_used_quantity( $feature ) {
495
+ $used = 0;
496
+
497
+ $subscriptions = $this->get_subscriptions();
498
+
499
+ if ( array_key_exists( $feature, $subscriptions ) ) {
500
+ $quantity = (array) $subscriptions[$feature];
501
+
502
+ $used = $quantity['used'];
503
+ }
504
+
505
+ return $used;
506
+ }
507
+
508
+ /**
509
+ * Make the request to the API endpons
510
+ * @param $url The end part of the url to make the request.
511
+ * $body The body to send the message
512
+ * $method POST | GET | PUT, etc
513
+ * or send a customized message to be showed in case of error
514
+ * $decode_response_body TRUE (default) to decode the body response
515
+ * @return stdClass with the the fields:
516
+ * is_error TRUE or FALSE
517
+ * error in case of is_error be true
518
+ * body in case of is_error be false
519
+ */
520
+ protected function request_api( $method, $url, $body = null, $decode_response_body = true, $custom_headers = null ) {
521
+ $request = array(
522
+ 'method' => $method,
523
+ 'headers' => $this->_get_headers( $custom_headers ),
524
+ 'timeout' => self::DEFAULT_TIMEOUT
525
+ );
526
+ if ( ! is_null( $body ) ) {
527
+ $request[ 'body' ] = $body;
528
+ }
529
+ $response = wp_remote_request( $url, $request );
530
+ $result = new stdClass();
531
+ $result->url = $url;
532
+ $result->raw = $response;
533
+ if ( is_wp_error( $response ) ) {
534
+ $result->is_error = true;
535
+ $result->error = $response->get_error_message();
536
+ } else {
537
+ $result->response_code = wp_remote_retrieve_response_code( $response );
538
+ if ( 200 === $result->response_code ) {
539
+ if ( true === $decode_response_body ) {
540
+ $result->body = json_decode( $response['body'] );
541
+ if ( false === is_null( $result->body ) ) {
542
+ $result->is_error = false;
543
+ } else {
544
+ $result->is_error = true;
545
+ $result->error = __( 'Error decoding the response', AI1EC_PLUGIN_NAME );
546
+ unset( $result->body );
547
+ }
548
+ } else {
549
+ $result->is_error = false;
550
+ $result->body = $response['body'];
551
+ }
552
+ } else {
553
+ $result->is_error = true;
554
+ $result->error = wp_remote_retrieve_response_message( $response );
555
+ }
556
+ }
557
+ return $result;
558
+ }
559
+
560
+ /**
561
+ * Make a post request to the api
562
+ * @param rest_endpoint Partial URL that can include {calendar_id} that will be replaced by the current calendar signed
563
+ */
564
+ public function call_api( $method, $endpoint, $body = null, $decode_response_body = true, $custom_headers = null ) {
565
+ $calendar_id = $this->_get_ticket_calendar();
566
+ if ( 0 >= $calendar_id ) {
567
+ return false;
568
+ }
569
+ $url = AI1EC_API_URL . str_replace( '{calendar_id}', $calendar_id, $endpoint );
570
+ $body = json_encode( $body );
571
+ return $this->request_api( $method, $url, $body, $decode_response_body, $custom_headers );
572
+ }
573
+
574
+ /**
575
+ * Save an error notification to be showed to the user on WP header of the page
576
+ * @param $response The response got from request_api method.
577
+ * $custom_error_message The custom message to show before the detailed message
578
+ * @return full error message
579
+ */
580
+ protected function save_error_notification( $response, $custom_error_response ) {
581
+ $error_message = $this->_transform_error_message(
582
+ $custom_error_response,
583
+ $response->raw,
584
+ $response->url,
585
+ true
586
+ );
587
+ $response->error_message = $error_message;
588
+ $notification = $this->_registry->get( 'notification.admin' );
589
+ $notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
590
+ error_log( $custom_error_response . ': ' . $error_message . ' - raw error: ' . print_r( $response->raw, true ) );
591
+ return $error_message;
592
+ }
593
+
594
+ /**
595
+ * Save an error notification to be showed to the user on WP header of the page
596
+ * @param $response The response got from request_api method.
597
+ * $custom_error_message The custom message to show before the detailed message
598
+ * @return full error message
599
+ */
600
+ protected function log_error( $response, $custom_error_response ) {
601
+ $error_message = $this->_transform_error_message(
602
+ $custom_error_response,
603
+ $response->raw,
604
+ $response->url,
605
+ true
606
+ );
607
+ error_log( $custom_error_response . ': ' . $error_message . ' - raw error: ' . print_r( $response->raw, true ) );
608
+ return $error_message;
609
+ }
610
+
611
+ protected function show_error( $error_message ) {
612
+ $notification = $this->_registry->get( 'notification.admin' );
613
+ $notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
614
+ error_log( $error_message);
615
+ return $error_message;
616
+ }
617
+
618
+ /**
619
+ * Useful method to check if the response of request_api is a successful message
620
+ */
621
+ public function is_response_success( $response ) {
622
+ return $response != null &&
623
+ ( !isset( $response->is_error ) || ( isset( $response->is_error ) && false === $response->is_error ) );
624
+ }
625
 
626
  }
app/model/api/api-features.php CHANGED
@@ -21,7 +21,7 @@ class Ai1ec_Api_Features {
21
  const CODE_VENUES = 'venues';
22
  const CODE_IMPORT_FEEDS = 'import-feeds';
23
 
24
- const FEATURES = array(
25
  self::CODE_API_ACCESS => '',
26
  self::CODE_TICKETING => '',
27
  self::CODE_TWITTER => 'all-in-one-event-calendar-twitter-integration/all-in-one-event-calendar-twitter-integration.php',
21
  const CODE_VENUES = 'venues';
22
  const CODE_IMPORT_FEEDS = 'import-feeds';
23
 
24
+ public static $features = array(
25
  self::CODE_API_ACCESS => '',
26
  self::CODE_TICKETING => '',
27
  self::CODE_TWITTER => 'all-in-one-event-calendar-twitter-integration/all-in-one-event-calendar-twitter-integration.php',
language/all-in-one-event-calendar.mo CHANGED
Binary file
language/all-in-one-event-calendar.po CHANGED
@@ -2,13 +2,13 @@
2
  # This file is distributed under the same license as the All-in-One Event Calendar by Time.ly package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: All-in-One Event Calendar by Time.ly 2.5.25\n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/all-in-one-event-calendar\n"
7
- "POT-Creation-Date: 2017-09-18 16:19:18+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
- "PO-Revision-Date: 2017-09-18 16:19+0000\n"
12
  "Last-Translator: Timely <support@time.ly>\n"
13
  "Language-Team:\n"
14
 
2
  # This file is distributed under the same license as the All-in-One Event Calendar by Time.ly package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: All-in-One Event Calendar by Time.ly 2.5.26\n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/all-in-one-event-calendar\n"
7
+ "POT-Creation-Date: 2017-09-18 18:14:23+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2017-09-18 18:14+0000\n"
12
  "Last-Translator: Timely <support@time.ly>\n"
13
  "Language-Team:\n"
14
 
language/all-in-one-event-calendar.pot CHANGED
@@ -2,9 +2,9 @@
2
  # This file is distributed under the same license as the All-in-One Event Calendar by Time.ly package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: All-in-One Event Calendar by Time.ly 2.5.25\n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/all-in-one-event-calendar\n"
7
- "POT-Creation-Date: 2017-09-18 16:19:18+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
2
  # This file is distributed under the same license as the All-in-One Event Calendar by Time.ly package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: All-in-One Event Calendar by Time.ly 2.5.26\n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/all-in-one-event-calendar\n"
7
+ "POT-Creation-Date: 2017-09-18 18:14:23+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: uchu, calvinyeh, raquelleira, renatotbueno, domanb, elirenato, hubrik, vtowel, yani.iliev, nicolapeluchetti, jbutkus, lpawlik, bangelov
3
  Tags: calendar, event, ical, feed, ics, icalendar, sync, aggregator, google, venue, calendar widget, events widget
4
  Requires at least: 3.5
5
- Tested up to: 4.8.1
6
- Stable tag: 2.5.25
7
  License: GNU General Public License, version 3 (GPL-3.0)
8
 
9
  An events calendar system with multiple views, upcoming events widget, color-coded categories, recurrence, and import/export of .ics feeds.
@@ -129,6 +129,9 @@ https://vimeo.com/135004810
129
 
130
  == Changelog ==
131
 
 
 
 
132
  = Version 2.5.25 =
133
  * Fixed: Compatibility with PHP < 5.4.
134
 
2
  Contributors: uchu, calvinyeh, raquelleira, renatotbueno, domanb, elirenato, hubrik, vtowel, yani.iliev, nicolapeluchetti, jbutkus, lpawlik, bangelov
3
  Tags: calendar, event, ical, feed, ics, icalendar, sync, aggregator, google, venue, calendar widget, events widget
4
  Requires at least: 3.5
5
+ Tested up to: 4.8.2
6
+ Stable tag: 2.5.26
7
  License: GNU General Public License, version 3 (GPL-3.0)
8
 
9
  An events calendar system with multiple views, upcoming events widget, color-coded categories, recurrence, and import/export of .ics feeds.
129
 
130
  == Changelog ==
131
 
132
+ = Version 2.5.26 =
133
+ * Fixed: Compatibility with PHP < 5.4.
134
+
135
  = Version 2.5.25 =
136
  * Fixed: Compatibility with PHP < 5.4.
137