All-in-One Event Calendar - Version 2.5.24

Version Description

Download this release

Release Info

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

Code changes from version 2.5.23 to 2.5.24

Files changed (62) hide show
  1. all-in-one-event-calendar.php +23 -23
  2. app/config/constants.php +476 -476
  3. app/controller/calendar-feeds.php +65 -65
  4. app/controller/content-filter.php +44 -44
  5. app/controller/extension-license.php +96 -96
  6. app/controller/extension.php +253 -253
  7. app/controller/front.php +1155 -1149
  8. app/controller/import-export.php +91 -91
  9. app/controller/javascript-widget.php +232 -232
  10. app/controller/javascript.php +901 -901
  11. app/controller/shutdown.php +84 -84
  12. app/model/api/api-abstract.php +585 -526
  13. app/model/api/api-features.php +42 -19
  14. app/model/api/api-feeds.php +308 -308
  15. app/model/api/api-registration.php +238 -238
  16. app/model/api/api-settings.php +1 -1
  17. app/model/api/api-ticketing.php +863 -863
  18. app/model/app.php +18 -18
  19. app/model/event-compatibility.php +89 -89
  20. app/model/event.php +880 -880
  21. app/model/event/creating.php +489 -486
  22. app/model/event/entity.php +355 -355
  23. app/model/event/instance.php +368 -368
  24. app/model/event/legacy.php +122 -122
  25. app/model/event/parent.php +345 -345
  26. app/model/event/taxonomy.php +170 -170
  27. app/model/event/trashing.php +186 -186
  28. app/model/filter/auth_ids.php +3 -3
  29. app/model/filter/cat_ids.php +3 -3
  30. app/model/filter/instance_ids.php +3 -3
  31. app/model/filter/int.php +55 -55
  32. app/model/filter/interface.php +27 -27
  33. app/model/filter/post_ids.php +3 -3
  34. app/model/filter/tag_ids.php +3 -3
  35. app/model/filter/taxonomy.php +79 -79
  36. app/model/meta-user.php +79 -79
  37. app/model/meta.php +166 -166
  38. app/model/option.php +98 -98
  39. app/model/review.php +165 -165
  40. app/model/search.php +863 -863
  41. app/model/settings-view.php +144 -144
  42. app/model/settings.php +1049 -1049
  43. app/model/taxonomy.php +174 -174
  44. app/view/admin/abstract.php +53 -53
  45. app/view/admin/add-new-event.php +559 -534
  46. app/view/admin/add-ons.php +80 -80
  47. app/view/admin/all-events.php +239 -239
  48. app/view/admin/calendar-feeds.php +90 -90
  49. app/view/admin/event-category.php +225 -225
  50. app/view/admin/get-repeat-box.php +653 -653
  51. app/view/admin/get-tax-box.php +16 -16
  52. app/view/admin/nav.php +22 -22
  53. app/view/admin/organize.php +87 -87
  54. app/view/admin/samples.php +62 -0
  55. app/view/admin/settings.php +322 -322
  56. app/view/admin/theme-options.php +210 -210
  57. app/view/admin/theme-switching.php +46 -46
  58. app/view/admin/tickets.php +179 -179
  59. app/view/admin/widget-creator.php +127 -127
  60. app/view/calendar/fallbacks.php +28 -28
  61. app/view/calendar/page.php +508 -508
  62. app/view/calendar/shortcode.php +0 -0
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.21
9
  * Text Domain: all-in-one-event-calendar
10
  * Domain Path: /language
11
  */
@@ -13,52 +13,52 @@ $ai1ec_base_dir = dirname( __FILE__ );
13
  $ai1ec_base_url = plugins_url( '', __FILE__ );
14
 
15
  $ai1ec_config_path = $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'app' .
16
- DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR;
17
 
18
  // Include configuration files and initiate global constants as they are used
19
  // By the error/exception handler too.
20
  foreach ( array( 'constants-local.php', 'constants.php' ) as $file ) {
21
- if ( is_file( $ai1ec_config_path . $file ) ) {
22
- require_once $ai1ec_config_path . $file;
23
- }
24
  }
25
 
26
  if ( ! function_exists( 'ai1ec_initiate_constants' ) ) {
27
- throw new Ai1ec_Exception(
28
- 'No constant file was found.'
29
- );
30
  }
31
  ai1ec_initiate_constants( $ai1ec_base_dir, $ai1ec_base_url );
32
 
33
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
34
- DIRECTORY_SEPARATOR . 'exception' . DIRECTORY_SEPARATOR . 'ai1ec.php';
35
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
36
- DIRECTORY_SEPARATOR . 'exception' . DIRECTORY_SEPARATOR . 'error.php';
37
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
38
- DIRECTORY_SEPARATOR . 'exception' . DIRECTORY_SEPARATOR . 'handler.php';
39
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
40
- DIRECTORY_SEPARATOR . 'http' . DIRECTORY_SEPARATOR . 'response' .
41
- DIRECTORY_SEPARATOR . 'helper.php';
42
  $ai1ec_exception_handler = new Ai1ec_Exception_Handler(
43
- 'Ai1ec_Exception',
44
- 'Ai1ec_Error_Exception'
45
  );
46
 
47
 
48
  // if the user clicked the link to reactivate the plugin
49
  if ( isset( $_GET[Ai1ec_Exception_Handler::DB_REACTIVATE_PLUGIN] ) ) {
50
- $ai1ec_exception_handler->reactivate_plugin();
51
  }
52
  $soft_disable_message = $ai1ec_exception_handler->get_disabled_message();
53
  if ( false !== $soft_disable_message ) {
54
- return $ai1ec_exception_handler->show_notices( $soft_disable_message );
55
  }
56
 
57
  $prev_er_handler = set_error_handler(
58
- array( $ai1ec_exception_handler, 'handle_error' )
59
  );
60
  $prev_ex_handler = set_exception_handler(
61
- array( $ai1ec_exception_handler, 'handle_exception' )
62
  );
63
  $ai1ec_exception_handler->set_prev_er_handler( $prev_er_handler );
64
  $ai1ec_exception_handler->set_prev_ex_handler( $prev_ex_handler );
@@ -66,16 +66,16 @@ $ai1ec_exception_handler->set_prev_ex_handler( $prev_ex_handler );
66
  // Regular startup sequence starts here
67
 
68
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
69
- DIRECTORY_SEPARATOR . 'bootstrap' . DIRECTORY_SEPARATOR . 'loader.php';
70
 
71
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
72
- DIRECTORY_SEPARATOR . 'global-functions.php';
73
 
74
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'app' .
75
- DIRECTORY_SEPARATOR . 'controller' . DIRECTORY_SEPARATOR . 'extension.php';
76
 
77
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'app' .
78
- DIRECTORY_SEPARATOR . 'controller' . DIRECTORY_SEPARATOR . 'extension-license.php';
79
 
80
  $ai1ec_loader = new Ai1ec_Loader( $ai1ec_base_dir );
81
  @ini_set( 'unserialize_callback_func', 'spl_autoload_call' );
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.24
9
  * Text Domain: all-in-one-event-calendar
10
  * Domain Path: /language
11
  */
13
  $ai1ec_base_url = plugins_url( '', __FILE__ );
14
 
15
  $ai1ec_config_path = $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'app' .
16
+ DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR;
17
 
18
  // Include configuration files and initiate global constants as they are used
19
  // By the error/exception handler too.
20
  foreach ( array( 'constants-local.php', 'constants.php' ) as $file ) {
21
+ if ( is_file( $ai1ec_config_path . $file ) ) {
22
+ require_once $ai1ec_config_path . $file;
23
+ }
24
  }
25
 
26
  if ( ! function_exists( 'ai1ec_initiate_constants' ) ) {
27
+ throw new Ai1ec_Exception(
28
+ 'No constant file was found.'
29
+ );
30
  }
31
  ai1ec_initiate_constants( $ai1ec_base_dir, $ai1ec_base_url );
32
 
33
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
34
+ DIRECTORY_SEPARATOR . 'exception' . DIRECTORY_SEPARATOR . 'ai1ec.php';
35
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
36
+ DIRECTORY_SEPARATOR . 'exception' . DIRECTORY_SEPARATOR . 'error.php';
37
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
38
+ DIRECTORY_SEPARATOR . 'exception' . DIRECTORY_SEPARATOR . 'handler.php';
39
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
40
+ DIRECTORY_SEPARATOR . 'http' . DIRECTORY_SEPARATOR . 'response' .
41
+ DIRECTORY_SEPARATOR . 'helper.php';
42
  $ai1ec_exception_handler = new Ai1ec_Exception_Handler(
43
+ 'Ai1ec_Exception',
44
+ 'Ai1ec_Error_Exception'
45
  );
46
 
47
 
48
  // if the user clicked the link to reactivate the plugin
49
  if ( isset( $_GET[Ai1ec_Exception_Handler::DB_REACTIVATE_PLUGIN] ) ) {
50
+ $ai1ec_exception_handler->reactivate_plugin();
51
  }
52
  $soft_disable_message = $ai1ec_exception_handler->get_disabled_message();
53
  if ( false !== $soft_disable_message ) {
54
+ return $ai1ec_exception_handler->show_notices( $soft_disable_message );
55
  }
56
 
57
  $prev_er_handler = set_error_handler(
58
+ array( $ai1ec_exception_handler, 'handle_error' )
59
  );
60
  $prev_ex_handler = set_exception_handler(
61
+ array( $ai1ec_exception_handler, 'handle_exception' )
62
  );
63
  $ai1ec_exception_handler->set_prev_er_handler( $prev_er_handler );
64
  $ai1ec_exception_handler->set_prev_ex_handler( $prev_ex_handler );
66
  // Regular startup sequence starts here
67
 
68
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
69
+ DIRECTORY_SEPARATOR . 'bootstrap' . DIRECTORY_SEPARATOR . 'loader.php';
70
 
71
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'lib' .
72
+ DIRECTORY_SEPARATOR . 'global-functions.php';
73
 
74
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'app' .
75
+ DIRECTORY_SEPARATOR . 'controller' . DIRECTORY_SEPARATOR . 'extension.php';
76
 
77
  require $ai1ec_base_dir . DIRECTORY_SEPARATOR . 'app' .
78
+ DIRECTORY_SEPARATOR . 'controller' . DIRECTORY_SEPARATOR . 'extension-license.php';
79
 
80
  $ai1ec_loader = new Ai1ec_Loader( $ai1ec_base_dir );
81
  @ini_set( 'unserialize_callback_func', 'spl_autoload_call' );
app/config/constants.php CHANGED
@@ -13,481 +13,481 @@
13
  */
14
  function ai1ec_initiate_constants( $ai1ec_base_dir, $ai1ec_base_url ) {
15
 
16
- // ===============
17
- // = Plugin Path =
18
- // ===============
19
- if ( ! defined( 'AI1EC_PATH' ) ) {
20
- define( 'AI1EC_PATH', $ai1ec_base_dir );
21
- }
22
-
23
- // =======================
24
- // = Extensions base dir =
25
- // =======================
26
- if ( ! defined( 'AI1EC_EXTENSIONS_BASEDIR' ) ) {
27
- define(
28
- 'AI1EC_EXTENSIONS_BASEDIR',
29
- dirname( $ai1ec_base_dir ) . DIRECTORY_SEPARATOR
30
- );
31
- }
32
-
33
- // ===============
34
- // = Plugin Name =
35
- // ===============
36
- if ( ! defined( 'AI1EC_PLUGIN_NAME' ) ) {
37
- define( 'AI1EC_PLUGIN_NAME', 'all-in-one-event-calendar' );
38
- }
39
-
40
- // ===================
41
- // = Plugin Basename =
42
- // ===================
43
- if ( ! defined( 'AI1EC_PLUGIN_BASENAME' ) ) {
44
- $plugin = AI1EC_PATH . DIRECTORY_SEPARATOR . AI1EC_PLUGIN_NAME . '.php';
45
- define( 'AI1EC_PLUGIN_BASENAME', plugin_basename( $plugin ) );
46
- unset( $plugin );
47
- }
48
-
49
- // ==================
50
- // = Plugin Version =
51
- // ==================
52
- if ( ! defined( 'AI1EC_VERSION' ) ) {
53
- define( 'AI1EC_VERSION', '2.5.21' );
54
- }
55
-
56
- // ================
57
- // = RSS FEED URL =
58
- // ================
59
- if ( ! defined( 'AI1EC_RSS_FEED' ) ) {
60
- define( 'AI1EC_RSS_FEED', 'https://time.ly/blog/feed/' );
61
- }
62
-
63
- // =================
64
- // = Language Path =
65
- // =================
66
- if ( ! defined( 'AI1EC_LANGUAGE_PATH' ) ) {
67
- define(
68
- 'AI1EC_LANGUAGE_PATH',
69
- AI1EC_PLUGIN_NAME . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR
70
- );
71
- }
72
- // ================
73
- // = Cron Version =
74
- // ================
75
- if ( ! defined( 'AI1EC_CRON_VERSION' ) ) {
76
- define( 'AI1EC_CRON_VERSION', AI1EC_VERSION );
77
- }
78
- if ( ! defined( 'AI1EC_U_CRON_VERSION' ) ) {
79
- define( 'AI1EC_U_CRON_VERSION', AI1EC_VERSION );
80
- }
81
- if ( ! defined( 'AI1EC_U_CRON_FREQ' ) ) {
82
- define( 'AI1EC_U_CRON_FREQ', 'hourly' );
83
- }
84
-
85
- // ==============
86
- // = Plugin Url =
87
- // ==============
88
- if ( ! defined( 'AI1EC_URL' ) ) {
89
- define( 'AI1EC_URL', $ai1ec_base_url );
90
- }
91
- // ===============
92
- // = VENDOR PATH =
93
- // ===============
94
- if ( ! defined( 'AI1EC_VENDOR_PATH' ) ) {
95
- define(
96
- 'AI1EC_VENDOR_PATH',
97
- AI1EC_PATH . DIRECTORY_SEPARATOR . 'vendor' .
98
- DIRECTORY_SEPARATOR
99
- );
100
- }
101
-
102
- // ===============
103
- // = ADMIN PATH =
104
- // ===============
105
- if ( ! defined( 'AI1EC_ADMIN_PATH' ) ) {
106
- define(
107
- 'AI1EC_ADMIN_PATH',
108
- AI1EC_PATH . DIRECTORY_SEPARATOR . 'public' .
109
- DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR
110
- );
111
- }
112
-
113
- // ===============
114
- // = ADMIN URL =
115
- // ===============
116
- if ( ! defined( 'AI1EC_ADMIN_URL' ) ) {
117
- define(
118
- 'AI1EC_ADMIN_URL',
119
- AI1EC_URL . '/public/admin/'
120
- );
121
- }
122
-
123
- // ==============
124
- // = CACHE PATH =
125
- // ==============
126
- if ( ! defined( 'AI1EC_CACHE_PATH' ) ) {
127
- define(
128
- 'AI1EC_CACHE_PATH',
129
- AI1EC_PATH . DIRECTORY_SEPARATOR . 'cache' .
130
- DIRECTORY_SEPARATOR
131
- );
132
- }
133
-
134
- // ==============
135
- // = CACHE URL =
136
- // ==============
137
- if ( ! defined( 'AI1EC_CACHE_URL' ) ) {
138
- define(
139
- 'AI1EC_CACHE_URL',
140
- AI1EC_URL . '/cache/'
141
- );
142
- }
143
-
144
- // ==============
145
- // = TWIG CACHE PATH =
146
- // ==============
147
- if ( ! defined( 'AI1EC_TWIG_CACHE_PATH' ) ) {
148
- define(
149
- 'AI1EC_TWIG_CACHE_PATH',
150
- AI1EC_CACHE_PATH . 'twig' .
151
- DIRECTORY_SEPARATOR
152
- );
153
- }
154
-
155
- // ======================
156
- // = Default theme name =
157
- // ======================
158
- if ( ! defined( 'AI1EC_DEFAULT_THEME_NAME' ) ) {
159
- define( 'AI1EC_DEFAULT_THEME_NAME', 'vortex' );
160
- }
161
- // ================
162
- // = THEME FOLDER =
163
- // ================
164
- if ( ! defined( 'AI1EC_THEME_FOLDER' ) ) {
165
- define( 'AI1EC_THEME_FOLDER', 'themes-ai1ec' );
166
- }
167
-
168
- // =======================
169
- // = DEFAULT THEME PATH =
170
- // =======================
171
- if ( ! defined( 'AI1EC_DEFAULT_THEME_ROOT' ) ) {
172
- define(
173
- 'AI1EC_DEFAULT_THEME_ROOT',
174
- AI1EC_PATH . DIRECTORY_SEPARATOR . 'public' .
175
- DIRECTORY_SEPARATOR . AI1EC_THEME_FOLDER
176
- );
177
- }
178
-
179
- // =======================
180
- // = DEFAULT THEME PATH =
181
- // =======================
182
- if ( ! defined( 'AI1EC_DEFAULT_THEME_PATH' ) ) {
183
- define(
184
- 'AI1EC_DEFAULT_THEME_PATH',
185
- AI1EC_DEFAULT_THEME_ROOT . DIRECTORY_SEPARATOR .
186
- AI1EC_DEFAULT_THEME_NAME
187
- );
188
- }
189
-
190
- // ===================
191
- // = AI1EC Theme URL =
192
- // ===================
193
- if ( ! defined( 'AI1EC_THEMES_URL' ) ) {
194
- define(
195
- 'AI1EC_THEMES_URL',
196
- AI1EC_URL . '/public/' . AI1EC_THEME_FOLDER
197
- );
198
- }
199
-
200
-
201
- // =====================
202
- // = AI1EC Core themes =
203
- // =====================
204
- if ( ! defined( 'AI1EC_CORE_THEMES' ) ) {
205
- define( 'AI1EC_CORE_THEMES', 'vortex,umbra,gamma,plana' );
206
- }
207
-
208
- // ===================
209
- // = AI1EC Theme URL =
210
- // ===================
211
- if ( ! defined( 'AI1EC_THEMES_URL' ) ) {
212
- define( 'AI1EC_THEMES_URL', AI1EC_URL . '/public/' . AI1EC_THEME_FOLDER . '/' );
213
- }
214
-
215
- // =================
216
- // = Admin CSS URL =
217
- // =================
218
- if ( ! defined( 'AI1EC_ADMIN_THEME_CSS_URL' ) ) {
219
- define( 'AI1EC_ADMIN_THEME_CSS_URL', AI1EC_URL .'/public/admin/css/' );
220
- }
221
-
222
- // =================
223
- // = Admin Font URL =
224
- // =================
225
- if ( ! defined( 'AI1EC_ADMIN_THEME_FONT_URL' ) ) {
226
- define( 'AI1EC_ADMIN_THEME_FONT_URL', AI1EC_URL .'/public/admin/font/' );
227
- }
228
-
229
- // =================
230
- // = Admin Js URL =
231
- // =================
232
- if ( ! defined( 'AI1EC_ADMIN_THEME_JS_URL' ) ) {
233
- define( 'AI1EC_ADMIN_THEME_JS_URL', AI1EC_URL .'/public/js/' );
234
- }
235
-
236
- // =============
237
- // = POST TYPE =
238
- // =============
239
- if ( ! defined( 'AI1EC_POST_TYPE' ) ) {
240
- define( 'AI1EC_POST_TYPE', 'ai1ec_event' );
241
- }
242
-
243
- // ==============
244
- // = SCRIPT URL =
245
- // ==============
246
- if ( ! defined( 'AI1EC_SCRIPT_URL' ) ) {
247
- define(
248
- 'AI1EC_SCRIPT_URL',
249
- get_option( 'home' ) . '/?plugin=' . AI1EC_PLUGIN_NAME
250
- );
251
- }
252
-
253
- // =========================================
254
- // = BASE URL FOR ALL CALENDAR ADMIN PAGES =
255
- // =========================================
256
- if ( ! defined( 'AI1EC_ADMIN_BASE_URL' ) ) {
257
- define( 'AI1EC_ADMIN_BASE_URL', 'edit.php?post_type=' . AI1EC_POST_TYPE );
258
- }
259
-
260
-
261
- // =====================================================
262
- // = THEME OPTIONS PAGE BASE URL (wrap in admin_url()) =
263
- // =====================================================
264
- if ( ! defined( 'AI1EC_THEME_OPTIONS_BASE_URL' ) ) {
265
- define( 'AI1EC_THEME_OPTIONS_BASE_URL', AI1EC_ADMIN_BASE_URL . '&page=' . AI1EC_PLUGIN_NAME . '-edit-css' );
266
- }
267
-
268
- // =======================================================
269
- // = THEME SELECTION PAGE BASE URL (wrap in admin_url()) =
270
- // =======================================================
271
- if ( ! defined( 'AI1EC_THEME_SELECTION_BASE_URL' ) ) {
272
- define(
273
- 'AI1EC_THEME_SELECTION_BASE_URL',
274
- AI1EC_ADMIN_BASE_URL . '&page=' . AI1EC_PLUGIN_NAME . '-themes'
275
- );
276
- }
277
-
278
-
279
- // =====================================================
280
- // = FEED SETTINGS PAGE BASE URL (wrap in admin_url()) =
281
- // =====================================================
282
- if ( ! defined( 'AI1EC_FEED_SETTINGS_BASE_URL' ) ) {
283
- define( 'AI1EC_FEED_SETTINGS_BASE_URL', AI1EC_ADMIN_BASE_URL . '&page=' . AI1EC_PLUGIN_NAME . '-feeds' );
284
- }
285
-
286
- // ================================================
287
- // = SETTINGS PAGE BASE URL (wrap in admin_url()) =
288
- // ================================================
289
- if ( ! defined( 'AI1EC_SETTINGS_BASE_URL' ) ) {
290
- define(
291
- 'AI1EC_SETTINGS_BASE_URL',
292
- AI1EC_ADMIN_BASE_URL . '&page=' . AI1EC_PLUGIN_NAME . '-settings'
293
- );
294
- }
295
-
296
- // ==============
297
- // = EXPORT URL =
298
- // ==============
299
- if ( ! defined( 'AI1EC_EXPORT_URL' ) ) {
300
- // ====================================================
301
- // = Convert http:// to webcal:// in AI1EC_SCRIPT_URL =
302
- // = (webcal:// protocol does not support https://) =
303
- // ====================================================
304
- $webcal_url = preg_replace( '/^https?:\/\//', 'webcal://', AI1EC_SCRIPT_URL );
305
- define(
306
- 'AI1EC_EXPORT_URL',
307
- $webcal_url . '&controller=ai1ec_exporter_controller' .
308
- '&action=export_events'
309
- );
310
- unset( $webcal_url );
311
- }
312
-
313
- if ( ! defined( 'AI1EC_CA_ROOT_PEM' ) ) {
314
- define(
315
- 'AI1EC_CA_ROOT_PEM',
316
- AI1EC_PATH . DIRECTORY_SEPARATOR . 'ca_cert' .
317
- DIRECTORY_SEPARATOR . 'ca_cert.pem'
318
- );
319
- }
320
-
321
- // ====================
322
- // = SPECIAL SETTINGS =
323
- // ====================
324
-
325
- // Set AI1EC_EVENT_PLATFORM to TRUE to turn WordPress into an events-only
326
- // platform. For a multi-site install, setting this to TRUE is equivalent to a
327
- // super-administrator selecting the
328
- // "Turn this blog into an events-only platform" checkbox
329
- // on the Calendar Settings page of every blog on the network.
330
- // This mode, when enabled on blogs where this plugin is active, hides all
331
- // administrative functions unrelated to events and the calendar (except to
332
- // super-administrators), and sets default WordPress settings appropriate for
333
- // pure event management.
334
- if ( ! defined( 'AI1EC_EVENT_PLATFORM' ) ) {
335
- define( 'AI1EC_EVENT_PLATFORM', false );
336
- }
337
-
338
- // If i choose to use the calendar url as the base for events permalinks,
339
- // i must specify another name for the events archive.
340
- if ( ! defined( 'AI1EC_ALTERNATIVE_ARCHIVE_URL' ) ) {
341
- define( 'AI1EC_ALTERNATIVE_ARCHIVE_URL', 'ai1ec_events_archive' );
342
- }
343
-
344
- // ===================
345
- // = AI1EC Theme URL =
346
- // ===================
347
- if ( ! defined( 'AI1EC_THEMES_URL_LEGACY' ) ) {
348
- define( 'AI1EC_THEMES_URL_LEGACY', WP_CONTENT_URL . '/' . AI1EC_THEME_FOLDER );
349
- }
350
-
351
- // =====================
352
- // = Default theme url legacy=
353
- // =====================
354
- if ( ! defined( 'AI1EC_DEFAULT_THEME_URL_LEGACY' ) ) {
355
- define( 'AI1EC_DEFAULT_THEME_URL_LEGACY', AI1EC_THEMES_URL . '/' . AI1EC_DEFAULT_THEME_NAME . '/' );
356
- }
357
-
358
- // =====================
359
- // = Default theme url =
360
- // =====================
361
- if ( ! defined( 'AI1EC_DEFAULT_THEME_URL' ) ) {
362
- define( 'AI1EC_DEFAULT_THEME_URL', AI1EC_THEMES_URL . '/' . AI1EC_DEFAULT_THEME_NAME . '/' );
363
- }
364
-
365
- // ===================
366
- // = CSS Folder name =
367
- // ===================
368
- if ( ! defined( 'AI1EC_CSS_FOLDER' ) ) {
369
- define( 'AI1EC_CSS_FOLDER', 'css' );
370
- }
371
-
372
- // ==================
373
- // = JS Folder name =
374
- // ==================
375
- if ( ! defined( 'AI1EC_JS_FOLDER' ) ) {
376
- define( 'AI1EC_JS_FOLDER', 'js' );
377
- }
378
-
379
- // =====================
380
- // = Image folder name =
381
- // =====================
382
- if ( ! defined( 'AI1EC_IMG_FOLDER' ) ) {
383
- define( 'AI1EC_IMG_FOLDER', 'img' );
384
- }
385
-
386
-
387
-
388
- // ========================
389
- // = Admin theme CSS path =
390
- // ========================
391
- if ( ! defined( 'AI1EC_ADMIN_THEME_CSS_PATH' ) ) {
392
- define( 'AI1EC_ADMIN_THEME_CSS_PATH', AI1EC_ADMIN_PATH . AI1EC_CSS_FOLDER );
393
- }
394
-
395
- // =======================
396
- // = Admin theme JS path =
397
- // =======================
398
- if ( ! defined( 'AI1EC_ADMIN_THEME_JS_PATH' ) ) {
399
- define( 'AI1EC_ADMIN_THEME_JS_PATH', AI1EC_PATH . DIRECTORY_SEPARATOR . 'public' .
400
  DIRECTORY_SEPARATOR . AI1EC_JS_FOLDER );
401
- }
402
-
403
- // =================
404
- // = Admin IMG URL =
405
- // =================
406
- if ( ! defined( 'AI1EC_ADMIN_THEME_IMG_URL' ) ) {
407
- define( 'AI1EC_ADMIN_THEME_IMG_URL', AI1EC_URL . '/public/admin/' . AI1EC_IMG_FOLDER );
408
- }
409
-
410
- // ====================
411
- // = Add-ons list URL =
412
- // ====================
413
- if ( ! defined( 'AI1EC_TIMELY_ADDONS_URI' ) ) {
414
- define( 'AI1EC_TIMELY_ADDONS_URI', 'https://time.ly/?action=addons_list' );
415
- }
416
-
417
- // Enable All-in-One-Event-Calendar to work in debug mode, which means,
418
- // that cache is ignored, extra output may appear at places, etc.
419
- // Do not set this to any other value than `false` on production even if
420
- // you know what you are doing, because you will waste valuable
421
- // resources - save the Earth, at least.
422
- if ( ! defined( 'AI1EC_DEBUG' ) ) {
423
- define( 'AI1EC_DEBUG', false );
424
- }
425
-
426
- // Enable Ai1EC cache functionality. If you set this to false, only cache
427
- // that is based on request, will remain active.
428
- // This is pointless in any case other than development, where literary
429
- // every second refresh needs to take fresh copy of everything.
430
- if ( ! defined( 'AI1EC_CACHE' ) ) {
431
- define( 'AI1EC_CACHE', true );
432
- }
433
-
434
- if ( ! defined( 'AI1EC_DISABLE_FILE_CACHE' ) ) {
435
- define( 'AI1EC_DISABLE_FILE_CACHE', false );
436
- }
437
-
438
- // A value identifying that cache is not available.
439
- // Used in place of actual path for cache to use.
440
- // Named constant allows reuse of a single typed variable.
441
- if ( ! defined( 'AI1EC_CACHE_UNAVAILABLE' ) ) {
442
- define( 'AI1EC_CACHE_UNAVAILABLE', 'AI1EC_CACHE_UNAVAILABLE' );
443
- }
444
-
445
- // Defines if backward (<= 2.1.5) theme compatibility is enabled or not.
446
- if ( ! defined( 'AI1EC_THEME_COMPATIBILITY_FER' ) ) {
447
- define( 'AI1EC_THEME_COMPATIBILITY_FER', true );
448
- }
449
-
450
- // Defines amount of needed free memory to compile LESS files.
451
- if ( ! defined( 'AI1EC_LESS_MIN_AVAIL_MEMORY' ) ) {
452
- define( 'AI1EC_LESS_MIN_AVAIL_MEMORY', '24M' );
453
- }
454
-
455
- // Defines if LESS files are parsed at every request
456
- if ( ! defined( 'AI1EC_PARSE_LESS_FILES_AT_EVERY_REQUEST' ) ) {
457
- define( 'AI1EC_PARSE_LESS_FILES_AT_EVERY_REQUEST', false );
458
- }
459
-
460
- // Defines a list of FER-enabled templates.
461
- if ( ! defined( 'AI1EC_FER_ENABLED_TEMPLATES_LIST' ) ) {
462
- define(
463
- 'AI1EC_FER_ENABLED_TEMPLATES_LIST',
464
- 'agenda,oneday,week,month,posterboard,stream'
465
- );
466
- }
467
-
468
- // Defines API URL.
469
- if ( ! defined( 'AI1EC_API_URL' ) ) {
470
- define(
471
- 'AI1EC_API_URL',
472
- 'https://api.time.ly/api/'
473
- );
474
- }
475
-
476
- // Defines Tickets checkout URL.
477
- if ( ! defined( 'AI1EC_TICKETS_CHECKOUT_URL' ) ) {
478
- define(
479
- 'AI1EC_TICKETS_CHECKOUT_URL',
480
- 'https://api.time.ly/events/{event_id}/checkout'
481
- );
482
- }
483
-
484
- // ================================================
485
- // = Force WordPress updates command link =
486
- // ================================================
487
- if ( ! defined( 'AI1EC_FORCE_UPDATES_URL' ) ) {
488
- define(
489
- 'AI1EC_FORCE_UPDATES_URL',
490
- AI1EC_ADMIN_BASE_URL . '&ai1ec_force_updates=true'
491
- );
492
- }
493
  }
13
  */
14
  function ai1ec_initiate_constants( $ai1ec_base_dir, $ai1ec_base_url ) {
15
 
16
+ // ===============
17
+ // = Plugin Path =
18
+ // ===============
19
+ if ( ! defined( 'AI1EC_PATH' ) ) {
20
+ define( 'AI1EC_PATH', $ai1ec_base_dir );
21
+ }
22
+
23
+ // =======================
24
+ // = Extensions base dir =
25
+ // =======================
26
+ if ( ! defined( 'AI1EC_EXTENSIONS_BASEDIR' ) ) {
27
+ define(
28
+ 'AI1EC_EXTENSIONS_BASEDIR',
29
+ dirname( $ai1ec_base_dir ) . DIRECTORY_SEPARATOR
30
+ );
31
+ }
32
+
33
+ // ===============
34
+ // = Plugin Name =
35
+ // ===============
36
+ if ( ! defined( 'AI1EC_PLUGIN_NAME' ) ) {
37
+ define( 'AI1EC_PLUGIN_NAME', 'all-in-one-event-calendar' );
38
+ }
39
+
40
+ // ===================
41
+ // = Plugin Basename =
42
+ // ===================
43
+ if ( ! defined( 'AI1EC_PLUGIN_BASENAME' ) ) {
44
+ $plugin = AI1EC_PATH . DIRECTORY_SEPARATOR . AI1EC_PLUGIN_NAME . '.php';
45
+ define( 'AI1EC_PLUGIN_BASENAME', plugin_basename( $plugin ) );
46
+ unset( $plugin );
47
+ }
48
+
49
+ // ==================
50
+ // = Plugin Version =
51
+ // ==================
52
+ if ( ! defined( 'AI1EC_VERSION' ) ) {
53
+ define( 'AI1EC_VERSION', '2.5.24' );
54
+ }
55
+
56
+ // ================
57
+ // = RSS FEED URL =
58
+ // ================
59
+ if ( ! defined( 'AI1EC_RSS_FEED' ) ) {
60
+ define( 'AI1EC_RSS_FEED', 'https://time.ly/blog/feed/' );
61
+ }
62
+
63
+ // =================
64
+ // = Language Path =
65
+ // =================
66
+ if ( ! defined( 'AI1EC_LANGUAGE_PATH' ) ) {
67
+ define(
68
+ 'AI1EC_LANGUAGE_PATH',
69
+ AI1EC_PLUGIN_NAME . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR
70
+ );
71
+ }
72
+ // ================
73
+ // = Cron Version =
74
+ // ================
75
+ if ( ! defined( 'AI1EC_CRON_VERSION' ) ) {
76
+ define( 'AI1EC_CRON_VERSION', AI1EC_VERSION );
77
+ }
78
+ if ( ! defined( 'AI1EC_U_CRON_VERSION' ) ) {
79
+ define( 'AI1EC_U_CRON_VERSION', AI1EC_VERSION );
80
+ }
81
+ if ( ! defined( 'AI1EC_U_CRON_FREQ' ) ) {
82
+ define( 'AI1EC_U_CRON_FREQ', 'hourly' );
83
+ }
84
+
85
+ // ==============
86
+ // = Plugin Url =
87
+ // ==============
88
+ if ( ! defined( 'AI1EC_URL' ) ) {
89
+ define( 'AI1EC_URL', $ai1ec_base_url );
90
+ }
91
+ // ===============
92
+ // = VENDOR PATH =
93
+ // ===============
94
+ if ( ! defined( 'AI1EC_VENDOR_PATH' ) ) {
95
+ define(
96
+ 'AI1EC_VENDOR_PATH',
97
+ AI1EC_PATH . DIRECTORY_SEPARATOR . 'vendor' .
98
+ DIRECTORY_SEPARATOR
99
+ );
100
+ }
101
+
102
+ // ===============
103
+ // = ADMIN PATH =
104
+ // ===============
105
+ if ( ! defined( 'AI1EC_ADMIN_PATH' ) ) {
106
+ define(
107
+ 'AI1EC_ADMIN_PATH',
108
+ AI1EC_PATH . DIRECTORY_SEPARATOR . 'public' .
109
+ DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR
110
+ );
111
+ }
112
+
113
+ // ===============
114
+ // = ADMIN URL =
115
+ // ===============
116
+ if ( ! defined( 'AI1EC_ADMIN_URL' ) ) {
117
+ define(
118
+ 'AI1EC_ADMIN_URL',
119
+ AI1EC_URL . '/public/admin/'
120
+ );
121
+ }
122
+
123
+ // ==============
124
+ // = CACHE PATH =
125
+ // ==============
126
+ if ( ! defined( 'AI1EC_CACHE_PATH' ) ) {
127
+ define(
128
+ 'AI1EC_CACHE_PATH',
129
+ AI1EC_PATH . DIRECTORY_SEPARATOR . 'cache' .
130
+ DIRECTORY_SEPARATOR
131
+ );
132
+ }
133
+
134
+ // ==============
135
+ // = CACHE URL =
136
+ // ==============
137
+ if ( ! defined( 'AI1EC_CACHE_URL' ) ) {
138
+ define(
139
+ 'AI1EC_CACHE_URL',
140
+ AI1EC_URL . '/cache/'
141
+ );
142
+ }
143
+
144
+ // ==============
145
+ // = TWIG CACHE PATH =
146
+ // ==============
147
+ if ( ! defined( 'AI1EC_TWIG_CACHE_PATH' ) ) {
148
+ define(
149
+ 'AI1EC_TWIG_CACHE_PATH',
150
+ AI1EC_CACHE_PATH . 'twig' .
151
+ DIRECTORY_SEPARATOR
152
+ );
153
+ }
154
+
155
+ // ======================
156
+ // = Default theme name =
157
+ // ======================
158
+ if ( ! defined( 'AI1EC_DEFAULT_THEME_NAME' ) ) {
159
+ define( 'AI1EC_DEFAULT_THEME_NAME', 'vortex' );
160
+ }
161
+ // ================
162
+ // = THEME FOLDER =
163
+ // ================
164
+ if ( ! defined( 'AI1EC_THEME_FOLDER' ) ) {
165
+ define( 'AI1EC_THEME_FOLDER', 'themes-ai1ec' );
166
+ }
167
+
168
+ // =======================
169
+ // = DEFAULT THEME PATH =
170
+ // =======================
171
+ if ( ! defined( 'AI1EC_DEFAULT_THEME_ROOT' ) ) {
172
+ define(
173
+ 'AI1EC_DEFAULT_THEME_ROOT',
174
+ AI1EC_PATH . DIRECTORY_SEPARATOR . 'public' .
175
+ DIRECTORY_SEPARATOR . AI1EC_THEME_FOLDER
176
+ );
177
+ }
178
+
179
+ // =======================
180
+ // = DEFAULT THEME PATH =
181
+ // =======================
182
+ if ( ! defined( 'AI1EC_DEFAULT_THEME_PATH' ) ) {
183
+ define(
184
+ 'AI1EC_DEFAULT_THEME_PATH',
185
+ AI1EC_DEFAULT_THEME_ROOT . DIRECTORY_SEPARATOR .
186
+ AI1EC_DEFAULT_THEME_NAME
187
+ );
188
+ }
189
+
190
+ // ===================
191
+ // = AI1EC Theme URL =
192
+ // ===================
193
+ if ( ! defined( 'AI1EC_THEMES_URL' ) ) {
194
+ define(
195
+ 'AI1EC_THEMES_URL',
196
+ AI1EC_URL . '/public/' . AI1EC_THEME_FOLDER
197
+ );
198
+ }
199
+
200
+
201
+ // =====================
202
+ // = AI1EC Core themes =
203
+ // =====================
204
+ if ( ! defined( 'AI1EC_CORE_THEMES' ) ) {
205
+ define( 'AI1EC_CORE_THEMES', 'vortex,umbra,gamma,plana' );
206
+ }
207
+
208
+ // ===================
209
+ // = AI1EC Theme URL =
210
+ // ===================
211
+ if ( ! defined( 'AI1EC_THEMES_URL' ) ) {
212
+ define( 'AI1EC_THEMES_URL', AI1EC_URL . '/public/' . AI1EC_THEME_FOLDER . '/' );
213
+ }
214
+
215
+ // =================
216
+ // = Admin CSS URL =
217
+ // =================
218
+ if ( ! defined( 'AI1EC_ADMIN_THEME_CSS_URL' ) ) {
219
+ define( 'AI1EC_ADMIN_THEME_CSS_URL', AI1EC_URL .'/public/admin/css/' );
220
+ }
221
+
222
+ // =================
223
+ // = Admin Font URL =
224
+ // =================
225
+ if ( ! defined( 'AI1EC_ADMIN_THEME_FONT_URL' ) ) {
226
+ define( 'AI1EC_ADMIN_THEME_FONT_URL', AI1EC_URL .'/public/admin/font/' );
227
+ }
228
+
229
+ // =================
230
+ // = Admin Js URL =
231
+ // =================
232
+ if ( ! defined( 'AI1EC_ADMIN_THEME_JS_URL' ) ) {
233
+ define( 'AI1EC_ADMIN_THEME_JS_URL', AI1EC_URL .'/public/js/' );
234
+ }
235
+
236
+ // =============
237
+ // = POST TYPE =
238
+ // =============
239
+ if ( ! defined( 'AI1EC_POST_TYPE' ) ) {
240
+ define( 'AI1EC_POST_TYPE', 'ai1ec_event' );
241
+ }
242
+
243
+ // ==============
244
+ // = SCRIPT URL =
245
+ // ==============
246
+ if ( ! defined( 'AI1EC_SCRIPT_URL' ) ) {
247
+ define(
248
+ 'AI1EC_SCRIPT_URL',
249
+ get_option( 'home' ) . '/?plugin=' . AI1EC_PLUGIN_NAME
250
+ );
251
+ }
252
+
253
+ // =========================================
254
+ // = BASE URL FOR ALL CALENDAR ADMIN PAGES =
255
+ // =========================================
256
+ if ( ! defined( 'AI1EC_ADMIN_BASE_URL' ) ) {
257
+ define( 'AI1EC_ADMIN_BASE_URL', 'edit.php?post_type=' . AI1EC_POST_TYPE );
258
+ }
259
+
260
+
261
+ // =====================================================
262
+ // = THEME OPTIONS PAGE BASE URL (wrap in admin_url()) =
263
+ // =====================================================
264
+ if ( ! defined( 'AI1EC_THEME_OPTIONS_BASE_URL' ) ) {
265
+ define( 'AI1EC_THEME_OPTIONS_BASE_URL', AI1EC_ADMIN_BASE_URL . '&page=' . AI1EC_PLUGIN_NAME . '-edit-css' );
266
+ }
267
+
268
+ // =======================================================
269
+ // = THEME SELECTION PAGE BASE URL (wrap in admin_url()) =
270
+ // =======================================================
271
+ if ( ! defined( 'AI1EC_THEME_SELECTION_BASE_URL' ) ) {
272
+ define(
273
+ 'AI1EC_THEME_SELECTION_BASE_URL',
274
+ AI1EC_ADMIN_BASE_URL . '&page=' . AI1EC_PLUGIN_NAME . '-themes'
275
+ );
276
+ }
277
+
278
+
279
+ // =====================================================
280
+ // = FEED SETTINGS PAGE BASE URL (wrap in admin_url()) =
281
+ // =====================================================
282
+ if ( ! defined( 'AI1EC_FEED_SETTINGS_BASE_URL' ) ) {
283
+ define( 'AI1EC_FEED_SETTINGS_BASE_URL', AI1EC_ADMIN_BASE_URL . '&page=' . AI1EC_PLUGIN_NAME . '-feeds' );
284
+ }
285
+
286
+ // ================================================
287
+ // = SETTINGS PAGE BASE URL (wrap in admin_url()) =
288
+ // ================================================
289
+ if ( ! defined( 'AI1EC_SETTINGS_BASE_URL' ) ) {
290
+ define(
291
+ 'AI1EC_SETTINGS_BASE_URL',
292
+ AI1EC_ADMIN_BASE_URL . '&page=' . AI1EC_PLUGIN_NAME . '-settings'
293
+ );
294
+ }
295
+
296
+ // ==============
297
+ // = EXPORT URL =
298
+ // ==============
299
+ if ( ! defined( 'AI1EC_EXPORT_URL' ) ) {
300
+ // ====================================================
301
+ // = Convert http:// to webcal:// in AI1EC_SCRIPT_URL =
302
+ // = (webcal:// protocol does not support https://) =
303
+ // ====================================================
304
+ $webcal_url = preg_replace( '/^https?:\/\//', 'webcal://', AI1EC_SCRIPT_URL );
305
+ define(
306
+ 'AI1EC_EXPORT_URL',
307
+ $webcal_url . '&controller=ai1ec_exporter_controller' .
308
+ '&action=export_events'
309
+ );
310
+ unset( $webcal_url );
311
+ }
312
+
313
+ if ( ! defined( 'AI1EC_CA_ROOT_PEM' ) ) {
314
+ define(
315
+ 'AI1EC_CA_ROOT_PEM',
316
+ AI1EC_PATH . DIRECTORY_SEPARATOR . 'ca_cert' .
317
+ DIRECTORY_SEPARATOR . 'ca_cert.pem'
318
+ );
319
+ }
320
+
321
+ // ====================
322
+ // = SPECIAL SETTINGS =
323
+ // ====================
324
+
325
+ // Set AI1EC_EVENT_PLATFORM to TRUE to turn WordPress into an events-only
326
+ // platform. For a multi-site install, setting this to TRUE is equivalent to a
327
+ // super-administrator selecting the
328
+ // "Turn this blog into an events-only platform" checkbox
329
+ // on the Calendar Settings page of every blog on the network.
330
+ // This mode, when enabled on blogs where this plugin is active, hides all
331
+ // administrative functions unrelated to events and the calendar (except to
332
+ // super-administrators), and sets default WordPress settings appropriate for
333
+ // pure event management.
334
+ if ( ! defined( 'AI1EC_EVENT_PLATFORM' ) ) {
335
+ define( 'AI1EC_EVENT_PLATFORM', false );
336
+ }
337
+
338
+ // If i choose to use the calendar url as the base for events permalinks,
339
+ // i must specify another name for the events archive.
340
+ if ( ! defined( 'AI1EC_ALTERNATIVE_ARCHIVE_URL' ) ) {
341
+ define( 'AI1EC_ALTERNATIVE_ARCHIVE_URL', 'ai1ec_events_archive' );
342
+ }
343
+
344
+ // ===================
345
+ // = AI1EC Theme URL =
346
+ // ===================
347
+ if ( ! defined( 'AI1EC_THEMES_URL_LEGACY' ) ) {
348
+ define( 'AI1EC_THEMES_URL_LEGACY', WP_CONTENT_URL . '/' . AI1EC_THEME_FOLDER );
349
+ }
350
+
351
+ // =====================
352
+ // = Default theme url legacy=
353
+ // =====================
354
+ if ( ! defined( 'AI1EC_DEFAULT_THEME_URL_LEGACY' ) ) {
355
+ define( 'AI1EC_DEFAULT_THEME_URL_LEGACY', AI1EC_THEMES_URL . '/' . AI1EC_DEFAULT_THEME_NAME . '/' );
356
+ }
357
+
358
+ // =====================
359
+ // = Default theme url =
360
+ // =====================
361
+ if ( ! defined( 'AI1EC_DEFAULT_THEME_URL' ) ) {
362
+ define( 'AI1EC_DEFAULT_THEME_URL', AI1EC_THEMES_URL . '/' . AI1EC_DEFAULT_THEME_NAME . '/' );
363
+ }
364
+
365
+ // ===================
366
+ // = CSS Folder name =
367
+ // ===================
368
+ if ( ! defined( 'AI1EC_CSS_FOLDER' ) ) {
369
+ define( 'AI1EC_CSS_FOLDER', 'css' );
370
+ }
371
+
372
+ // ==================
373
+ // = JS Folder name =
374
+ // ==================
375
+ if ( ! defined( 'AI1EC_JS_FOLDER' ) ) {
376
+ define( 'AI1EC_JS_FOLDER', 'js' );
377
+ }
378
+
379
+ // =====================
380
+ // = Image folder name =
381
+ // =====================
382
+ if ( ! defined( 'AI1EC_IMG_FOLDER' ) ) {
383
+ define( 'AI1EC_IMG_FOLDER', 'img' );
384
+ }
385
+
386
+
387
+
388
+ // ========================
389
+ // = Admin theme CSS path =
390
+ // ========================
391
+ if ( ! defined( 'AI1EC_ADMIN_THEME_CSS_PATH' ) ) {
392
+ define( 'AI1EC_ADMIN_THEME_CSS_PATH', AI1EC_ADMIN_PATH . AI1EC_CSS_FOLDER );
393
+ }
394
+
395
+ // =======================
396
+ // = Admin theme JS path =
397
+ // =======================
398
+ if ( ! defined( 'AI1EC_ADMIN_THEME_JS_PATH' ) ) {
399
+ define( 'AI1EC_ADMIN_THEME_JS_PATH', AI1EC_PATH . DIRECTORY_SEPARATOR . 'public' .
400
  DIRECTORY_SEPARATOR . AI1EC_JS_FOLDER );
401
+ }
402
+
403
+ // =================
404
+ // = Admin IMG URL =
405
+ // =================
406
+ if ( ! defined( 'AI1EC_ADMIN_THEME_IMG_URL' ) ) {
407
+ define( 'AI1EC_ADMIN_THEME_IMG_URL', AI1EC_URL . '/public/admin/' . AI1EC_IMG_FOLDER );
408
+ }
409
+
410
+ // ====================
411
+ // = Add-ons list URL =
412
+ // ====================
413
+ if ( ! defined( 'AI1EC_TIMELY_ADDONS_URI' ) ) {
414
+ define( 'AI1EC_TIMELY_ADDONS_URI', 'https://time.ly/?action=addons_list' );
415
+ }
416
+
417
+ // Enable All-in-One-Event-Calendar to work in debug mode, which means,
418
+ // that cache is ignored, extra output may appear at places, etc.
419
+ // Do not set this to any other value than `false` on production even if
420
+ // you know what you are doing, because you will waste valuable
421
+ // resources - save the Earth, at least.
422
+ if ( ! defined( 'AI1EC_DEBUG' ) ) {
423
+ define( 'AI1EC_DEBUG', false );
424
+ }
425
+
426
+ // Enable Ai1EC cache functionality. If you set this to false, only cache
427
+ // that is based on request, will remain active.
428
+ // This is pointless in any case other than development, where literary
429
+ // every second refresh needs to take fresh copy of everything.
430
+ if ( ! defined( 'AI1EC_CACHE' ) ) {
431
+ define( 'AI1EC_CACHE', true );
432
+ }
433
+
434
+ if ( ! defined( 'AI1EC_DISABLE_FILE_CACHE' ) ) {
435
+ define( 'AI1EC_DISABLE_FILE_CACHE', false );
436
+ }
437
+
438
+ // A value identifying that cache is not available.
439
+ // Used in place of actual path for cache to use.
440
+ // Named constant allows reuse of a single typed variable.
441
+ if ( ! defined( 'AI1EC_CACHE_UNAVAILABLE' ) ) {
442
+ define( 'AI1EC_CACHE_UNAVAILABLE', 'AI1EC_CACHE_UNAVAILABLE' );
443
+ }
444
+
445
+ // Defines if backward (<= 2.1.5) theme compatibility is enabled or not.
446
+ if ( ! defined( 'AI1EC_THEME_COMPATIBILITY_FER' ) ) {
447
+ define( 'AI1EC_THEME_COMPATIBILITY_FER', true );
448
+ }
449
+
450
+ // Defines amount of needed free memory to compile LESS files.
451
+ if ( ! defined( 'AI1EC_LESS_MIN_AVAIL_MEMORY' ) ) {
452
+ define( 'AI1EC_LESS_MIN_AVAIL_MEMORY', '24M' );
453
+ }
454
+
455
+ // Defines if LESS files are parsed at every request
456
+ if ( ! defined( 'AI1EC_PARSE_LESS_FILES_AT_EVERY_REQUEST' ) ) {
457
+ define( 'AI1EC_PARSE_LESS_FILES_AT_EVERY_REQUEST', false );
458
+ }
459
+
460
+ // Defines a list of FER-enabled templates.
461
+ if ( ! defined( 'AI1EC_FER_ENABLED_TEMPLATES_LIST' ) ) {
462
+ define(
463
+ 'AI1EC_FER_ENABLED_TEMPLATES_LIST',
464
+ 'agenda,oneday,week,month,posterboard,stream'
465
+ );
466
+ }
467
+
468
+ // Defines API URL.
469
+ if ( ! defined( 'AI1EC_API_URL' ) ) {
470
+ define(
471
+ 'AI1EC_API_URL',
472
+ 'https://api.time.ly/api/'
473
+ );
474
+ }
475
+
476
+ // Defines Tickets checkout URL.
477
+ if ( ! defined( 'AI1EC_TICKETS_CHECKOUT_URL' ) ) {
478
+ define(
479
+ 'AI1EC_TICKETS_CHECKOUT_URL',
480
+ 'https://api.time.ly/events/{event_id}/checkout'
481
+ );
482
+ }
483
+
484
+ // ================================================
485
+ // = Force WordPress updates command link =
486
+ // ================================================
487
+ if ( ! defined( 'AI1EC_FORCE_UPDATES_URL' ) ) {
488
+ define(
489
+ 'AI1EC_FORCE_UPDATES_URL',
490
+ AI1EC_ADMIN_BASE_URL . '&ai1ec_force_updates=true'
491
+ );
492
+ }
493
  }
app/controller/calendar-feeds.php CHANGED
@@ -11,74 +11,74 @@
11
  */
12
  class Ai1ec_Controller_Calendar_Feeds extends Ai1ec_Base {
13
 
14
- /**
15
- * @var array Holds the instances of registered plugins.
16
- */
17
- protected $_plugins = array();
18
 
19
- /**
20
- * Add plugin to the internal array.
21
- *
22
- * This assure us that the plugins extends our base abstract class.
23
- *
24
- * @param Ai1ec_Connector_Plugin $plugin Plugin to add.
25
- *
26
- * @return void
27
- */
28
- public function add_plugin( Ai1ec_Connector_Plugin $plugin ) {
29
- $plugin->initialize_settings_if_not_set();
30
- $this->_plugins[] = $plugin;
31
- }
32
 
33
- /**
34
- * Get an instance of a plugin class
35
- *
36
- * @param string $class
37
- * @throws Exception
38
- * @return Ai1ec_Connector_Plugin
39
- */
40
- public function get_plugin_instance( $class ) {
41
- foreach ( $this->_plugins as $plugin ) {
42
- if( get_class( $plugin ) === $class ) {
43
- return $plugin;
44
- }
45
- }
46
- throw new Exception( "Class not found" );
47
- }
48
 
49
- /**
50
- * Give the plugins the possibility to handle data posted in the calendar feeds page
51
- *
52
- * @return void
53
- */
54
- public function handle_feeds_page_post() {
55
- // Iterate over the plugins and call the methods
56
- foreach ( $this->_plugins as $plugin ) {
57
- $plugin->handle_feeds_page_post();
58
- }
59
- }
60
 
61
- /**
62
- * Render the tab header for each plugin
63
- *
64
- * @param $active_feed
65
- * The tab that should be visualized
66
- */
67
- public function render_tab_headers() {
68
- foreach ( $this->_plugins as $plugin ) {
69
- $plugin->render_tab_header();
70
- }
71
- }
72
- /**
73
- * Render the tab body for each plugin
74
- *
75
- * @param $active_feed
76
- * The tab that should be visualized
77
- */
78
- public function render_tab_contents() {
79
- foreach ( $this->_plugins as $plugin ) {
80
- $plugin->render_tab_content();
81
- }
82
- }
83
 
84
  }
11
  */
12
  class Ai1ec_Controller_Calendar_Feeds extends Ai1ec_Base {
13
 
14
+ /**
15
+ * @var array Holds the instances of registered plugins.
16
+ */
17
+ protected $_plugins = array();
18
 
19
+ /**
20
+ * Add plugin to the internal array.
21
+ *
22
+ * This assure us that the plugins extends our base abstract class.
23
+ *
24
+ * @param Ai1ec_Connector_Plugin $plugin Plugin to add.
25
+ *
26
+ * @return void
27
+ */
28
+ public function add_plugin( Ai1ec_Connector_Plugin $plugin ) {
29
+ $plugin->initialize_settings_if_not_set();
30
+ $this->_plugins[] = $plugin;
31
+ }
32
 
33
+ /**
34
+ * Get an instance of a plugin class
35
+ *
36
+ * @param string $class
37
+ * @throws Exception
38
+ * @return Ai1ec_Connector_Plugin
39
+ */
40
+ public function get_plugin_instance( $class ) {
41
+ foreach ( $this->_plugins as $plugin ) {
42
+ if( get_class( $plugin ) === $class ) {
43
+ return $plugin;
44
+ }
45
+ }
46
+ throw new Exception( "Class not found" );
47
+ }
48
 
49
+ /**
50
+ * Give the plugins the possibility to handle data posted in the calendar feeds page
51
+ *
52
+ * @return void
53
+ */
54
+ public function handle_feeds_page_post() {
55
+ // Iterate over the plugins and call the methods
56
+ foreach ( $this->_plugins as $plugin ) {
57
+ $plugin->handle_feeds_page_post();
58
+ }
59
+ }
60
 
61
+ /**
62
+ * Render the tab header for each plugin
63
+ *
64
+ * @param $active_feed
65
+ * The tab that should be visualized
66
+ */
67
+ public function render_tab_headers() {
68
+ foreach ( $this->_plugins as $plugin ) {
69
+ $plugin->render_tab_header();
70
+ }
71
+ }
72
+ /**
73
+ * Render the tab body for each plugin
74
+ *
75
+ * @param $active_feed
76
+ * The tab that should be visualized
77
+ */
78
+ public function render_tab_contents() {
79
+ foreach ( $this->_plugins as $plugin ) {
80
+ $plugin->render_tab_content();
81
+ }
82
+ }
83
 
84
  }
app/controller/content-filter.php CHANGED
@@ -11,52 +11,52 @@
11
  */
12
  class Ai1ec_Controller_Content_Filter extends Ai1ec_Base {
13
 
14
- /**
15
- * Content filters lib.
16
- * @var Ai1ec_Content_Filters
17
- */
18
- protected $_content_filter;
19
 
20
- /**
21
- * Setting _strict_compatibility_content_filtering.
22
- * @var bool
23
- */
24
- protected $_strict_compatibility_content_filtering;
25
 
26
- /**
27
- * Constructor.
28
- *
29
- * @param Ai1ec_Registry_Object $registry Registry object.
30
- *
31
- * @return void Method does not return.
32
- */
33
- public function __construct( Ai1ec_Registry_Object $registry ) {
34
- parent::__construct( $registry );
35
- $this->_content_filter = $registry->get( 'content.filter' );
36
- $this->_strict_compatibility_content_filtering =
37
- $registry->get( 'model.settings' )
38
- ->get( 'strict_compatibility_content_filtering' );
39
- }
40
 
41
- /**
42
- * Clears all the_content filters excluding few defaults.
43
- *
44
- * @return void Method does not return.
45
- */
46
- public function clear_the_content_filters() {
47
- if ( $this->_strict_compatibility_content_filtering ) {
48
- $this->_content_filter->clear_the_content_filters();
49
- }
50
- }
51
 
52
- /**
53
- * Restores the_content filters.
54
- *
55
- * @return void Method does not return.
56
- */
57
- public function restore_the_content_filters() {
58
- if ( $this->_strict_compatibility_content_filtering ) {
59
- $this->_content_filter->restore_the_content_filters();
60
- }
61
- }
62
  }
11
  */
12
  class Ai1ec_Controller_Content_Filter extends Ai1ec_Base {
13
 
14
+ /**
15
+ * Content filters lib.
16
+ * @var Ai1ec_Content_Filters
17
+ */
18
+ protected $_content_filter;
19
 
20
+ /**
21
+ * Setting _strict_compatibility_content_filtering.
22
+ * @var bool
23
+ */
24
+ protected $_strict_compatibility_content_filtering;
25
 
26
+ /**
27
+ * Constructor.
28
+ *
29
+ * @param Ai1ec_Registry_Object $registry Registry object.
30
+ *
31
+ * @return void Method does not return.
32
+ */
33
+ public function __construct( Ai1ec_Registry_Object $registry ) {
34
+ parent::__construct( $registry );
35
+ $this->_content_filter = $registry->get( 'content.filter' );
36
+ $this->_strict_compatibility_content_filtering =
37
+ $registry->get( 'model.settings' )
38
+ ->get( 'strict_compatibility_content_filtering' );
39
+ }
40
 
41
+ /**
42
+ * Clears all the_content filters excluding few defaults.
43
+ *
44
+ * @return void Method does not return.
45
+ */
46
+ public function clear_the_content_filters() {
47
+ if ( $this->_strict_compatibility_content_filtering ) {
48
+ $this->_content_filter->clear_the_content_filters();
49
+ }
50
+ }
51
 
52
+ /**
53
+ * Restores the_content filters.
54
+ *
55
+ * @return void Method does not return.
56
+ */
57
+ public function restore_the_content_filters() {
58
+ if ( $this->_strict_compatibility_content_filtering ) {
59
+ $this->_content_filter->restore_the_content_filters();
60
+ }
61
+ }
62
  }
app/controller/extension-license.php CHANGED
@@ -11,109 +11,109 @@
11
  */
12
  abstract class Ai1ec_Base_License_Controller extends Ai1ec_Base_Extension_Controller {
13
 
14
- /**
15
- * @var string Settings entry name for license key.
16
- */
17
- protected $_licence;
18
 
19
- /**
20
- * @var string Settings entry name for license status output.
21
- */
22
- protected $_licence_status;
23
 
24
- /**
25
- * @var string Licensing API endpoint URI.
26
- */
27
- protected $_store = 'https://time.ly/';
28
 
29
- /**
30
- * Get label to be used for license input field.
31
- *
32
- * @return string Localized label field.
33
- */
34
- abstract public function get_license_label();
35
 
36
- /**
37
- * @param Ai1ec_Registry_Object $registry
38
- */
39
- public function initialize_licence_actions() {
40
- $this->_register_licence_actions();
41
- $this->_register_licence_fields();
42
- }
43
 
44
- /**
45
- * Add the extension tab if not present
46
- *
47
- * @param array $tabs
48
- * @return array
49
- */
50
- public function add_tabs( array $tabs ) {
51
- if ( ! isset( $tabs['extensions'] ) ) {
52
- $tabs['extensions'] = array(
53
- 'name' => Ai1ec_I18n::__( 'Add-ons' ),
54
- 'items' => array(),
55
- );
56
- }
57
 
58
- return $tabs;
59
- }
60
 
61
- /**
62
- * Register action for licences.
63
- */
64
- protected function _register_licence_actions() {
65
- $dispatcher = $this->_registry->get( 'event.dispatcher' );
66
- // we need the super class so we use get_class()
67
- $class = explode( '_', get_class( $this ) );
68
- $controller = strtolower( end( $class ) );
69
- $dispatcher->register_filter(
70
- 'ai1ec_add_setting_tabs',
71
- array( 'controller.' . $controller, 'add_tabs' )
72
- );
73
- }
74
 
75
- /**
76
- * Register fields for licence
77
- */
78
- protected function _register_licence_fields() {
79
- $plugin_id = $this->get_machine_name();
80
- $this->_licence = 'ai1ec_licence_' . $plugin_id;
81
- $this->_licence_status = 'ai1ec_licence_status_' . $plugin_id;
82
- $options = array(
83
- $this->_licence => array(
84
- 'type' => 'string',
85
- 'version' => $this->get_version(),
86
- 'renderer' => array(
87
- 'class' => 'input',
88
- 'group-class' => 'ai1ec-col-sm-7',
89
- 'tab' => 'extensions',
90
- 'item' => 'licenses',
91
- 'type' => 'normal',
92
- 'label' => $this->get_license_label(),
93
- 'status' => $this->_licence_status,
94
- ),
95
- 'default' => '',
96
- ),
97
- $this->_licence_status => array(
98
- 'type' => 'string',
99
- 'version' => $this->get_version(),
100
- 'default' => 'invalid',
101
- ),
102
- );
103
- $settings = $this->_registry->get( 'model.settings' );
104
- foreach ( $options as $key => $option ) {
105
- $renderer = null;
106
- if ( isset( $option['renderer'] ) ) {
107
- $renderer = $option['renderer'];
108
- }
109
- $settings->register(
110
- $key,
111
- $option['default'],
112
- $option['type'],
113
- $renderer,
114
- $option['version']
115
- );
116
- }
117
- }
118
 
119
  }
11
  */
12
  abstract class Ai1ec_Base_License_Controller extends Ai1ec_Base_Extension_Controller {
13
 
14
+ /**
15
+ * @var string Settings entry name for license key.
16
+ */
17
+ protected $_licence;
18
 
19
+ /**
20
+ * @var string Settings entry name for license status output.
21
+ */
22
+ protected $_licence_status;
23
 
24
+ /**
25
+ * @var string Licensing API endpoint URI.
26
+ */
27
+ protected $_store = 'https://time.ly/';
28
 
29
+ /**
30
+ * Get label to be used for license input field.
31
+ *
32
+ * @return string Localized label field.
33
+ */
34
+ abstract public function get_license_label();
35
 
36
+ /**
37
+ * @param Ai1ec_Registry_Object $registry
38
+ */
39
+ public function initialize_licence_actions() {
40
+ $this->_register_licence_actions();
41
+ $this->_register_licence_fields();
42
+ }
43
 
44
+ /**
45
+ * Add the extension tab if not present
46
+ *
47
+ * @param array $tabs
48
+ * @return array
49
+ */
50
+ public function add_tabs( array $tabs ) {
51
+ if ( ! isset( $tabs['extensions'] ) ) {
52
+ $tabs['extensions'] = array(
53
+ 'name' => Ai1ec_I18n::__( 'Add-ons' ),
54
+ 'items' => array(),
55
+ );
56
+ }
57
 
58
+ return $tabs;
59
+ }
60
 
61
+ /**
62
+ * Register action for licences.
63
+ */
64
+ protected function _register_licence_actions() {
65
+ $dispatcher = $this->_registry->get( 'event.dispatcher' );
66
+ // we need the super class so we use get_class()
67
+ $class = explode( '_', get_class( $this ) );
68
+ $controller = strtolower( end( $class ) );
69
+ $dispatcher->register_filter(
70
+ 'ai1ec_add_setting_tabs',
71
+ array( 'controller.' . $controller, 'add_tabs' )
72
+ );
73
+ }
74
 
75
+ /**
76
+ * Register fields for licence
77
+ */
78
+ protected function _register_licence_fields() {
79
+ $plugin_id = $this->get_machine_name();
80
+ $this->_licence = 'ai1ec_licence_' . $plugin_id;
81
+ $this->_licence_status = 'ai1ec_licence_status_' . $plugin_id;
82
+ $options = array(
83
+ $this->_licence => array(
84
+ 'type' => 'string',
85
+ 'version' => $this->get_version(),
86
+ 'renderer' => array(
87
+ 'class' => 'input',
88
+ 'group-class' => 'ai1ec-col-sm-7',
89
+ 'tab' => 'extensions',
90
+ 'item' => 'licenses',
91
+ 'type' => 'normal',
92
+ 'label' => $this->get_license_label(),
93
+ 'status' => $this->_licence_status,
94
+ ),
95
+ 'default' => '',
96
+ ),
97
+ $this->_licence_status => array(
98
+ 'type' => 'string',
99
+ 'version' => $this->get_version(),
100
+ 'default' => 'invalid',
101
+ ),
102
+ );
103
+ $settings = $this->_registry->get( 'model.settings' );
104
+ foreach ( $options as $key => $option ) {
105
+ $renderer = null;
106
+ if ( isset( $option['renderer'] ) ) {
107
+ $renderer = $option['renderer'];
108
+ }
109
+ $settings->register(
110
+ $key,
111
+ $option['default'],
112
+ $option['type'],
113
+ $renderer,
114
+ $option['version']
115
+ );
116
+ }
117
+ }
118
 
119
  }
app/controller/extension.php CHANGED
@@ -11,281 +11,281 @@
11
  */
12
  abstract class Ai1ec_Base_Extension_Controller {
13
 
14
- /**
15
- * @var Ai1ec_Registry_Object
16
- */
17
- protected $_registry;
18
 
19
- /**
20
- * @var array
21
- */
22
- protected $_settings;
23
 
24
- /**
25
- * @var Ai1ec_Registry_Object
26
- */
27
- protected static $_registry_static;
28
 
29
- /**
30
- * @var array
31
- */
32
- protected static $_settings_static;
33
 
34
- /**
35
- * @var array
36
- */
37
- protected static $_schema;
38
 
39
- /**
40
- * Get the long name of the extension
41
- */
42
- abstract public function get_name();
43
 
44
- /**
45
- * Get the machine name of the extension
46
- */
47
- abstract public function get_machine_name();
48
 
49
- /**
50
- * Get the version of the extension
51
- */
52
- abstract public function get_version();
53
 
54
- /**
55
- * Get the name of the main plugin file
56
- */
57
- abstract public function get_file();
58
 
59
- /**
60
- * Add extension specific settings
61
- */
62
- abstract protected function _get_settings();
63
 
64
- /**
65
- * Register action/filters/shortcodes for the extension
66
- *
67
- * @param Ai1ec_Event_Dispatcher $dispatcher
68
- */
69
- abstract protected function _register_actions(
70
- Ai1ec_Event_Dispatcher $dispatcher
71
- );
72
 
73
- /**
74
- * Perform the basic compatibility check.
75
- *
76
- * @param string $ai1ec_version
77
- *
78
- * @return boolean
79
- */
80
- public function check_compatibility( $ai1ec_version ) {
81
- return version_compare(
82
- $ai1ec_version,
83
- $this->minimum_core_required(),
84
- '>='
85
- );
86
- }
87
 
88
- /**
89
- * @return string
90
- */
91
- public function minimum_core_required() {
92
- return '2.0.7';
93
- }
94
- /**
95
- * Removes options when uninstalling the plugin.
96
- */
97
- public static function on_uninstall() {
98
- global $wpdb;
99
- if ( ! current_user_can( 'activate_plugins' ) ) {
100
- return;
101
- }
102
- $settings = self::$_registry_static->get( 'model.settings' );
103
- foreach ( self::$_settings_static as $name => $params ) {
104
- $settings->remove_option( $name );
105
- }
106
- $schema = self::$_schema;
107
- foreach ( $schema['tables'] as $table_name ) {
108
- // Delete table events
109
- $wpdb->query( 'DROP TABLE IF EXISTS ' . $table_name );
110
- }
111
- }
112
 
113
- /**
114
- * Public constructor
115
- */
116
- public function __construct() {
117
- global $wpdb;
118
- self::$_schema = $this->_get_schema( $wpdb->prefix );
119
- $settings = $this->_get_settings();
120
- $this->_settings = $settings;
121
- self::$_settings_static = $settings;
122
- }
123
 
124
- /**
125
- * initialize the extension.
126
- */
127
- public function init( Ai1ec_Registry_Object $registry ) {
128
- $this->_registry = $registry;
129
- // static properties are needed as uninstall hook must be static
130
- // http://wpseek.com/register_uninstall_hook/
131
- self::$_registry_static = $registry;
132
- register_deactivation_hook(
133
- $this->get_file(),
134
- array( $this, 'on_deactivation' )
135
- );
136
 
137
- $this->_install_schema( $registry );
138
- $this->_register_actions( $registry->get( 'event.dispatcher' ) );
139
- $this->_add_settings( $registry->get( 'model.settings' ) );
140
- $this->_perform_upgrade( $registry );
141
- if ( method_exists( $this, 'initialize_licence_actions' ) ) {
142
- $this->initialize_licence_actions();
143
- }
144
- }
145
 
146
- /**
147
- * Hides settings on deactivation.
148
- */
149
- public function on_deactivation() {
150
- if ( ! current_user_can( 'activate_plugins' ) ) {
151
- return;
152
- }
153
- $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
154
- $referer = 'deactivate-plugin_' . $plugin;
155
- // if we are disabling the plugin in the exception handler, this can't be done.
156
- // but i want to disable options
157
- if ( function_exists( '_get_list_table' ) ) {
158
- $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
159
- $action = $wp_list_table->current_action();
160
- if ( 'deactivate-selected' === $action ) {
161
- $referer = 'bulk-plugins';
162
- }
163
- check_admin_referer( $referer );
164
- }
165
- $settings = $this->_registry->get( 'model.settings' );
166
- foreach ( $this->_settings as $name => $params ) {
167
- $settings->hide_option( $name );
168
- }
169
- }
170
 
171
- /**
172
- * Show the settings
173
- */
174
- public function show_settings( Ai1ec_Registry_Object $registry ) {
175
- $settings = $registry->get( 'model.settings' );
176
- foreach ( $this->_settings as $name => $params ) {
177
- if ( isset( $params['renderer'] ) ) {
178
- $settings->show_option( $name, $params['renderer'] );
179
- }
180
- }
181
- $settings->set( 'allow_statistics', true );
182
- }
183
 
184
- /**
185
- * If extensions need to add tables, they will need to override the function to add a schema.
186
- *
187
- * @param string $prefix Database prefix to use for table names.
188
- *
189
- * @return array An array with two keys, schema and tables which are used
190
- * for installing and dropping the table.
191
- */
192
- protected static function _get_schema( $prefix ) {
193
- return array(
194
- 'tables' => array(),
195
- 'schema' => '',
196
- );
197
- }
198
 
199
- /**
200
- * Performe upgarde actions based on extension version
201
- *
202
- * @param Ai1ec_Registry_Object $registry
203
- */
204
- protected function _perform_upgrade( Ai1ec_Registry_Object $registry ) {
205
- $version_variable = 'ai1ec_' . $this->get_machine_name() .
206
- '_version';
207
- $option = $registry->get( 'model.option' );
208
- $version = $option->get( $version_variable );
209
- if ( $version !== $this->get_version() ) {
210
- $registry->get( 'model.settings' )->perform_upgrade_actions();
211
- $this->_perform_upgrade_actions();
212
- $option->set( $version_variable, $this->get_version(), true );
213
- }
214
- }
215
 
216
- /**
217
- * Function called on add on upgrade.
218
- * Can be overridden by add ons for extra behaviour
219
- */
220
- protected function _perform_upgrade_actions() {
221
-
222
- }
223
 
224
- /**
225
- * Since the call the to the uninstall hook it's static, if a different behaviour
226
- * is needed also this call must be overridden.
227
- */
228
- protected function _register_uninstall_hook() {
229
- register_uninstall_hook(
230
- $this->get_file(),
231
- array( get_class( $this ), 'on_uninstall' )
232
- );
233
- }
234
 
235
- /**
236
- * Adds extension settings
237
- *
238
- * @param Ai1ec_Settings $settings
239
- */
240
- protected function _add_settings( Ai1ec_Settings $settings ) {
241
- foreach ( $this->_settings as $name => $params ) {
242
- $renderer = null;
243
- if ( isset( $params['renderer'] ) ) {
244
- $renderer = $params['renderer'];
245
- }
246
- $settings->register(
247
- $name,
248
- $params['value'],
249
- $params['type'],
250
- $renderer,
251
- $this->get_version()
252
- );
253
- }
254
- }
255
 
256
- /**
257
- * Check if the schema needs to be updated
258
- *
259
- * @param Ai1ec_Registry_Object $registry
260
- * @throws Ai1ec_Database_Update_Exception
261
- */
262
- protected function _install_schema( Ai1ec_Registry_Object $registry ) {
263
- $option = $registry->get( 'model.option' );
264
- $schema = self::$_schema;
265
- if (
266
- is_admin() &&
267
- ! empty( $schema['schema'] )
268
- ) {
269
- $db_version_variable = 'ai1ec_' . $this->get_machine_name() .
270
- '_db_version';
271
- $version = sha1( $schema['schema'] );
272
- if (
273
- $option->get( $db_version_variable ) !== $version
274
- ) {
275
- if (
276
- $registry->get( 'database.helper' )->apply_delta(
277
- $schema['schema']
278
- )
279
- ) {
280
- $option->set( $db_version_variable, $version );
281
- } else {
282
- throw new Ai1ec_Database_Update_Exception(
283
- 'Database upgrade for ' . $this->get_name() .
284
- ' failed'
285
- );
286
- }
287
- }
288
- }
289
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  }
11
  */
12
  abstract class Ai1ec_Base_Extension_Controller {
13
 
14
+ /**
15
+ * @var Ai1ec_Registry_Object
16
+ */
17
+ protected $_registry;
18
 
19
+ /**
20
+ * @var array
21
+ */
22
+ protected $_settings;
23
 
24
+ /**
25
+ * @var Ai1ec_Registry_Object
26
+ */
27
+ protected static $_registry_static;
28
 
29
+ /**
30
+ * @var array
31
+ */
32
+ protected static $_settings_static;
33
 
34
+ /**
35
+ * @var array
36
+ */
37
+ protected static $_schema;
38
 
39
+ /**
40
+ * Get the long name of the extension
41
+ */
42
+ abstract public function get_name();
43
 
44
+ /**
45
+ * Get the machine name of the extension
46
+ */
47
+ abstract public function get_machine_name();
48
 
49
+ /**
50
+ * Get the version of the extension
51
+ */
52
+ abstract public function get_version();
53
 
54
+ /**
55
+ * Get the name of the main plugin file
56
+ */
57
+ abstract public function get_file();
58
 
59
+ /**
60
+ * Add extension specific settings
61
+ */
62
+ abstract protected function _get_settings();
63
 
64
+ /**
65
+ * Register action/filters/shortcodes for the extension
66
+ *
67
+ * @param Ai1ec_Event_Dispatcher $dispatcher
68
+ */
69
+ abstract protected function _register_actions(
70
+ Ai1ec_Event_Dispatcher $dispatcher
71
+ );
72
 
73
+ /**
74
+ * Perform the basic compatibility check.
75
+ *
76
+ * @param string $ai1ec_version
77
+ *
78
+ * @return boolean
79
+ */
80
+ public function check_compatibility( $ai1ec_version ) {
81
+ return version_compare(
82
+ $ai1ec_version,
83
+ $this->minimum_core_required(),
84
+ '>='
85
+ );
86
+ }
87
 
88
+ /**
89
+ * @return string
90
+ */
91
+ public function minimum_core_required() {
92
+ return '2.0.7';
93
+ }
94
+ /**
95
+ * Removes options when uninstalling the plugin.
96
+ */
97
+ public static function on_uninstall() {
98
+ global $wpdb;
99
+ if ( ! current_user_can( 'activate_plugins' ) ) {
100
+ return;
101
+ }
102
+ $settings = self::$_registry_static->get( 'model.settings' );
103
+ foreach ( self::$_settings_static as $name => $params ) {
104
+ $settings->remove_option( $name );
105
+ }
106
+ $schema = self::$_schema;
107
+ foreach ( $schema['tables'] as $table_name ) {
108
+ // Delete table events
109
+ $wpdb->query( 'DROP TABLE IF EXISTS ' . $table_name );
110
+ }
111
+ }
112
 
113
+ /**
114
+ * Public constructor
115
+ */
116
+ public function __construct() {
117
+ global $wpdb;
118
+ self::$_schema = $this->_get_schema( $wpdb->prefix );
119
+ $settings = $this->_get_settings();
120
+ $this->_settings = $settings;
121
+ self::$_settings_static = $settings;
122
+ }
123
 
124
+ /**
125
+ * initialize the extension.
126
+ */
127
+ public function init( Ai1ec_Registry_Object $registry ) {
128
+ $this->_registry = $registry;
129
+ // static properties are needed as uninstall hook must be static
130
+ // http://wpseek.com/register_uninstall_hook/
131
+ self::$_registry_static = $registry;
132
+ register_deactivation_hook(
133
+ $this->get_file(),
134
+ array( $this, 'on_deactivation' )
135
+ );
136
 
137
+ $this->_install_schema( $registry );
138
+ $this->_register_actions( $registry->get( 'event.dispatcher' ) );
139
+ $this->_add_settings( $registry->get( 'model.settings' ) );
140
+ $this->_perform_upgrade( $registry );
141
+ if ( method_exists( $this, 'initialize_licence_actions' ) ) {
142
+ $this->initialize_licence_actions();
143
+ }
144
+ }
145
 
146
+ /**
147
+ * Hides settings on deactivation.
148
+ */
149
+ public function on_deactivation() {
150
+ if ( ! current_user_can( 'activate_plugins' ) ) {
151
+ return;
152
+ }
153
+ $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
154
+ $referer = 'deactivate-plugin_' . $plugin;
155
+ // if we are disabling the plugin in the exception handler, this can't be done.
156
+ // but i want to disable options
157
+ if ( function_exists( '_get_list_table' ) ) {
158
+ $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
159
+ $action = $wp_list_table->current_action();
160
+ if ( 'deactivate-selected' === $action ) {
161
+ $referer = 'bulk-plugins';
162
+ }
163
+ check_admin_referer( $referer );
164
+ }
165
+ $settings = $this->_registry->get( 'model.settings' );
166
+ foreach ( $this->_settings as $name => $params ) {
167
+ $settings->hide_option( $name );
168
+ }
169
+ }
170
 
171
+ /**
172
+ * Show the settings
173
+ */
174
+ public function show_settings( Ai1ec_Registry_Object $registry ) {
175
+ $settings = $registry->get( 'model.settings' );
176
+ foreach ( $this->_settings as $name => $params ) {
177
+ if ( isset( $params['renderer'] ) ) {
178
+ $settings->show_option( $name, $params['renderer'] );
179
+ }
180
+ }
181
+ $settings->set( 'allow_statistics', true );
182
+ }
183
 
184
+ /**
185
+ * If extensions need to add tables, they will need to override the function to add a schema.
186
+ *
187
+ * @param string $prefix Database prefix to use for table names.
188
+ *
189
+ * @return array An array with two keys, schema and tables which are used
190
+ * for installing and dropping the table.
191
+ */
192
+ protected static function _get_schema( $prefix ) {
193
+ return array(
194
+ 'tables' => array(),
195
+ 'schema' => '',
196
+ );
197
+ }
198
 
199
+ /**
200
+ * Performe upgarde actions based on extension version
201
+ *
202
+ * @param Ai1ec_Registry_Object $registry
203
+ */
204
+ protected function _perform_upgrade( Ai1ec_Registry_Object $registry ) {
205
+ $version_variable = 'ai1ec_' . $this->get_machine_name() .
206
+ '_version';
207
+ $option = $registry->get( 'model.option' );
208
+ $version = $option->get( $version_variable );
209
+ if ( $version !== $this->get_version() ) {
210
+ $registry->get( 'model.settings' )->perform_upgrade_actions();
211
+ $this->_perform_upgrade_actions();
212
+ $option->set( $version_variable, $this->get_version(), true );
213
+ }
214
+ }
215
 
216
+ /**
217
+ * Function called on add on upgrade.
218
+ * Can be overridden by add ons for extra behaviour
219
+ */
220
+ protected function _perform_upgrade_actions() {
 
 
221
 
222
+ }
 
 
 
 
 
 
 
 
 
223
 
224
+ /**
225
+ * Since the call the to the uninstall hook it's static, if a different behaviour
226
+ * is needed also this call must be overridden.
227
+ */
228
+ protected function _register_uninstall_hook() {
229
+ register_uninstall_hook(
230
+ $this->get_file(),
231
+ array( get_class( $this ), 'on_uninstall' )
232
+ );
233
+ }
 
 
 
 
 
 
 
 
 
 
234
 
235
+ /**
236
+ * Adds extension settings
237
+ *
238
+ * @param Ai1ec_Settings $settings
239
+ */
240
+ protected function _add_settings( Ai1ec_Settings $settings ) {
241
+ foreach ( $this->_settings as $name => $params ) {
242
+ $renderer = null;
243
+ if ( isset( $params['renderer'] ) ) {
244
+ $renderer = $params['renderer'];
245
+ }
246
+ $settings->register(
247
+ $name,
248
+ $params['value'],
249
+ $params['type'],
250
+ $renderer,
251
+ $this->get_version()
252
+ );
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Check if the schema needs to be updated
258
+ *
259
+ * @param Ai1ec_Registry_Object $registry
260
+ * @throws Ai1ec_Database_Update_Exception
261
+ */
262
+ protected function _install_schema( Ai1ec_Registry_Object $registry ) {
263
+ $option = $registry->get( 'model.option' );
264
+ $schema = self::$_schema;
265
+ if (
266
+ is_admin() &&
267
+ ! empty( $schema['schema'] )
268
+ ) {
269
+ $db_version_variable = 'ai1ec_' . $this->get_machine_name() .
270
+ '_db_version';
271
+ $version = sha1( $schema['schema'] );
272
+ if (
273
+ $option->get( $db_version_variable ) !== $version
274
+ ) {
275
+ if (
276
+ $registry->get( 'database.helper' )->apply_delta(
277
+ $schema['schema']
278
+ )
279
+ ) {
280
+ $option->set( $db_version_variable, $version );
281
+ } else {
282
+ throw new Ai1ec_Database_Update_Exception(
283
+ 'Database upgrade for ' . $this->get_name() .
284
+ ' failed'
285
+ );
286
+ }
287
+ }
288
+ }
289
+ }
290
 
291
  }
app/controller/front.php CHANGED
@@ -11,1211 +11,1217 @@
11
  */
12
  class Ai1ec_Front_Controller {
13
 
14
- /**
15
- * @var Ai1ec_Registry_Object The Object registry.
16
- */
17
- protected $_registry;
18
 
19
- /**
20
- * @var bool Whether the domain has alredy been loaded or not.
21
- */
22
- protected $_domain_loaded = false;
23
 
24
- /**
25
- * @var string The pagebase used by Ai1ec_Href_Helper.
26
- */
27
- protected $_pagebase_for_href;
28
 
29
- /**
30
- * @var Ai1ec_Request_Parser Instance of the request pa
31
- */
32
- protected $_request;
33
 
34
- /**
35
- * @var array
36
- */
37
- protected $_default_theme;
38
 
39
- /**
40
- * Initializes the default theme property.
41
- */
42
- public function __construct() {
43
- // Initialize default theme.
44
- $this->_default_theme = array(
45
- 'theme_dir' => AI1EC_DEFAULT_THEME_PATH,
46
- 'theme_root' => AI1EC_DEFAULT_THEME_ROOT,
47
- 'theme_url' => AI1EC_THEMES_URL . '/' . AI1EC_DEFAULT_THEME_NAME,
48
- 'stylesheet' => AI1EC_DEFAULT_THEME_NAME,
49
- 'legacy' => false,
50
- );
51
- }
52
 
53
- /**
54
- * Initialize the controller.
55
- *
56
- * @param Ai1ec_Loader $ai1ec_loader Instance of Ai1EC classes loader
57
- *
58
- * @return void
59
- */
60
- public function initialize( $ai1ec_loader ) {
61
- ai1ec_start();
62
- $this->_init( $ai1ec_loader );
63
- $this->_initialize_dispatcher();
64
- $lessphp = $this->_registry->get( 'less.lessphp' );
65
- $lessphp->initialize_less_variables_if_not_set();
66
- $this->_registry->get( 'controller.shutdown' )
67
- ->register( 'ai1ec_stop' );
68
- add_action( 'plugins_loaded', array( $this, 'register_extensions' ), 1 );
69
- add_action( 'after_setup_theme', array( $this, 'register_themes' ), 1 );
70
- add_action( 'init', array( $lessphp, 'invalidate_css_cache_if_requested' ) );
71
- }
72
 
73
- /**
74
- * Let other objects access default theme
75
- *
76
- * @return array
77
- */
78
- public function get_default_theme() {
79
- return $this->_default_theme;
80
- }
81
 
82
- /**
83
- * Remove unwanted menus
84
- */
85
- public function admin_menu() {
86
- remove_submenu_page(
87
- 'edit.php?post_type=ai1ec_event',
88
- 'edit-tags.php?taxonomy=events_tags&amp;post_type=ai1ec_event'
89
- );
90
- }
91
 
92
- /**
93
- * Notify extensions and pass them instance of objects registry.
94
- *
95
- * @return void
96
- */
97
- public function register_extensions() {
98
- do_action( 'ai1ec_loaded', $this->_registry );
99
- }
100
 
101
- /**
102
- * Notify themes and pass them instance of objects registry.
103
- *
104
- * @return void
105
- */
106
- public function register_themes() {
107
- do_action( 'ai1ec_after_themes_setup', $this->_registry );
108
- }
109
 
110
- /**
111
- * Returns the registry object
112
- *
113
- * @param mixed $discard not used. Always return the registry.
114
- *
115
- * @return Ai1ec_Registry_Object
116
- */
117
- public function return_registry( $discard ) {
118
- return $this->_registry;
119
- }
120
 
121
- /**
122
- * If WIDGET_PARAMETER is set.
123
- *
124
- * @return boolean
125
- */
126
- protected function is_widget() {
127
- return isset(
128
- $_GET[Ai1ec_Controller_Javascript_Widget::WIDGET_PARAMETER]
129
- );
130
- }
131
 
132
- /**
133
- * If Advanced JS cache enabled.
134
- *
135
- * @return boolean
136
- */
137
- protected function if_js_cache_enabled() {
138
- $settings = $this->_registry->get( 'model.settings' );
139
- return $settings->get( 'cache_dynamic_js' );
140
- }
141
 
142
- /**
143
- * If LEGACY_WIDGET_PARAMETER is set.
144
- *
145
- * @return boolean
146
- */
147
- protected function is_legacy_widget() {
148
- return isset(
149
- $_GET[Ai1ec_Controller_Javascript_Widget::LEGACY_WIDGET_PARAMETER]
150
- );
151
- }
152
 
153
- /**
154
- * Execute commands if our plugin must handle the request.
155
- *
156
- * @wp_hook init
157
- *
158
- * @return void
159
- */
160
- public function route_request() {
161
- $this->_process_request();
162
- // get the resolver
163
- $resolver = $this->_registry->get(
164
- 'command.resolver',
165
- $this->_request
166
- );
167
- // get the command
168
- $commands = $resolver->get_commands();
169
- // if we have a command
170
- if ( ! empty( $commands ) ) {
171
- foreach( $commands as $command ) {
172
- $result = $command->execute();
173
- if ( $command->stop_execution() ) {
174
- return $result;
175
- }
176
- }
177
- }
178
- }
179
 
180
- /**
181
- * Initializes the URL router used by our plugin.
182
- *
183
- * @wp_hook init
184
- *
185
- * @return void
186
- */
187
- public function initialize_router() {
188
- /* @var $cal_state Ai1ec_Calendar_State */
189
- $cal_state = $this->_registry->get( 'calendar.state' );
190
- $cal_state->set_routing_initialization( true );
191
- $settings = $this->_registry->get( 'model.settings' );
192
- $cal_page = $settings->get( 'calendar_page_id' );
193
 
194
- if (
195
- ! $cal_page ||
196
- $cal_page < 1
197
- ) { // Routing may not be affected in any way if no calendar page exists.
198
- $cal_state->set_routing_initialization( false );
199
- return null;
200
- }
201
- $router = $this->_registry->get( 'routing.router' );
202
- $localization_helper = $this->_registry->get( 'p28n.wpml' );
203
- $page_base = '';
204
- $clang = '';
205
 
206
- if ( $localization_helper->is_wpml_active() ) {
207
- $trans = $localization_helper
208
- ->get_wpml_translations_of_page(
209
- $cal_page,
210
- true
211
- );
212
- $clang = $localization_helper->get_language();
213
- if ( isset( $trans[$clang] ) ) {
214
- $cal_page = $trans[$clang];
215
- }
216
- }
217
- $template_link_helper = $this->_registry->get( 'template.link.helper' );
218
 
219
- if ( ! get_post( $cal_page ) ) {
220
- $cal_state->set_routing_initialization( false );
221
- return null;
222
- }
223
 
224
- $page_base = $template_link_helper->get_page_link(
225
- $cal_page
226
- );
227
 
228
- $page_base = Ai1ec_Wp_Uri_Helper::get_pagebase( $page_base );
229
- $page_link = 'index.php?page_id=' .
230
- $cal_page;
231
- $pagebase_for_href = Ai1ec_Wp_Uri_Helper::get_pagebase_for_links(
232
- get_page_link( $cal_page ),
233
- $clang
234
- );
235
 
236
- // save the pagebase to set up the factory later
237
- $application = $this->_registry->get( 'bootstrap.registry.application' );
238
- $application->set( 'calendar_base_page', $pagebase_for_href );
239
- $option = $this->_registry->get( 'model.option' );
240
 
241
- // If the calendar is set as the front page, disable permalinks.
242
- // They would not be legal under a Windows server. See:
243
- // https://issues.apache.org/bugzilla/show_bug.cgi?id=41441
244
 
245
- if (
246
- $option->get( 'permalink_structure' ) &&
247
- ( int ) get_option( 'page_on_front' ) !==
248
- ( int ) $cal_page
249
- ) {
250
- $application->set( 'permalinks_enabled', true );
251
- }
252
 
253
- $router->asset_base( $page_base )
254
- ->register_rewrite( $page_link );
255
- $cal_state->set_routing_initialization( false );
256
- }
257
 
258
- /**
259
- * Initialize the system.
260
- *
261
- * Perform all the inizialization needed for the system.
262
- * Throws some uncatched exception for critical failures.
263
- * Plugin will be disabled by the exception handler on those failures.
264
- *
265
- * @param Ai1ec_Loader $ai1ec_loader Instance of Ai1EC classes loader
266
- *
267
- * @throws Ai1ec_Constants_Not_Set_Exception
268
- * @throws Ai1ec_Database_Update_Exception
269
- * @throws Ai1ec_Database_Schema_Exception
270
- *
271
- * @return void Method does not return
272
- */
273
- protected function _init( $ai1ec_loader ) {
274
- $exception = null;
275
- // Load the textdomain
276
- add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );
277
- try {
278
- // Initialize the registry object
279
- $this->_initialize_registry( $ai1ec_loader );
280
- $this->_registry->get( 'event.dispatcher' )->register_filter(
281
- 'ai1ec_perform_scheme_update',
282
- array( 'database.datetime-migration', 'filter_scheme_update' )
283
- );
284
- // Procedures to take when upgrading plugin version
285
- $this->_plugin_upgrade_procedures();
286
- // Load the css if needed
287
- $this->_load_css_if_needed();
288
- // Initialize the crons
289
- $this->_install_crons();
290
- // Register the activation hook
291
- $this->_initialize_schema();
292
- // set the default theme if not set
293
- $this->_add_default_theme_if_not_set();
294
- } catch ( Ai1ec_Constants_Not_Set_Exception $e ) {
295
- // This is blocking, throw it and disable the plugin
296
- $exception = $e;
297
- } catch ( Ai1ec_Database_Update_Exception $e ) {
298
- // Blocking throw it so that the plugin is disabled
299
- $exception = $e;
300
- } catch ( Ai1ec_Database_Schema_Exception $e ) {
301
- // Blocking throw it so that the plugin is disabled
302
- $exception = $e;
303
- } catch ( Ai1ec_Scheduling_Exception $e ) {
304
- // not blocking
305
- }
306
 
307
- if ( null !== $exception ) {
308
- throw $exception;
309
- }
310
- }
311
 
312
- /**
313
- * Set the default theme if no theme is set, or populate theme info array if
314
- * insufficient information is currently being stored.
315
- *
316
- * @uses apply_filters() Calls 'ai1ec_pre_save_current_theme' hook to allow
317
- * overwriting of theme information before being stored.
318
- */
319
- protected function _add_default_theme_if_not_set() {
320
- $option = $this->_registry->get( 'model.option' );
321
- $theme = $option->get( 'ai1ec_current_theme', array() );
322
- $update = false;
323
- // Theme setting is undefined; default to Vortex.
324
- if ( empty( $theme ) ) {
325
- $theme = $this->_default_theme;
326
- $update = true;
327
- }
328
- // Legacy settings; in 1.x the active theme was stored as a bare string,
329
- // and they were located in a different folder than they are now.
330
- else if ( is_string( $theme ) ) {
331
- $theme_name = strtolower( $theme );
332
- $core_themes = explode( ',', AI1EC_CORE_THEMES );
333
- $legacy = ! in_array( $theme_name, $core_themes );
334
 
335
- if ( $legacy ) {
336
- $root = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . AI1EC_THEME_FOLDER;
337
- $url = WP_CONTENT_URL . '/' . AI1EC_THEME_FOLDER . '/' . $theme_name;
338
- } else {
339
- $root = AI1EC_DEFAULT_THEME_ROOT;
340
- $url = AI1EC_THEMES_URL . '/' . $theme_name;
341
- }
342
- // if it's from 1.x, move folders to avoid confusion
343
- if ( apply_filters( 'ai1ec_move_themes_to_backup', true ) ) {
344
- $this->_registry->get( 'theme.search' )
345
- ->move_themes_to_backup( $core_themes );
346
- }
347
- // Ensure existence of theme directory.
348
- if ( ! is_dir( $root . DIRECTORY_SEPARATOR . $theme_name ) ) {
349
- // It's missing; something is wrong with this theme. Reset theme to
350
- // Vortex and warn the user accordingly.
351
- $option->set( 'ai1ec_current_theme', $this->_default_theme );
352
- $notification = $this->_registry->get( 'notification.admin' );
353
- $notification->store(
354
- sprintf(
355
- Ai1ec_I18n::__(
356
- 'Your active calendar theme could not be properly initialized. The default theme has been activated instead. Please visit %s and try reactivating your theme manually.'
357
- ),
358
- '<a href="' . ai1ec_admin_url( AI1EC_THEME_SELECTION_BASE_URL ) . '">' .
359
- Ai1ec_I18n::__( 'Calendar Themes' ) . '</a>'
360
- ),
361
- 'error',
362
- 1
363
- );
364
- }
365
 
366
- $theme = array(
367
- 'theme_dir' => $root . DIRECTORY_SEPARATOR . $theme_name,
368
- 'theme_root' => $root,
369
- 'theme_url' => $url,
370
- 'stylesheet' => $theme_name,
371
- 'legacy' => $legacy,
372
- );
373
- $update = true;
374
- }
375
- // Ensure 'theme_url' is defined, as this property was added after the first
376
- // public beta release.
377
- else if ( ! isset( $theme['theme_url'] ) ) {
378
- if ( $theme['legacy'] ) {
379
- $theme['theme_url'] = WP_CONTENT_URL . '/' . AI1EC_THEME_FOLDER . '/' .
380
- $theme['stylesheet'];
381
- } else {
382
- $theme['theme_url'] = AI1EC_THEMES_URL . '/' . $theme['stylesheet'];
383
- }
384
- $update = true;
385
- }
386
 
387
- if ( $update ) {
388
- $theme = apply_filters( 'ai1ec_pre_save_current_theme', $theme );
389
- $option->set( 'ai1ec_current_theme', $theme );
390
- }
391
- }
392
 
393
- /**
394
- * Adds actions handled by the front controller.
395
- */
396
- protected function _add_front_controller_actions() {
397
- // Initialize router. I use add_action as the dispatcher would just add
398
- // overhead.
399
- add_action(
400
- 'init',
401
- array( $this, 'initialize_router' ),
402
- PHP_INT_MAX - 1
403
- );
404
- add_action(
405
- 'widgets_init',
406
- array( 'Ai1ec_View_Admin_Widget', 'register_widget' )
407
- );
408
 
409
- if (
410
- $this->is_widget() ||
411
- $this->is_legacy_widget()
412
- ) {
413
- $this->_registry->get( 'event.dispatcher' )->register_action(
414
- 'init',
415
- array( 'controller.javascript-widget', 'render_js_widget' ),
416
- PHP_INT_MAX
417
- );
418
- }
419
 
420
- // Route the request.
421
- $action = 'template_redirect';
422
- if ( is_admin() ) {
423
- $action = 'init';
424
- add_action( 'admin_menu', array( $this, 'admin_menu' ) );
425
- }
426
- add_action( $action, array( $this, 'route_request' ) );
427
- add_filter( 'ai1ec_registry', array( $this, 'return_registry' ) );
428
- }
429
 
430
- /**
431
- * Initialize the dispatcher.
432
- *
433
- * Complete this when writing the dispatcher.
434
- *
435
- * @return void
436
- */
437
- protected function _initialize_dispatcher() {
438
- $dispatcher = $this->_registry->get( 'event.dispatcher' );
439
- $dispatcher->register_action(
440
- 'init',
441
- array( 'post.custom-type', 'register' )
442
- );
443
- $this->_add_front_controller_actions();
444
- if ( isset( $_GET[Ai1ec_Javascript_Controller::LOAD_JS_PARAMETER] ) ) {
445
- $dispatcher->register_action(
446
- 'wp_loaded',
447
- array( 'controller.javascript', 'render_js' )
448
- );
449
- }
450
- $dispatcher->register_action(
451
- 'before_delete_post',
452
- array( 'model.event.trashing', 'before_delete_post' ),
453
- 0,
454
- 3
455
- );
456
- $dispatcher->register_action(
457
- 'delete_post',
458
- array( 'model.event.trashing', 'delete' )
459
- );
460
- $dispatcher->register_action(
461
- 'wp_trash_post',
462
- array( 'model.event.trashing', 'trash_post' )
463
- );
464
- $dispatcher->register_action(
465
- 'trashed_post',
466
- array( 'model.event.trashing', 'trashed_post' )
467
- );
468
- $dispatcher->register_action(
469
- 'untrash_post',
470
- array( 'model.event.trashing', 'untrash_post' )
471
- );
472
- $dispatcher->register_action(
473
- 'untrashed_post',
474
- array( 'model.event.trashing', 'untrashed_post' )
475
- );
476
- $dispatcher->register_action(
477
- 'pre_http_request',
478
- array( 'http.request', 'pre_http_request' ),
479
- 10,
480
- 3
481
- );
482
- $dispatcher->register_action(
483
- 'http_request_args',
484
- array( 'http.request', 'init_certificate' ),
485
- 10,
486
- 2
487
- );
488
- // add the filter to let the organize page work
489
- $dispatcher->register_action(
490
- 'admin_init',
491
- array( 'view.admin.organize', 'add_taxonomy_actions' ),
492
- 10000
493
- );
494
- $dispatcher->register_action(
495
- 'plugins_loaded',
496
- array( 'theme.loader', 'clean_cache_on_upgrade' ),
497
- PHP_INT_MAX
498
- );
499
- $dispatcher->register_filter(
500
- 'get_the_excerpt',
501
- array( 'view.event.content', 'event_excerpt' ),
502
- 11
503
- );
504
- remove_filter( 'the_excerpt', 'wpautop', 10 );
505
- $dispatcher->register_filter(
506
- 'the_excerpt',
507
- array( 'view.event.content', 'event_excerpt_noautop' ),
508
- 11
509
- );
510
- $dispatcher->register_filter(
511
- 'robots_txt',
512
- array( 'robots.helper', 'rules' ),
513
- 10,
514
- 2
515
- );
516
- $dispatcher->register_filter(
517
- 'ai1ec_dbi_debug',
518
- array( 'http.request', 'debug_filter' )
519
- );
520
- $dispatcher->register_filter(
521
- 'ai1ec_dbi_debug',
522
- array( 'compatibility.cli', 'disable_db_debug' )
523
- );
524
- // editing a child instance
525
- if ( isset( $_SERVER['SCRIPT_NAME'] ) && basename( $_SERVER['SCRIPT_NAME'] ) === 'post.php' ) {
526
- $dispatcher->register_action(
527
- 'admin_action_editpost',
528
- array( 'model.event.parent', 'admin_init_post' )
529
- );
530
- $dispatcher->register_filter(
531
- 'user_has_cap',
532
- array( 'content.filter', 'display_trash_link' ),
533
- 10,
534
- 2
535
- );
536
- }
537
- // post row action for parent/child
538
- $dispatcher->register_action(
539
- 'post_row_actions',
540
- array( 'model.event.parent', 'post_row_actions' ),
541
- 100,
542
- 2
543
- );
544
- // Category colors
545
- $dispatcher->register_action(
546
- 'events_categories_add_form_fields',
547
- array( 'view.admin.event-category', 'events_categories_add_form_fields' )
548
- );
549
- $dispatcher->register_action(
550
- 'events_categories_edit_form_fields',
551
- array( 'view.admin.event-category', 'events_categories_edit_form_fields' )
552
- );
553
- $dispatcher->register_action(
554
- 'created_events_categories',
555
- array( 'view.admin.event-category', 'created_events_categories' )
556
- );
557
- $dispatcher->register_action(
558
- 'edited_events_categories',
559
- array( 'view.admin.event-category', 'edited_events_categories' )
560
- );
561
- $dispatcher->register_action(
562
- 'manage_edit-events_categories_columns',
563
- array( 'view.admin.event-category', 'manage_event_categories_columns' )
564
- );
565
- $dispatcher->register_action(
566
- 'manage_events_categories_custom_column',
567
- array( 'view.admin.event-category', 'manage_events_categories_custom_column' ),
568
- 10,
569
- 3
570
- );
571
 
572
- // register ICS cron action
573
- $dispatcher->register_action(
574
- Ai1ecIcsConnectorPlugin::HOOK_NAME,
575
- array( 'calendar-feed.ics', 'cron' )
576
- );
577
- $dispatcher->register_shortcode(
578
- 'ai1ec',
579
- array( 'view.calendar.shortcode', 'shortcode' )
580
- );
581
- $dispatcher->register_action(
582
- 'updated_option',
583
- array( 'model.settings', 'wp_options_observer' ),
584
- PHP_INT_MAX - 1,
585
- 3
586
- );
587
 
588
- $dispatcher->register_action(
589
- 'ai1ec_settings_updated',
590
- array( 'compatibility.check', 'ai1ec_settings_observer' ),
591
- PHP_INT_MAX - 1,
592
- 2
593
- );
594
- if ( $this->if_js_cache_enabled() ) {
595
- $dispatcher->register_action(
596
- 'ai1ec_settings_updated',
597
- array( 'controller.javascript', 'revalidate_cache' ),
598
- PHP_INT_MAX - 1
599
- );
600
- $dispatcher->register_action(
601
- 'ai1ec_settings_updated',
602
- array( 'controller.javascript-widget', 'revalidate_cache' ),
603
- PHP_INT_MAX - 1
604
- );
605
- }
606
 
607
- if ( is_admin() ) {
608
- // Import suggested event
609
- $dispatcher->register_action(
610
- 'wp_ajax_ai1ec_import_suggested_event',
611
- array( 'calendar-feed.ics', 'add_discover_events_feed_subscription' )
612
- );
613
- // Remove suggested event
614
- $dispatcher->register_action(
615
- 'wp_ajax_ai1ec_remove_suggested_event',
616
- array( 'calendar-feed.ics', 'delete_individual_event_subscription' )
617
- );
618
- // Search for events
619
- $dispatcher->register_action(
620
- 'wp_ajax_ai1ec_search_events',
621
- array( 'calendar-feed.suggested', 'search_events' )
622
- );
623
- // get the repeat box
624
- $dispatcher->register_action(
625
- 'wp_ajax_ai1ec_get_repeat_box',
626
- array( 'view.admin.get-repeat-box', 'get_repeat_box' )
627
- );
628
- // get the tax options box
629
- $dispatcher->register_action(
630
- 'wp_ajax_ai1ec_get_tax_box',
631
- array( 'view.admin.get-tax-box', 'get_tax_box' )
632
- );
633
- // add dismissable notice handler
634
- $dispatcher->register_action(
635
- 'wp_ajax_ai1ec_dismiss_notice',
636
- array( 'notification.admin', 'dismiss_notice' )
637
- );
638
- // save rrurle and convert it to text
639
- $dispatcher->register_action(
640
- 'wp_ajax_ai1ec_rrule_to_text',
641
- array( 'view.admin.get-repeat-box', 'convert_rrule_to_text' )
642
- );
643
- // display ticketing details in the events list
644
- $dispatcher->register_action(
645
- 'wp_ajax_ai1ec_show_ticket_details',
646
- array( 'view.admin.all-events', 'show_ticket_details' )
647
- );
648
- // display attendees list
649
- $dispatcher->register_action(
650
- 'wp_ajax_ai1ec_show_attendees',
651
- array( 'view.admin.all-events', 'show_attendees' )
652
- );
653
- // CSS and templates for ticketing options
654
- $dispatcher->register_action(
655
- 'restrict_manage_posts',
656
- array( 'view.admin.all-events', 'add_ticketing_styling' )
657
- );
658
- // taxonomy filter
659
- $dispatcher->register_action(
660
- 'restrict_manage_posts',
661
- array( 'view.admin.all-events', 'taxonomy_filter_restrict_manage_posts' )
662
- );
663
- $dispatcher->register_action(
664
- 'parse_query',
665
- array( 'view.admin.all-events', 'taxonomy_filter_post_type_request' )
666
- );
667
- $dispatcher->register_action(
668
- 'admin_menu',
669
- array( 'view.admin.calendar-feeds', 'add_page' )
670
- );
671
- $dispatcher->register_action(
672
- 'current_screen',
673
- array( 'view.admin.calendar-feeds', 'add_meta_box' )
674
- );
675
- $dispatcher->register_action(
676
- 'admin_menu',
677
- array( 'view.admin.add-ons', 'add_page' )
678
- );
679
- $dispatcher->register_action(
680
- 'admin_menu',
681
- array( 'view.admin.theme-switching', 'add_page' )
682
- );
683
- $dispatcher->register_action(
684
- 'admin_menu',
685
- array( 'view.admin.theme-options', 'add_page' )
686
- );
687
- $dispatcher->register_action(
688
- 'current_screen',
689
- array( 'view.admin.theme-options', 'add_meta_box' )
690
- );
691
- $dispatcher->register_action(
692
- 'admin_menu',
693
- array( 'view.admin.settings', 'add_page' )
694
- );
695
- $dispatcher->register_action(
696
- 'current_screen',
697
- array( 'view.admin.settings', 'add_meta_box' )
698
- );
699
- $dispatcher->register_action(
700
- 'init',
701
- array( 'controller.javascript', 'load_admin_js' )
702
- );
703
- $dispatcher->register_action(
704
- 'wp_ajax_ai1ec_add_ics',
705
- array( 'calendar-feed.ics', 'add_ics_feed' )
706
- );
707
- $dispatcher->register_action(
708
- 'wp_ajax_ai1ec_delete_ics',
709
- array( 'calendar-feed.ics', 'delete_feeds_and_events' )
710
- );
711
- $dispatcher->register_action(
712
- 'wp_ajax_ai1ec_update_ics',
713
- array( 'calendar-feed.ics', 'update_ics_feed' )
714
- );
715
- $dispatcher->register_action(
716
- 'wp_ajax_ai1ec_feeds_page_post',
717
- array( 'calendar-feed.ics', 'handle_feeds_page_post' )
718
- );
719
- $dispatcher->register_action(
720
- 'wp_ajax_ai1ec_send_feedback_message',
721
- array( 'model.review', 'send_feedback_message' )
722
- );
723
- $dispatcher->register_action(
724
- 'wp_ajax_ai1ec_save_feedback_review',
725
- array( 'model.review', 'save_feedback_review' )
726
- );
727
- $dispatcher->register_action(
728
- 'network_admin_notices',
729
- array( 'notification.admin', 'send' )
730
- );
731
- $dispatcher->register_action(
732
- 'admin_notices',
733
- array( 'notification.admin', 'send' )
734
- );
735
- $dispatcher->register_action(
736
- 'admin_footer-edit.php',
737
- array( 'clone.renderer-helper', 'duplicate_custom_bulk_admin_footer' )
738
- );
739
- $dispatcher->register_filter(
740
- 'post_row_actions',
741
- array( 'clone.renderer-helper', 'ai1ec_duplicate_post_make_duplicate_link_row' ),
742
- 100,
743
- 2
744
- );
745
- $dispatcher->register_action(
746
- 'add_meta_boxes',
747
- array( 'view.admin.add-new-event', 'event_meta_box_container' )
748
- );
749
- $dispatcher->register_action(
750
- 'edit_form_after_title',
751
- array( 'view.admin.add-new-event', 'event_inline_alert' )
752
- );
753
- $dispatcher->register_action(
754
- 'save_post',
755
- array( 'model.event.creating', 'save_post' ),
756
- 10,
757
- 3
758
- );
759
- $dispatcher->register_action(
760
- 'pre_post_update',
761
- array( 'model.event.creating', 'pre_post_update' ),
762
- 0,
763
- 2
764
- );
765
- $dispatcher->register_action(
766
- 'wp_insert_post_data',
767
- array( 'model.event.creating', 'wp_insert_post_data' )
768
- );
769
- $dispatcher->register_action(
770
- 'manage_ai1ec_event_posts_custom_column',
771
- array( 'view.admin.all-events', 'custom_columns' ),
772
- 10,
773
- 2
774
- );
775
- $dispatcher->register_filter(
776
- 'manage_ai1ec_event_posts_columns',
777
- array( 'view.admin.all-events', 'change_columns' )
778
- );
779
- $dispatcher->register_filter(
780
- 'manage_edit-ai1ec_event_sortable_columns',
781
- array( 'view.admin.all-events', 'sortable_columns' )
782
- );
783
- $dispatcher->register_filter(
784
- 'posts_orderby',
785
- array( 'view.admin.all-events', 'orderby' ),
786
- 10,
787
- 2
788
- );
789
- $dispatcher->register_filter(
790
- 'ai1ec_count_future_events',
791
- array( 'view.admin.all-events', 'count_future_events' ),
792
- 10,
793
- 1
794
- );
795
- $dispatcher->register_filter(
796
- 'post_updated_messages',
797
- array( 'view.event.post', 'post_updated_messages' )
798
- );
799
- add_action( 'admin_head', array( $this, 'admin_head' ) );
800
- $dispatcher->register_action(
801
- 'plugin_action_links_' . AI1EC_PLUGIN_BASENAME,
802
- array( 'view.admin.nav', 'plugin_action_links' )
803
- );
804
- $dispatcher->register_action(
805
- 'wp_ajax_ai1ec_rescan_cache',
806
- array( 'twig.cache', 'rescan' )
807
- );
808
- $dispatcher->register_action(
809
- 'admin_init',
810
- array( 'environment.check', 'run_checks' )
811
- );
812
- $dispatcher->register_action(
813
- 'activated_plugin',
814
- array( 'environment.check', 'check_addons_activation' )
815
- );
816
- $dispatcher->register_filter(
817
- 'upgrader_post_install',
818
- array( 'environment.check', 'check_bulk_addons_activation' )
819
- );
820
- if ( $this->if_js_cache_enabled() ) {
821
- $dispatcher->register_action(
822
- 'activated_plugin',
823
- array( 'controller.javascript', 'revalidate_cache' )
824
- );
825
- $dispatcher->register_action(
826
- 'deactivated_plugin',
827
- array( 'controller.javascript', 'revalidate_cache' )
828
- );
829
- $dispatcher->register_action(
830
- 'upgrader_post_install',
831
- array( 'controller.javascript', 'revalidate_cache' )
832
- );
833
- $dispatcher->register_action(
834
- 'activated_plugin',
835
- array( 'controller.javascript-widget', 'revalidate_cache' )
836
- );
837
- $dispatcher->register_action(
838
- 'deactivated_plugin',
839
- array( 'controller.javascript-widget', 'revalidate_cache' )
840
- );
841
- $dispatcher->register_action(
842
- 'upgrader_post_install',
843
- array( 'controller.javascript-widget', 'revalidate_cache' )
844
- );
 
 
 
 
 
 
845
 
846
- }
847
- // Widget Creator
848
- $dispatcher->register_action(
849
- 'admin_enqueue_scripts',
850
- array( 'css.admin', 'admin_enqueue_scripts' )
851
- );
852
- $dispatcher->register_action(
853
- 'current_screen',
854
- array( 'view.admin.widget-creator', 'add_meta_box' )
855
- );
856
- $dispatcher->register_action(
857
- 'admin_menu',
858
- array( 'view.admin.widget-creator', 'add_page' )
859
- );
860
- $dispatcher->register_filter(
861
- 'pre_set_site_transient_update_plugins',
862
- array( 'calendar.updates', 'check_updates' )
863
- );
864
- $dispatcher->register_filter(
865
- 'plugins_api',
866
- array( 'calendar.updates', 'plugins_api_filter' ),
867
- 10,
868
- 3
869
- );
870
- $dispatcher->register_action(
871
- 'admin_menu',
872
- array( 'view.admin.tickets', 'add_page' )
873
- );
874
-
875
- } else { // ! is_admin()
876
- $dispatcher->register_action(
877
- 'after_setup_theme',
878
- array( 'theme.loader', 'execute_theme_functions' )
879
- );
880
- $dispatcher->register_action(
881
- 'the_post',
882
- array( 'post.content', 'check_content' ),
883
- PHP_INT_MAX
884
- );
885
- $dispatcher->register_action(
886
- 'send_headers',
887
- array( 'request.redirect', 'handle_categories_and_tags' )
888
- );
889
- $dispatcher->register_action(
890
- 'wp_head',
891
- array( 'view.event.single', 'add_meta_tags' )
892
- );
893
- }
894
- }
895
- /**
896
- * Outputs menu icon between head tags
897
- */
898
- public function admin_head() {
899
- global $wp_version;
900
- $argv = array(
901
- 'before_font_icons' => version_compare( $wp_version, '3.8', '<' ),
902
- 'admin_theme_img_url' => AI1EC_ADMIN_THEME_IMG_URL,
903
- 'admin_theme_font_url' => AI1EC_ADMIN_THEME_FONT_URL,
904
- );
905
- $this->_registry->get( 'theme.loader' )
906
- ->get_file( 'timely-menu-icon.twig', $argv, true )
907
- ->render();
908
- }
909
 
910
- /**
911
- * _add_defaults method
912
- *
913
- * Add (merge) default options to given query variable.
914
- *
915
- * @param string settingsquery variable to ammend
916
- *
917
- * @return string|NULL Modified variable values or NULL on failure
918
- *
919
- * @global Ai1ec_Settings $ai1ec_settings Instance of settings object
920
- * to pull data from
921
- * @staticvar array $mapper Mapping of query names to
922
- * default in settings
923
- */
924
- protected function _add_defaults( $name ) {
925
- $settings = $this->_registry->get( 'model.settings' );
926
- static $mapper = array(
927
- 'cat' => 'categories',
928
- 'tag' => 'tags',
929
- );
930
- $rq_name = 'ai1ec_' . $name . '_ids';
931
- if (
932
- ! isset( $mapper[$name] ) ||
933
- ! array_key_exists( $rq_name, $this->_request )
934
- ) {
935
- return NULL;
936
- }
937
- $options = explode( ',', $this->_request[$rq_name] );
938
- $property = 'default_' . $mapper[$name];
939
- $options = array_merge(
940
- $options,
941
- $settings->get( $property )
942
- );
943
- $filtered = array();
944
- foreach ( $options as $item ) { // avoid array_filter + is_numeric
945
- $item = (int)$item;
946
- if ( $item > 0 ) {
947
- $filtered[] = $item;
948
- }
949
- }
950
- unset( $options );
951
- if ( empty( $filtered ) ) {
952
- return NULL;
953
- }
954
- return implode( ',', $filtered );
955
- }
956
 
957
- /**
958
- * Process_request function.
959
- *
960
- * Initialize/validate custom request array, based on contents of $_REQUEST,
961
- * to keep track of this component's request variables.
962
- *
963
- * @return void
964
- **/
965
- protected function _process_request() {
966
- $settings = $this->_registry->get( 'model.settings' );
967
- $this->_request = $this->_registry->get( 'http.request.parser' );
968
- $aco = $this->_registry->get( 'acl.aco' );
969
- $page_id = $settings->get( 'calendar_page_id' );
970
- if (
971
- ! $aco->is_admin() &&
972
- $page_id &&
973
- is_page( $page_id )
974
- ) {
975
- foreach ( array( 'cat', 'tag' ) as $name ) {
976
- $implosion = $this->_add_defaults( $name );
977
- if ( $implosion ) {
978
- $this->request['ai1ec_' . $name . '_ids'] = $implosion;
979
- $_REQUEST['ai1ec_' . $name . '_ids'] = $implosion;
980
- }
981
- }
982
- }
983
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
984
 
985
- /**
986
- * Initialize cron functions.
987
- *
988
- * @throws Ai1ec_Scheduling_Exception
989
- *
990
- * @return void
991
- */
992
- protected function _install_crons() {
993
- $scheduling = $this->_registry->get( 'scheduling.utility' );
994
- $hook_name = 'ai1ec_n_cron';
995
- $scheduling->delete( $hook_name );
996
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
997
 
998
- /**
999
- * Initialize the registry object.
1000
- *
1001
- * @param Ai1ec_Loader $ai1ec_loader Instance of Ai1EC classes loader
1002
- *
1003
- * @return void Method does not return
1004
- */
1005
- protected function _initialize_registry( $ai1ec_loader ) {
1006
- global $ai1ec_registry;
1007
- $this->_registry = new Ai1ec_Registry_Object( $ai1ec_loader );
1008
- Ai1ec_Time_Utility::set_registry( $this->_registry );
1009
- $ai1ec_registry = $this->_registry;
1010
- }
1011
 
1012
- /**
1013
- * Loads the CSS for the plugin
1014
- *
1015
- */
1016
- protected function _load_css_if_needed() {
1017
- // ==================================
1018
- // = Add the hook to render the css =
1019
- // ==================================
1020
- if ( isset( $_GET[Ai1ec_Css_Frontend::QUERY_STRING_PARAM] ) ) {
1021
- // we need to wait for the extension to be registered if the css
1022
- // needs to be compiled. Will find a better way when compiling css.
1023
- $css_controller = $this->_registry->get( 'css.frontend' );
1024
- add_action( 'plugins_loaded', array( $css_controller, 'render_css' ), 2 );
1025
- }
1026
- }
1027
 
1028
- /**
1029
- * Load the texdomain for the plugin.
1030
- *
1031
- * @wp_hook plugins_loaded
1032
- *
1033
- * @return void
1034
- */
1035
- public function load_textdomain() {
1036
- if ( false === $this->_domain_loaded ) {
1037
- load_plugin_textdomain(
1038
- AI1EC_PLUGIN_NAME, false, AI1EC_LANGUAGE_PATH
1039
- );
1040
- $this->_domain_loaded = true;
1041
- }
1042
- }
1043
 
1044
- /**
1045
- * Check if the schema is up to date.
1046
- *
1047
- * @throws Ai1ec_Database_Schema_Exception
1048
- * @throws Ai1ec_Database_Update_Exception
1049
- *
1050
- * @return void
1051
- */
1052
- protected function _initialize_schema() {
1053
- $option = $this->_registry->get( 'model.option' );
1054
- $schema_sql = $this->get_current_db_schema();
1055
- $version = sha1( $schema_sql );
1056
- // If existing DB version is not consistent with current plugin's version,
1057
- // or does not exist, then create/update table structure using dbDelta().
1058
- if ( $option->get( 'ai1ec_db_version' ) != $version ) {
1059
 
1060
- $errors = $this->_registry->get( 'database.applicator' )
1061
- ->check_db_consistency_for_date_migration() ;
1062
- if ( ! empty( $errors ) ) {
1063
- $message = Ai1ec_I18n::__(
1064
- 'Your database is found to be corrupt. Likely previous update has failed. Please restore All-in-One Event Calendar tables from a backup and retry.<br>Following errors were found:<br>%s'
1065
- );
1066
- $message = sprintf( $message, implode( $errors, '<br>' ) );
1067
- throw new Ai1ec_Database_Update_Exception( $message );
1068
- }
1069
- $this->_registry->get( 'database.applicator' )
1070
- ->remove_instance_duplicates();
 
 
 
 
1071
 
1072
- if (
1073
- apply_filters( 'ai1ec_perform_scheme_update', true ) &&
1074
- $this->_registry->get( 'database.helper' )->apply_delta(
1075
- $schema_sql
1076
- )
1077
- ) {
1078
- $option->set( 'ai1ec_db_version', $version );
1079
- } else {
1080
- throw new Ai1ec_Database_Update_Exception();
1081
- }
 
1082
 
1083
- // If the schema structure upgrade is complete move contents
1084
- $categories_key = 'ai1ec_category_meta_ported';
1085
- if ( ! $option->get( $categories_key ) ) {
1086
- $this->_migrate_categories_meta();
1087
- $option->set( $categories_key, true );
1088
- }
1089
- }
1090
- }
 
 
1091
 
1092
- /**
1093
- * Transform categories meta information.
1094
- *
1095
- * Use new `meta` table instead of legacy `colors` table.
1096
- *
1097
- * @return void Method does not return.
1098
- */
1099
- protected function _migrate_categories_meta() {
1100
- $db = $this->_registry->get( 'dbi.dbi' );
1101
- $table_name = $db->get_table_name( 'ai1ec_event_category_colors' );
1102
- $db_h = $this->_registry->get( 'database.helper' );
1103
- if ( $db_h->table_exists( $table_name ) ) { // if old table exists otherwise ignore it
1104
- // Migrate color information
1105
- $dest_table = $db->get_table_name( 'ai1ec_event_category_meta' );
1106
- $colors = $db->select(
1107
- $table_name,
1108
- array( 'term_id', 'term_color'),
1109
- ARRAY_A
1110
- );
1111
- if ( ! empty( $colors ) ) {
1112
- foreach ( $colors as $color ) {
1113
- $db->insert( $dest_table, $color );
1114
- }
1115
- }
1116
- // Drop the old table
1117
- $db->query( 'DROP TABLE IF EXISTS ' . $table_name );
1118
- }
1119
- }
1120
 
1121
- /**
1122
- * Procedures to take when upgrading plugin version
1123
- *
1124
- * @return void
1125
- */
1126
- protected function _plugin_upgrade_procedures() {
1127
- $option = $this->_registry->get( 'model.option' );
1128
- $version = AI1EC_VERSION;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1129
 
1130
- if ( $option->get( 'ai1ec_version' ) != $version ) {
1131
- try {
1132
- // Force regeneration of JS cache
1133
- $this->_registry->get( 'controller.javascript' )->revalidate_cache();
1134
- $this->_registry->get( 'controller.javascript-widget' )->revalidate_cache();
 
 
 
1135
 
1136
- // Run upgrade commands
1137
- $settings = $this->_registry->get( 'model.settings' );
1138
- $settings->perform_upgrade_actions();
1139
- } catch ( Exception $e ) {
1140
- }
1141
 
1142
- // Update plugin version
1143
- $option->set( 'ai1ec_version', $version );
1144
- }
1145
- }
 
1146
 
1147
- /**
1148
- * Get current database schema as a multi SQL statement.
1149
- *
1150
- * @return string Multiline SQL statement.
1151
- */
1152
- public function get_current_db_schema() {
1153
- $dbi = $this->_registry->get( 'dbi.dbi' );
1154
- // =======================
1155
- // = Create table events =
1156
- // =======================
1157
- $table_name = $dbi->get_table_name( 'ai1ec_events' );
1158
- $sql = "CREATE TABLE $table_name (
1159
- post_id bigint(20) NOT NULL,
1160
- start int(10) UNSIGNED NOT NULL,
1161
- end int(10) UNSIGNED,
1162
- timezone_name varchar(50),
1163
- allday tinyint(1) NOT NULL,
1164
- instant_event tinyint(1) NOT NULL DEFAULT 0,
1165
- recurrence_rules longtext,
1166
- exception_rules longtext,
1167
- recurrence_dates longtext,
1168
- exception_dates longtext,
1169
- venue varchar(255),
1170
- country varchar(255),
1171
- address varchar(255),
1172
- city varchar(255),
1173
- province varchar(255),
1174
- postal_code varchar(32),
1175
- show_map tinyint(1),
1176
- contact_name varchar(255),
1177
- contact_phone varchar(32),
1178
- contact_email varchar(128),
1179
- contact_url varchar(255),
1180
- cost varchar(255),
1181
- ticket_url varchar(255),
1182
- ical_feed_url varchar(255),
1183
- ical_source_url varchar(255),
1184
- ical_organizer varchar(255),
1185
- ical_contact varchar(255),
1186
- ical_uid varchar(255),
1187
- show_coordinates tinyint(1),
1188
- latitude decimal(20,15),
1189
- longitude decimal(20,15),
1190
- force_regenerate tinyint(1) NOT NULL DEFAULT 0,
1191
- PRIMARY KEY (post_id),
1192
- KEY feed_source (ical_feed_url)
1193
- ) CHARACTER SET utf8;";
1194
 
1195
- // ==========================
1196
- // = Create table instances =
1197
- // ==========================
1198
- $table_name = $dbi->get_table_name( 'ai1ec_event_instances' );
1199
- $sql .= "CREATE TABLE $table_name (
1200
- id bigint(20) NOT NULL AUTO_INCREMENT,
1201
- post_id bigint(20) NOT NULL,
1202
- start int(10) UNSIGNED NOT NULL,
1203
- end int(10) UNSIGNED NOT NULL,
1204
- PRIMARY KEY (id),
1205
- UNIQUE KEY evt_instance (post_id,start)
1206
- ) CHARACTER SET utf8;";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1207
 
1208
- // ================================
1209
- // = Create table category colors =
1210
- // ================================
1211
- $table_name = $dbi->get_table_name( 'ai1ec_event_category_meta' );
1212
- $sql .= "CREATE TABLE $table_name (
1213
- term_id bigint(20) NOT NULL,
1214
- term_color varchar(255) NOT NULL,
1215
- term_image varchar(254) NULL DEFAULT NULL,
1216
- PRIMARY KEY (term_id)
1217
- ) CHARACTER SET utf8;";
 
 
1218
 
1219
- return $sql;
1220
- }
 
 
 
 
 
 
 
 
 
 
 
1221
  }
11
  */
12
  class Ai1ec_Front_Controller {
13
 
14
+ /**
15
+ * @var Ai1ec_Registry_Object The Object registry.
16
+ */
17
+ protected $_registry;
18
 
19
+ /**
20
+ * @var bool Whether the domain has alredy been loaded or not.
21
+ */
22
+ protected $_domain_loaded = false;
23
 
24
+ /**
25
+ * @var string The pagebase used by Ai1ec_Href_Helper.
26
+ */
27
+ protected $_pagebase_for_href;
28
 
29
+ /**
30
+ * @var Ai1ec_Request_Parser Instance of the request pa
31
+ */
32
+ protected $_request;
33
 
34
+ /**
35
+ * @var array
36
+ */
37
+ protected $_default_theme;
38
 
39
+ /**
40
+ * Initializes the default theme property.
41
+ */
42
+ public function __construct() {
43
+ // Initialize default theme.
44
+ $this->_default_theme = array(
45
+ 'theme_dir' => AI1EC_DEFAULT_THEME_PATH,
46
+ 'theme_root' => AI1EC_DEFAULT_THEME_ROOT,
47
+ 'theme_url' => AI1EC_THEMES_URL . '/' . AI1EC_DEFAULT_THEME_NAME,
48
+ 'stylesheet' => AI1EC_DEFAULT_THEME_NAME,
49
+ 'legacy' => false,
50
+ );
51
+ }
52
 
53
+ /**
54
+ * Initialize the controller.
55
+ *
56
+ * @param Ai1ec_Loader $ai1ec_loader Instance of Ai1EC classes loader
57
+ *
58
+ * @return void
59
+ */
60
+ public function initialize( $ai1ec_loader ) {
61
+ ai1ec_start();
62
+ $this->_init( $ai1ec_loader );
63
+ $this->_initialize_dispatcher();
64
+ $lessphp = $this->_registry->get( 'less.lessphp' );
65
+ $lessphp->initialize_less_variables_if_not_set();
66
+ $this->_registry->get( 'controller.shutdown' )
67
+ ->register( 'ai1ec_stop' );
68
+ add_action( 'plugins_loaded', array( $this, 'register_extensions' ), 1 );
69
+ add_action( 'after_setup_theme', array( $this, 'register_themes' ), 1 );
70
+ add_action( 'init', array( $lessphp, 'invalidate_css_cache_if_requested' ) );
71
+ }
72
 
73
+ /**
74
+ * Let other objects access default theme
75
+ *
76
+ * @return array
77
+ */
78
+ public function get_default_theme() {
79
+ return $this->_default_theme;
80
+ }
81
 
82
+ /**
83
+ * Remove unwanted menus
84
+ */
85
+ public function admin_menu() {
86
+ remove_submenu_page(
87
+ 'edit.php?post_type=ai1ec_event',
88
+ 'edit-tags.php?taxonomy=events_tags&amp;post_type=ai1ec_event'
89
+ );
90
+ }
91
 
92
+ /**
93
+ * Notify extensions and pass them instance of objects registry.
94
+ *
95
+ * @return void
96
+ */
97
+ public function register_extensions() {
98
+ do_action( 'ai1ec_loaded', $this->_registry );
99
+ }
100
 
101
+ /**
102
+ * Notify themes and pass them instance of objects registry.
103
+ *
104
+ * @return void
105
+ */
106
+ public function register_themes() {
107
+ do_action( 'ai1ec_after_themes_setup', $this->_registry );
108
+ }
109
 
110
+ /**
111
+ * Returns the registry object
112
+ *
113
+ * @param mixed $discard not used. Always return the registry.
114
+ *
115
+ * @return Ai1ec_Registry_Object
116
+ */
117
+ public function return_registry( $discard ) {
118
+ return $this->_registry;
119
+ }
120
 
121
+ /**
122
+ * If WIDGET_PARAMETER is set.
123
+ *
124
+ * @return boolean
125
+ */
126
+ protected function is_widget() {
127
+ return isset(
128
+ $_GET[Ai1ec_Controller_Javascript_Widget::WIDGET_PARAMETER]
129
+ );
130
+ }
131
 
132
+ /**
133
+ * If Advanced JS cache enabled.
134
+ *
135
+ * @return boolean
136
+ */
137
+ protected function if_js_cache_enabled() {
138
+ $settings = $this->_registry->get( 'model.settings' );
139
+ return $settings->get( 'cache_dynamic_js' );
140
+ }
141
 
142
+ /**
143
+ * If LEGACY_WIDGET_PARAMETER is set.
144
+ *
145
+ * @return boolean
146
+ */
147
+ protected function is_legacy_widget() {
148
+ return isset(
149
+ $_GET[Ai1ec_Controller_Javascript_Widget::LEGACY_WIDGET_PARAMETER]
150
+ );
151
+ }
152
 
153
+ /**
154
+ * Execute commands if our plugin must handle the request.
155
+ *
156
+ * @wp_hook init
157
+ *
158
+ * @return void
159
+ */
160
+ public function route_request() {
161
+ $this->_process_request();
162
+ // get the resolver
163
+ $resolver = $this->_registry->get(
164
+ 'command.resolver',
165
+ $this->_request
166
+ );
167
+ // get the command
168
+ $commands = $resolver->get_commands();
169
+ // if we have a command
170
+ if ( ! empty( $commands ) ) {
171
+ foreach( $commands as $command ) {
172
+ $result = $command->execute();
173
+ if ( $command->stop_execution() ) {
174
+ return $result;
175
+ }
176
+ }
177
+ }
178
+ }
179
 
180
+ /**
181
+ * Initializes the URL router used by our plugin.
182
+ *
183
+ * @wp_hook init
184
+ *
185
+ * @return void
186
+ */
187
+ public function initialize_router() {
188
+ /* @var $cal_state Ai1ec_Calendar_State */
189
+ $cal_state = $this->_registry->get( 'calendar.state' );
190
+ $cal_state->set_routing_initialization( true );
191
+ $settings = $this->_registry->get( 'model.settings' );
192
+ $cal_page = $settings->get( 'calendar_page_id' );
193
 
194
+ if (
195
+ ! $cal_page ||
196
+ $cal_page < 1
197
+ ) { // Routing may not be affected in any way if no calendar page exists.
198
+ $cal_state->set_routing_initialization( false );
199
+ return null;
200
+ }
201
+ $router = $this->_registry->get( 'routing.router' );
202
+ $localization_helper = $this->_registry->get( 'p28n.wpml' );
203
+ $page_base = '';
204
+ $clang = '';
205
 
206
+ if ( $localization_helper->is_wpml_active() ) {
207
+ $trans = $localization_helper
208
+ ->get_wpml_translations_of_page(
209
+ $cal_page,
210
+ true
211
+ );
212
+ $clang = $localization_helper->get_language();
213
+ if ( isset( $trans[$clang] ) ) {
214
+ $cal_page = $trans[$clang];
215
+ }
216
+ }
217
+ $template_link_helper = $this->_registry->get( 'template.link.helper' );
218
 
219
+ if ( ! get_post( $cal_page ) ) {
220
+ $cal_state->set_routing_initialization( false );
221
+ return null;
222
+ }
223
 
224
+ $page_base = $template_link_helper->get_page_link(
225
+ $cal_page
226
+ );
227
 
228
+ $page_base = Ai1ec_Wp_Uri_Helper::get_pagebase( $page_base );
229
+ $page_link = 'index.php?page_id=' .
230
+ $cal_page;
231
+ $pagebase_for_href = Ai1ec_Wp_Uri_Helper::get_pagebase_for_links(
232
+ get_page_link( $cal_page ),
233
+ $clang
234
+ );
235
 
236
+ // save the pagebase to set up the factory later
237
+ $application = $this->_registry->get( 'bootstrap.registry.application' );
238
+ $application->set( 'calendar_base_page', $pagebase_for_href );
239
+ $option = $this->_registry->get( 'model.option' );
240
 
241
+ // If the calendar is set as the front page, disable permalinks.
242
+ // They would not be legal under a Windows server. See:
243
+ // https://issues.apache.org/bugzilla/show_bug.cgi?id=41441
244
 
245
+ if (
246
+ $option->get( 'permalink_structure' ) &&
247
+ ( int ) get_option( 'page_on_front' ) !==
248
+ ( int ) $cal_page
249
+ ) {
250
+ $application->set( 'permalinks_enabled', true );
251
+ }
252
 
253
+ $router->asset_base( $page_base )
254
+ ->register_rewrite( $page_link );
255
+ $cal_state->set_routing_initialization( false );
256
+ }
257
 
258
+ /**
259
+ * Initialize the system.
260
+ *
261
+ * Perform all the inizialization needed for the system.
262
+ * Throws some uncatched exception for critical failures.
263
+ * Plugin will be disabled by the exception handler on those failures.
264
+ *
265
+ * @param Ai1ec_Loader $ai1ec_loader Instance of Ai1EC classes loader
266
+ *
267
+ * @throws Ai1ec_Constants_Not_Set_Exception
268
+ * @throws Ai1ec_Database_Update_Exception
269
+ * @throws Ai1ec_Database_Schema_Exception
270
+ *
271
+ * @return void Method does not return
272
+ */
273
+ protected function _init( $ai1ec_loader ) {
274
+ $exception = null;
275
+ // Load the textdomain
276
+ add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );
277
+ try {
278
+ // Initialize the registry object
279
+ $this->_initialize_registry( $ai1ec_loader );
280
+ $this->_registry->get( 'event.dispatcher' )->register_filter(
281
+ 'ai1ec_perform_scheme_update',
282
+ array( 'database.datetime-migration', 'filter_scheme_update' )
283
+ );
284
+ // Procedures to take when upgrading plugin version
285
+ $this->_plugin_upgrade_procedures();
286
+ // Load the css if needed
287
+ $this->_load_css_if_needed();
288
+ // Initialize the crons
289
+ $this->_install_crons();
290
+ // Register the activation hook
291
+ $this->_initialize_schema();
292
+ // set the default theme if not set
293
+ $this->_add_default_theme_if_not_set();
294
+ } catch ( Ai1ec_Constants_Not_Set_Exception $e ) {
295
+ // This is blocking, throw it and disable the plugin
296
+ $exception = $e;
297
+ } catch ( Ai1ec_Database_Update_Exception $e ) {
298
+ // Blocking throw it so that the plugin is disabled
299
+ $exception = $e;
300
+ } catch ( Ai1ec_Database_Schema_Exception $e ) {
301
+ // Blocking throw it so that the plugin is disabled
302
+ $exception = $e;
303
+ } catch ( Ai1ec_Scheduling_Exception $e ) {
304
+ // not blocking
305
+ }
306
 
307
+ if ( null !== $exception ) {
308
+ throw $exception;
309
+ }
310
+ }
311
 
312
+ /**
313
+ * Set the default theme if no theme is set, or populate theme info array if
314
+ * insufficient information is currently being stored.
315
+ *
316
+ * @uses apply_filters() Calls 'ai1ec_pre_save_current_theme' hook to allow
317
+ * overwriting of theme information before being stored.
318
+ */
319
+ protected function _add_default_theme_if_not_set() {
320
+ $option = $this->_registry->get( 'model.option' );
321
+ $theme = $option->get( 'ai1ec_current_theme', array() );
322
+ $update = false;
323
+ // Theme setting is undefined; default to Vortex.
324
+ if ( empty( $theme ) ) {
325
+ $theme = $this->_default_theme;
326
+ $update = true;
327
+ }
328
+ // Legacy settings; in 1.x the active theme was stored as a bare string,
329
+ // and they were located in a different folder than they are now.
330
+ else if ( is_string( $theme ) ) {
331
+ $theme_name = strtolower( $theme );
332
+ $core_themes = explode( ',', AI1EC_CORE_THEMES );
333
+ $legacy = ! in_array( $theme_name, $core_themes );
334
 
335
+ if ( $legacy ) {
336
+ $root = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . AI1EC_THEME_FOLDER;
337
+ $url = WP_CONTENT_URL . '/' . AI1EC_THEME_FOLDER . '/' . $theme_name;
338
+ } else {
339
+ $root = AI1EC_DEFAULT_THEME_ROOT;
340
+ $url = AI1EC_THEMES_URL . '/' . $theme_name;
341
+ }
342
+ // if it's from 1.x, move folders to avoid confusion
343
+ if ( apply_filters( 'ai1ec_move_themes_to_backup', true ) ) {
344
+ $this->_registry->get( 'theme.search' )
345
+ ->move_themes_to_backup( $core_themes );
346
+ }
347
+ // Ensure existence of theme directory.
348
+ if ( ! is_dir( $root . DIRECTORY_SEPARATOR . $theme_name ) ) {
349
+ // It's missing; something is wrong with this theme. Reset theme to
350
+ // Vortex and warn the user accordingly.
351
+ $option->set( 'ai1ec_current_theme', $this->_default_theme );
352
+ $notification = $this->_registry->get( 'notification.admin' );
353
+ $notification->store(
354
+ sprintf(
355
+ Ai1ec_I18n::__(
356
+ 'Your active calendar theme could not be properly initialized. The default theme has been activated instead. Please visit %s and try reactivating your theme manually.'
357
+ ),
358
+ '<a href="' . ai1ec_admin_url( AI1EC_THEME_SELECTION_BASE_URL ) . '">' .
359
+ Ai1ec_I18n::__( 'Calendar Themes' ) . '</a>'
360
+ ),
361
+ 'error',
362
+ 1
363
+ );
364
+ }
365
 
366
+ $theme = array(
367
+ 'theme_dir' => $root . DIRECTORY_SEPARATOR . $theme_name,
368
+ 'theme_root' => $root,
369
+ 'theme_url' => $url,
370
+ 'stylesheet' => $theme_name,
371
+ 'legacy' => $legacy,
372
+ );
373
+ $update = true;
374
+ }
375
+ // Ensure 'theme_url' is defined, as this property was added after the first
376
+ // public beta release.
377
+ else if ( ! isset( $theme['theme_url'] ) ) {
378
+ if ( $theme['legacy'] ) {
379
+ $theme['theme_url'] = WP_CONTENT_URL . '/' . AI1EC_THEME_FOLDER . '/' .
380
+ $theme['stylesheet'];
381
+ } else {
382
+ $theme['theme_url'] = AI1EC_THEMES_URL . '/' . $theme['stylesheet'];
383
+ }
384
+ $update = true;
385
+ }
386
 
387
+ if ( $update ) {
388
+ $theme = apply_filters( 'ai1ec_pre_save_current_theme', $theme );
389
+ $option->set( 'ai1ec_current_theme', $theme );
390
+ }
391
+ }
392
 
393
+ /**
394
+ * Adds actions handled by the front controller.
395
+ */
396
+ protected function _add_front_controller_actions() {
397
+ // Initialize router. I use add_action as the dispatcher would just add
398
+ // overhead.
399
+ add_action(
400
+ 'init',
401
+ array( $this, 'initialize_router' ),
402
+ PHP_INT_MAX - 1
403
+ );
404
+ add_action(
405
+ 'widgets_init',
406
+ array( 'Ai1ec_View_Admin_Widget', 'register_widget' )
407
+ );
408
 
409
+ if (
410
+ $this->is_widget() ||
411
+ $this->is_legacy_widget()
412
+ ) {
413
+ $this->_registry->get( 'event.dispatcher' )->register_action(
414
+ 'init',
415
+ array( 'controller.javascript-widget', 'render_js_widget' ),
416
+ PHP_INT_MAX
417
+ );
418
+ }
419
 
420
+ // Route the request.
421
+ $action = 'template_redirect';
422
+ if ( is_admin() ) {
423
+ $action = 'init';
424
+ add_action( 'admin_menu', array( $this, 'admin_menu' ) );
425
+ }
426
+ add_action( $action, array( $this, 'route_request' ) );
427
+ add_filter( 'ai1ec_registry', array( $this, 'return_registry' ) );
428
+ }
429
 
430
+ /**
431
+ * Initialize the dispatcher.
432
+ *
433
+ * Complete this when writing the dispatcher.
434
+ *
435
+ * @return void
436
+ */
437
+ protected function _initialize_dispatcher() {
438
+ $dispatcher = $this->_registry->get( 'event.dispatcher' );
439
+ $dispatcher->register_action(
440
+ 'init',
441
+ array( 'post.custom-type', 'register' )
442
+ );
443
+ $this->_add_front_controller_actions();
444
+ if ( isset( $_GET[Ai1ec_Javascript_Controller::LOAD_JS_PARAMETER] ) ) {
445
+ $dispatcher->register_action(
446
+ 'wp_loaded',
447
+ array( 'controller.javascript', 'render_js' )
448
+ );
449
+ }
450
+ $dispatcher->register_action(
451
+ 'before_delete_post',
452
+ array( 'model.event.trashing', 'before_delete_post' ),
453
+ 0,
454
+ 3
455
+ );
456
+ $dispatcher->register_action(
457
+ 'delete_post',
458
+ array( 'model.event.trashing', 'delete' )
459
+ );
460
+ $dispatcher->register_action(
461
+ 'wp_trash_post',
462
+ array( 'model.event.trashing', 'trash_post' )
463
+ );
464
+ $dispatcher->register_action(
465
+ 'trashed_post',
466
+ array( 'model.event.trashing', 'trashed_post' )
467
+ );
468
+ $dispatcher->register_action(
469
+ 'untrash_post',
470
+ array( 'model.event.trashing', 'untrash_post' )
471
+ );
472
+ $dispatcher->register_action(
473
+ 'untrashed_post',
474
+ array( 'model.event.trashing', 'untrashed_post' )
475
+ );
476
+ $dispatcher->register_action(
477
+ 'pre_http_request',
478
+ array( 'http.request', 'pre_http_request' ),
479
+ 10,
480
+ 3
481
+ );
482
+ $dispatcher->register_action(
483
+ 'http_request_args',
484
+ array( 'http.request', 'init_certificate' ),
485
+ 10,
486
+ 2
487
+ );
488
+ // add the filter to let the organize page work
489
+ $dispatcher->register_action(
490
+ 'admin_init',
491
+ array( 'view.admin.organize', 'add_taxonomy_actions' ),
492
+ 10000
493
+ );
494
+ $dispatcher->register_action(
495
+ 'plugins_loaded',
496
+ array( 'theme.loader', 'clean_cache_on_upgrade' ),
497
+ PHP_INT_MAX
498
+ );
499
+ $dispatcher->register_filter(
500
+ 'get_the_excerpt',
501
+ array( 'view.event.content', 'event_excerpt' ),
502
+ 11
503
+ );
504
+ remove_filter( 'the_excerpt', 'wpautop', 10 );
505
+ $dispatcher->register_filter(
506
+ 'the_excerpt',
507
+ array( 'view.event.content', 'event_excerpt_noautop' ),
508
+ 11
509
+ );
510
+ $dispatcher->register_filter(
511
+ 'robots_txt',
512
+ array( 'robots.helper', 'rules' ),
513
+ 10,
514
+ 2
515
+ );
516
+ $dispatcher->register_filter(
517
+ 'ai1ec_dbi_debug',
518
+ array( 'http.request', 'debug_filter' )
519
+ );
520
+ $dispatcher->register_filter(
521
+ 'ai1ec_dbi_debug',
522
+ array( 'compatibility.cli', 'disable_db_debug' )
523
+ );
524
+ // editing a child instance
525
+ if ( isset( $_SERVER['SCRIPT_NAME'] ) && basename( $_SERVER['SCRIPT_NAME'] ) === 'post.php' ) {
526
+ $dispatcher->register_action(
527
+ 'admin_action_editpost',
528
+ array( 'model.event.parent', 'admin_init_post' )
529
+ );
530
+ $dispatcher->register_filter(
531
+ 'user_has_cap',
532
+ array( 'content.filter', 'display_trash_link' ),
533
+ 10,
534
+ 2
535
+ );
536
+ }
537
+ // post row action for parent/child
538
+ $dispatcher->register_action(
539
+ 'post_row_actions',
540
+ array( 'model.event.parent', 'post_row_actions' ),
541
+ 100,
542
+ 2
543
+ );
544
+ // Category colors
545
+ $dispatcher->register_action(
546
+ 'events_categories_add_form_fields',
547
+ array( 'view.admin.event-category', 'events_categories_add_form_fields' )
548
+ );
549
+ $dispatcher->register_action(
550
+ 'events_categories_edit_form_fields',
551
+ array( 'view.admin.event-category', 'events_categories_edit_form_fields' )
552
+ );
553
+ $dispatcher->register_action(
554
+ 'created_events_categories',
555
+ array( 'view.admin.event-category', 'created_events_categories' )
556
+ );
557
+ $dispatcher->register_action(
558
+ 'edited_events_categories',
559
+ array( 'view.admin.event-category', 'edited_events_categories' )
560
+ );
561
+ $dispatcher->register_action(
562
+ 'manage_edit-events_categories_columns',
563
+ array( 'view.admin.event-category', 'manage_event_categories_columns' )
564
+ );
565
+ $dispatcher->register_action(
566
+ 'manage_events_categories_custom_column',
567
+ array( 'view.admin.event-category', 'manage_events_categories_custom_column' ),
568
+ 10,
569
+ 3
570
+ );
571
 
572
+ // register ICS cron action
573
+ $dispatcher->register_action(
574
+ Ai1ecIcsConnectorPlugin::HOOK_NAME,
575
+ array( 'calendar-feed.ics', 'cron' )
576
+ );
577
+ $dispatcher->register_shortcode(
578
+ 'ai1ec',
579
+ array( 'view.calendar.shortcode', 'shortcode' )
580
+ );
581
+ $dispatcher->register_action(
582
+ 'updated_option',
583
+ array( 'model.settings', 'wp_options_observer' ),
584
+ PHP_INT_MAX - 1,
585
+ 3
586
+ );
587
 
588
+ $dispatcher->register_action(
589
+ 'ai1ec_settings_updated',
590
+ array( 'compatibility.check', 'ai1ec_settings_observer' ),
591
+ PHP_INT_MAX - 1,
592
+ 2
593
+ );
594
+ if ( $this->if_js_cache_enabled() ) {
595
+ $dispatcher->register_action(
596
+ 'ai1ec_settings_updated',
597
+ array( 'controller.javascript', 'revalidate_cache' ),
598
+ PHP_INT_MAX - 1
599
+ );
600
+ $dispatcher->register_action(
601
+ 'ai1ec_settings_updated',
602
+ array( 'controller.javascript-widget', 'revalidate_cache' ),
603
+ PHP_INT_MAX - 1
604
+ );
605
+ }
606
 
607
+ if ( is_admin() ) {
608
+ // Import suggested event
609
+ $dispatcher->register_action(
610
+ 'wp_ajax_ai1ec_import_suggested_event',
611
+ array( 'calendar-feed.ics', 'add_discover_events_feed_subscription' )
612
+ );
613
+ // Remove suggested event
614
+ $dispatcher->register_action(
615
+ 'wp_ajax_ai1ec_remove_suggested_event',
616
+ array( 'calendar-feed.ics', 'delete_individual_event_subscription' )
617
+ );
618
+ // Search for events
619
+ $dispatcher->register_action(
620
+ 'wp_ajax_ai1ec_search_events',
621
+ array( 'calendar-feed.suggested', 'search_events' )
622
+ );
623
+ // get the repeat box
624
+ $dispatcher->register_action(
625
+ 'wp_ajax_ai1ec_get_repeat_box',
626
+ array( 'view.admin.get-repeat-box', 'get_repeat_box' )
627
+ );
628
+ // get the tax options box
629
+ $dispatcher->register_action(
630
+ 'wp_ajax_ai1ec_get_tax_box',
631
+ array( 'view.admin.get-tax-box', 'get_tax_box' )
632
+ );
633
+ // add dismissable notice handler
634
+ $dispatcher->register_action(
635
+ 'wp_ajax_ai1ec_dismiss_notice',
636
+ array( 'notification.admin', 'dismiss_notice' )
637
+ );
638
+ // save rrurle and convert it to text
639
+ $dispatcher->register_action(
640
+ 'wp_ajax_ai1ec_rrule_to_text',
641
+ array( 'view.admin.get-repeat-box', 'convert_rrule_to_text' )
642
+ );
643
+ // display ticketing details in the events list
644
+ $dispatcher->register_action(
645
+ 'wp_ajax_ai1ec_show_ticket_details',
646
+ array( 'view.admin.all-events', 'show_ticket_details' )
647
+ );
648
+ // display attendees list
649
+ $dispatcher->register_action(
650
+ 'wp_ajax_ai1ec_show_attendees',
651
+ array( 'view.admin.all-events', 'show_attendees' )
652
+ );
653
+ // CSS and templates for ticketing options
654
+ $dispatcher->register_action(
655
+ 'restrict_manage_posts',
656
+ array( 'view.admin.all-events', 'add_ticketing_styling' )
657
+ );
658
+ // taxonomy filter
659
+ $dispatcher->register_action(
660
+ 'restrict_manage_posts',
661
+ array( 'view.admin.all-events', 'taxonomy_filter_restrict_manage_posts' )
662
+ );
663
+ $dispatcher->register_action(
664
+ 'parse_query',
665
+ array( 'view.admin.all-events', 'taxonomy_filter_post_type_request' )
666
+ );
667
+ $dispatcher->register_action(
668
+ 'admin_menu',
669
+ array( 'view.admin.calendar-feeds', 'add_page' )
670
+ );
671
+ $dispatcher->register_action(
672
+ 'current_screen',
673
+ array( 'view.admin.calendar-feeds', 'add_meta_box' )
674
+ );
675
+ $dispatcher->register_action(
676
+ 'admin_menu',
677
+ array( 'view.admin.add-ons', 'add_page' )
678
+ );
679
+ $dispatcher->register_action(
680
+ 'admin_menu',
681
+ array( 'view.admin.theme-switching', 'add_page' )
682
+ );
683
+ $dispatcher->register_action(
684
+ 'admin_menu',
685
+ array( 'view.admin.theme-options', 'add_page' )
686
+ );
687
+ $dispatcher->register_action(
688
+ 'current_screen',
689
+ array( 'view.admin.theme-options', 'add_meta_box' )
690
+ );
691
+ $dispatcher->register_action(
692
+ 'admin_menu',
693
+ array( 'view.admin.settings', 'add_page' )
694
+ );
695
+ $dispatcher->register_action(
696
+ 'current_screen',
697
+ array( 'view.admin.settings', 'add_meta_box' )
698
+ );
699
+ $dispatcher->register_action(
700
+ 'init',
701
+ array( 'controller.javascript', 'load_admin_js' )
702
+ );
703
+ $dispatcher->register_action(
704
+ 'admin_menu',
705
+ array( 'view.admin.samples', 'add_page' ),
706
+ 100,
707
+ 1
708
+ );
709
+ $dispatcher->register_action(
710
+ 'wp_ajax_ai1ec_add_ics',
711
+ array( 'calendar-feed.ics', 'add_ics_feed' )
712
+ );
713
+ $dispatcher->register_action(
714
+ 'wp_ajax_ai1ec_delete_ics',
715
+ array( 'calendar-feed.ics', 'delete_feeds_and_events' )
716
+ );
717
+ $dispatcher->register_action(
718
+ 'wp_ajax_ai1ec_update_ics',
719
+ array( 'calendar-feed.ics', 'update_ics_feed' )
720
+ );
721
+ $dispatcher->register_action(
722
+ 'wp_ajax_ai1ec_feeds_page_post',
723
+ array( 'calendar-feed.ics', 'handle_feeds_page_post' )
724
+ );
725
+ $dispatcher->register_action(
726
+ 'wp_ajax_ai1ec_send_feedback_message',
727
+ array( 'model.review', 'send_feedback_message' )
728
+ );
729
+ $dispatcher->register_action(
730
+ 'wp_ajax_ai1ec_save_feedback_review',
731
+ array( 'model.review', 'save_feedback_review' )
732
+ );
733
+ $dispatcher->register_action(
734
+ 'network_admin_notices',
735
+ array( 'notification.admin', 'send' )
736
+ );
737
+ $dispatcher->register_action(
738
+ 'admin_notices',
739
+ array( 'notification.admin', 'send' )
740
+ );
741
+ $dispatcher->register_action(
742
+ 'admin_footer-edit.php',
743
+ array( 'clone.renderer-helper', 'duplicate_custom_bulk_admin_footer' )
744
+ );
745
+ $dispatcher->register_filter(
746
+ 'post_row_actions',
747
+ array( 'clone.renderer-helper', 'ai1ec_duplicate_post_make_duplicate_link_row' ),
748
+ 100,
749
+ 2
750
+ );
751
+ $dispatcher->register_action(
752
+ 'add_meta_boxes',
753
+ array( 'view.admin.add-new-event', 'event_meta_box_container' )
754
+ );
755
+ $dispatcher->register_action(
756
+ 'edit_form_after_title',
757
+ array( 'view.admin.add-new-event', 'event_inline_alert' )
758
+ );
759
+ $dispatcher->register_action(
760
+ 'save_post',
761
+ array( 'model.event.creating', 'save_post' ),
762
+ 10,
763
+ 3
764
+ );
765
+ $dispatcher->register_action(
766
+ 'pre_post_update',
767
+ array( 'model.event.creating', 'pre_post_update' ),
768
+ 0,
769
+ 2
770
+ );
771
+ $dispatcher->register_action(
772
+ 'wp_insert_post_data',
773
+ array( 'model.event.creating', 'wp_insert_post_data' )
774
+ );
775
+ $dispatcher->register_action(
776
+ 'manage_ai1ec_event_posts_custom_column',
777
+ array( 'view.admin.all-events', 'custom_columns' ),
778
+ 10,
779
+ 2
780
+ );
781
+ $dispatcher->register_filter(
782
+ 'manage_ai1ec_event_posts_columns',
783
+ array( 'view.admin.all-events', 'change_columns' )
784
+ );
785
+ $dispatcher->register_filter(
786
+ 'manage_edit-ai1ec_event_sortable_columns',
787
+ array( 'view.admin.all-events', 'sortable_columns' )
788
+ );
789
+ $dispatcher->register_filter(
790
+ 'posts_orderby',
791
+ array( 'view.admin.all-events', 'orderby' ),
792
+ 10,
793
+ 2
794
+ );
795
+ $dispatcher->register_filter(
796
+ 'ai1ec_count_future_events',
797
+ array( 'view.admin.all-events', 'count_future_events' ),
798
+ 10,
799
+ 1
800
+ );
801
+ $dispatcher->register_filter(
802
+ 'post_updated_messages',
803
+ array( 'view.event.post', 'post_updated_messages' )
804
+ );
805
+ add_action( 'admin_head', array( $this, 'admin_head' ) );
806
+ $dispatcher->register_action(
807
+ 'plugin_action_links_' . AI1EC_PLUGIN_BASENAME,
808
+ array( 'view.admin.nav', 'plugin_action_links' )
809
+ );
810
+ $dispatcher->register_action(
811
+ 'wp_ajax_ai1ec_rescan_cache',
812
+ array( 'twig.cache', 'rescan' )
813
+ );
814
+ $dispatcher->register_action(
815
+ 'admin_init',
816
+ array( 'environment.check', 'run_checks' )
817
+ );
818
+ $dispatcher->register_action(
819
+ 'activated_plugin',
820
+ array( 'environment.check', 'check_addons_activation' )
821
+ );
822
+ $dispatcher->register_filter(
823
+ 'upgrader_post_install',
824
+ array( 'environment.check', 'check_bulk_addons_activation' )
825
+ );
826
+ if ( $this->if_js_cache_enabled() ) {
827
+ $dispatcher->register_action(
828
+ 'activated_plugin',
829
+ array( 'controller.javascript', 'revalidate_cache' )
830
+ );
831
+ $dispatcher->register_action(
832
+ 'deactivated_plugin',
833
+ array( 'controller.javascript', 'revalidate_cache' )
834
+ );
835
+ $dispatcher->register_action(
836
+ 'upgrader_post_install',
837
+ array( 'controller.javascript', 'revalidate_cache' )
838
+ );
839
+ $dispatcher->register_action(
840
+ 'activated_plugin',
841
+ array( 'controller.javascript-widget', 'revalidate_cache' )
842
+ );
843
+ $dispatcher->register_action(
844
+ 'deactivated_plugin',
845
+ array( 'controller.javascript-widget', 'revalidate_cache' )
846
+ );
847
+ $dispatcher->register_action(
848
+ 'upgrader_post_install',
849
+ array( 'controller.javascript-widget', 'revalidate_cache' )
850
+ );
851
 
852
+ }
853
+ // Widget Creator
854
+ $dispatcher->register_action(
855
+ 'admin_enqueue_scripts',
856
+ array( 'css.admin', 'admin_enqueue_scripts' )
857
+ );
858
+ $dispatcher->register_action(
859
+ 'current_screen',
860
+ array( 'view.admin.widget-creator', 'add_meta_box' )
861
+ );
862
+ $dispatcher->register_action(
863
+ 'admin_menu',
864
+ array( 'view.admin.widget-creator', 'add_page' )
865
+ );
866
+ $dispatcher->register_filter(
867
+ 'pre_set_site_transient_update_plugins',
868
+ array( 'calendar.updates', 'check_updates' )
869
+ );
870
+ $dispatcher->register_filter(
871
+ 'plugins_api',
872
+ array( 'calendar.updates', 'plugins_api_filter' ),
873
+ 10,
874
+ 3
875
+ );
876
+ $dispatcher->register_action(
877
+ 'admin_menu',
878
+ array( 'view.admin.tickets', 'add_page' )
879
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
880
 
881
+ } else { // ! is_admin()
882
+ $dispatcher->register_action(
883
+ 'after_setup_theme',
884
+ array( 'theme.loader', 'execute_theme_functions' )
885
+ );
886
+ $dispatcher->register_action(
887
+ 'the_post',
888
+ array( 'post.content', 'check_content' ),
889
+ PHP_INT_MAX
890
+ );
891
+ $dispatcher->register_action(
892
+ 'send_headers',
893
+ array( 'request.redirect', 'handle_categories_and_tags' )
894
+ );
895
+ $dispatcher->register_action(
896
+ 'wp_head',
897
+ array( 'view.event.single', 'add_meta_tags' )
898
+ );
899
+ }
900
+ }
901
+ /**
902
+ * Outputs menu icon between head tags
903
+ */
904
+ public function admin_head() {
905
+ global $wp_version;
906
+ $argv = array(
907
+ 'before_font_icons' => version_compare( $wp_version, '3.8', '<' ),
908
+ 'admin_theme_img_url' => AI1EC_ADMIN_THEME_IMG_URL,
909
+ 'admin_theme_font_url' => AI1EC_ADMIN_THEME_FONT_URL,
910
+ );
911
+ $this->_registry->get( 'theme.loader' )
912
+ ->get_file( 'timely-menu-icon.twig', $argv, true )
913
+ ->render();
914
+ }
 
 
 
 
 
 
 
 
 
 
 
 
915
 
916
+ /**
917
+ * _add_defaults method
918
+ *
919
+ * Add (merge) default options to given query variable.
920
+ *
921
+ * @param string settingsquery variable to ammend
922
+ *
923
+ * @return string|NULL Modified variable values or NULL on failure
924
+ *
925
+ * @global Ai1ec_Settings $ai1ec_settings Instance of settings object
926
+ * to pull data from
927
+ * @staticvar array $mapper Mapping of query names to
928
+ * default in settings
929
+ */
930
+ protected function _add_defaults( $name ) {
931
+ $settings = $this->_registry->get( 'model.settings' );
932
+ static $mapper = array(
933
+ 'cat' => 'categories',
934
+ 'tag' => 'tags',
935
+ );
936
+ $rq_name = 'ai1ec_' . $name . '_ids';
937
+ if (
938
+ ! isset( $mapper[$name] ) ||
939
+ ! array_key_exists( $rq_name, $this->_request )
940
+ ) {
941
+ return NULL;
942
+ }
943
+ $options = explode( ',', $this->_request[$rq_name] );
944
+ $property = 'default_' . $mapper[$name];
945
+ $options = array_merge(
946
+ $options,
947
+ $settings->get( $property )
948
+ );
949
+ $filtered = array();
950
+ foreach ( $options as $item ) { // avoid array_filter + is_numeric
951
+ $item = (int)$item;
952
+ if ( $item > 0 ) {
953
+ $filtered[] = $item;
954
+ }
955
+ }
956
+ unset( $options );
957
+ if ( empty( $filtered ) ) {
958
+ return NULL;
959
+ }
960
+ return implode( ',', $filtered );
961
+ }
962
 
963
+ /**
964
+ * Process_request function.
965
+ *
966
+ * Initialize/validate custom request array, based on contents of $_REQUEST,
967
+ * to keep track of this component's request variables.
968
+ *
969
+ * @return void
970
+ **/
971
+ protected function _process_request() {
972
+ $settings = $this->_registry->get( 'model.settings' );
973
+ $this->_request = $this->_registry->get( 'http.request.parser' );
974
+ $aco = $this->_registry->get( 'acl.aco' );
975
+ $page_id = $settings->get( 'calendar_page_id' );
976
+ if (
977
+ ! $aco->is_admin() &&
978
+ $page_id &&
979
+ is_page( $page_id )
980
+ ) {
981
+ foreach ( array( 'cat', 'tag' ) as $name ) {
982
+ $implosion = $this->_add_defaults( $name );
983
+ if ( $implosion ) {
984
+ $this->request['ai1ec_' . $name . '_ids'] = $implosion;
985
+ $_REQUEST['ai1ec_' . $name . '_ids'] = $implosion;
986
+ }
987
+ }
988
+ }
989
+ }
990
 
991
+ /**
992
+ * Initialize cron functions.
993
+ *
994
+ * @throws Ai1ec_Scheduling_Exception
995
+ *
996
+ * @return void
997
+ */
998
+ protected function _install_crons() {
999
+ $scheduling = $this->_registry->get( 'scheduling.utility' );
1000
+ $hook_name = 'ai1ec_n_cron';
1001
+ $scheduling->delete( $hook_name );
1002
+ }
 
1003
 
1004
+ /**
1005
+ * Initialize the registry object.
1006
+ *
1007
+ * @param Ai1ec_Loader $ai1ec_loader Instance of Ai1EC classes loader
1008
+ *
1009
+ * @return void Method does not return
1010
+ */
1011
+ protected function _initialize_registry( $ai1ec_loader ) {
1012
+ global $ai1ec_registry;
1013
+ $this->_registry = new Ai1ec_Registry_Object( $ai1ec_loader );
1014
+ Ai1ec_Time_Utility::set_registry( $this->_registry );
1015
+ $ai1ec_registry = $this->_registry;
1016
+ }
 
 
1017
 
1018
+ /**
1019
+ * Loads the CSS for the plugin
1020
+ *
1021
+ */
1022
+ protected function _load_css_if_needed() {
1023
+ // ==================================
1024
+ // = Add the hook to render the css =
1025
+ // ==================================
1026
+ if ( isset( $_GET[Ai1ec_Css_Frontend::QUERY_STRING_PARAM] ) ) {
1027
+ // we need to wait for the extension to be registered if the css
1028
+ // needs to be compiled. Will find a better way when compiling css.
1029
+ $css_controller = $this->_registry->get( 'css.frontend' );
1030
+ add_action( 'plugins_loaded', array( $css_controller, 'render_css' ), 2 );
1031
+ }
1032
+ }
1033
 
1034
+ /**
1035
+ * Load the texdomain for the plugin.
1036
+ *
1037
+ * @wp_hook plugins_loaded
1038
+ *
1039
+ * @return void
1040
+ */
1041
+ public function load_textdomain() {
1042
+ if ( false === $this->_domain_loaded ) {
1043
+ load_plugin_textdomain(
1044
+ AI1EC_PLUGIN_NAME, false, AI1EC_LANGUAGE_PATH
1045
+ );
1046
+ $this->_domain_loaded = true;
1047
+ }
1048
+ }
1049
 
1050
+ /**
1051
+ * Check if the schema is up to date.
1052
+ *
1053
+ * @throws Ai1ec_Database_Schema_Exception
1054
+ * @throws Ai1ec_Database_Update_Exception
1055
+ *
1056
+ * @return void
1057
+ */
1058
+ protected function _initialize_schema() {
1059
+ $option = $this->_registry->get( 'model.option' );
1060
+ $schema_sql = $this->get_current_db_schema();
1061
+ $version = sha1( $schema_sql );
1062
+ // If existing DB version is not consistent with current plugin's version,
1063
+ // or does not exist, then create/update table structure using dbDelta().
1064
+ if ( $option->get( 'ai1ec_db_version' ) != $version ) {
1065
 
1066
+ $errors = $this->_registry->get( 'database.applicator' )
1067
+ ->check_db_consistency_for_date_migration() ;
1068
+ if ( ! empty( $errors ) ) {
1069
+ $message = Ai1ec_I18n::__(
1070
+ 'Your database is found to be corrupt. Likely previous update has failed. Please restore All-in-One Event Calendar tables from a backup and retry.<br>Following errors were found:<br>%s'
1071
+ );
1072
+ $message = sprintf( $message, implode( $errors, '<br>' ) );
1073
+ throw new Ai1ec_Database_Update_Exception( $message );
1074
+ }
1075
+ $this->_registry->get( 'database.applicator' )
1076
+ ->remove_instance_duplicates();
1077
 
1078
+ if (
1079
+ apply_filters( 'ai1ec_perform_scheme_update', true ) &&
1080
+ $this->_registry->get( 'database.helper' )->apply_delta(
1081
+ $schema_sql
1082
+ )
1083
+ ) {
1084
+ $option->set( 'ai1ec_db_version', $version );
1085
+ } else {
1086
+ throw new Ai1ec_Database_Update_Exception();
1087
+ }
1088
 
1089
+ // If the schema structure upgrade is complete move contents
1090
+ $categories_key = 'ai1ec_category_meta_ported';
1091
+ if ( ! $option->get( $categories_key ) ) {
1092
+ $this->_migrate_categories_meta();
1093
+ $option->set( $categories_key, true );
1094
+ }
1095
+ }
1096
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1097
 
1098
+ /**
1099
+ * Transform categories meta information.
1100
+ *
1101
+ * Use new `meta` table instead of legacy `colors` table.
1102
+ *
1103
+ * @return void Method does not return.
1104
+ */
1105
+ protected function _migrate_categories_meta() {
1106
+ $db = $this->_registry->get( 'dbi.dbi' );
1107
+ $table_name = $db->get_table_name( 'ai1ec_event_category_colors' );
1108
+ $db_h = $this->_registry->get( 'database.helper' );
1109
+ if ( $db_h->table_exists( $table_name ) ) { // if old table exists otherwise ignore it
1110
+ // Migrate color information
1111
+ $dest_table = $db->get_table_name( 'ai1ec_event_category_meta' );
1112
+ $colors = $db->select(
1113
+ $table_name,
1114
+ array( 'term_id', 'term_color'),
1115
+ ARRAY_A
1116
+ );
1117
+ if ( ! empty( $colors ) ) {
1118
+ foreach ( $colors as $color ) {
1119
+ $db->insert( $dest_table, $color );
1120
+ }
1121
+ }
1122
+ // Drop the old table
1123
+ $db->query( 'DROP TABLE IF EXISTS ' . $table_name );
1124
+ }
1125
+ }
1126
 
1127
+ /**
1128
+ * Procedures to take when upgrading plugin version
1129
+ *
1130
+ * @return void
1131
+ */
1132
+ protected function _plugin_upgrade_procedures() {
1133
+ $option = $this->_registry->get( 'model.option' );
1134
+ $version = AI1EC_VERSION;
1135
 
1136
+ if ( $option->get( 'ai1ec_version' ) != $version ) {
1137
+ try {
1138
+ // Force regeneration of JS cache
1139
+ $this->_registry->get( 'controller.javascript' )->revalidate_cache();
1140
+ $this->_registry->get( 'controller.javascript-widget' )->revalidate_cache();
1141
 
1142
+ // Run upgrade commands
1143
+ $settings = $this->_registry->get( 'model.settings' );
1144
+ $settings->perform_upgrade_actions();
1145
+ } catch ( Exception $e ) {
1146
+ }
1147
 
1148
+ // Update plugin version
1149
+ $option->set( 'ai1ec_version', $version );
1150
+ }
1151
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1152
 
1153
+ /**
1154
+ * Get current database schema as a multi SQL statement.
1155
+ *
1156
+ * @return string Multiline SQL statement.
1157
+ */
1158
+ public function get_current_db_schema() {
1159
+ $dbi = $this->_registry->get( 'dbi.dbi' );
1160
+ // =======================
1161
+ // = Create table events =
1162
+ // =======================
1163
+ $table_name = $dbi->get_table_name( 'ai1ec_events' );
1164
+ $sql = "CREATE TABLE $table_name (
1165
+ post_id bigint(20) NOT NULL,
1166
+ start int(10) UNSIGNED NOT NULL,
1167
+ end int(10) UNSIGNED,
1168
+ timezone_name varchar(50),
1169
+ allday tinyint(1) NOT NULL,
1170
+ instant_event tinyint(1) NOT NULL DEFAULT 0,
1171
+ recurrence_rules longtext,
1172
+ exception_rules longtext,
1173
+ recurrence_dates longtext,
1174
+ exception_dates longtext,
1175
+ venue varchar(255),
1176
+ country varchar(255),
1177
+ address varchar(255),
1178
+ city varchar(255),
1179
+ province varchar(255),
1180
+ postal_code varchar(32),
1181
+ show_map tinyint(1),
1182
+ contact_name varchar(255),
1183
+ contact_phone varchar(32),
1184
+ contact_email varchar(128),
1185
+ contact_url varchar(255),
1186
+ cost varchar(255),
1187
+ ticket_url varchar(255),
1188
+ ical_feed_url varchar(255),
1189
+ ical_source_url varchar(255),
1190
+ ical_organizer varchar(255),
1191
+ ical_contact varchar(255),
1192
+ ical_uid varchar(255),
1193
+ show_coordinates tinyint(1),
1194
+ latitude decimal(20,15),
1195
+ longitude decimal(20,15),
1196
+ force_regenerate tinyint(1) NOT NULL DEFAULT 0,
1197
+ PRIMARY KEY (post_id),
1198
+ KEY feed_source (ical_feed_url)
1199
+ ) CHARACTER SET utf8;";
1200
 
1201
+ // ==========================
1202
+ // = Create table instances =
1203
+ // ==========================
1204
+ $table_name = $dbi->get_table_name( 'ai1ec_event_instances' );
1205
+ $sql .= "CREATE TABLE $table_name (
1206
+ id bigint(20) NOT NULL AUTO_INCREMENT,
1207
+ post_id bigint(20) NOT NULL,
1208
+ start int(10) UNSIGNED NOT NULL,
1209
+ end int(10) UNSIGNED NOT NULL,
1210
+ PRIMARY KEY (id),
1211
+ UNIQUE KEY evt_instance (post_id,start)
1212
+ ) CHARACTER SET utf8;";
1213
 
1214
+ // ================================
1215
+ // = Create table category colors =
1216
+ // ================================
1217
+ $table_name = $dbi->get_table_name( 'ai1ec_event_category_meta' );
1218
+ $sql .= "CREATE TABLE $table_name (
1219
+ term_id bigint(20) NOT NULL,
1220
+ term_color varchar(255) NOT NULL,
1221
+ term_image varchar(254) NULL DEFAULT NULL,
1222
+ PRIMARY KEY (term_id)
1223
+ ) CHARACTER SET utf8;";
1224
+
1225
+ return $sql;
1226
+ }
1227
  }
app/controller/import-export.php CHANGED
@@ -11,102 +11,102 @@
11
  */
12
  class Ai1ec_Import_Export_Controller {
13
 
14
- /**
15
- * @var array The registered engines.
16
- */
17
- protected $_engines = array();
18
 
19
 
20
- /**
21
- * @var Ai1ec_Registry_Object
22
- */
23
- protected $_registry;
24
 
25
- /**
26
- * @var array Import / export params.
27
- */
28
- protected $_params;
29
 
30
- /**
31
- * This controller is instanciated only if we need to import/export something.
32
- *
33
- * When it is instanciated it allows other engines to be injected through a
34
- * filter. If we do not plan to ship core engines, let's skip the
35
- * $core_engines param.
36
- *
37
- * @param Ai1ec_Registry_Object $registry
38
- * @param array $core_engines
39
- * @param array $params
40
- */
41
- public function __construct(
42
- Ai1ec_Registry_Object $registry,
43
- array $core_engines = array( 'ics', 'api-ics' ),
44
- array $params = array()
45
- ) {
46
- $this->_registry = $registry;
47
- $known_engines = apply_filters(
48
- 'ai1ec_register_import_export_engines',
49
- $core_engines
50
- );
51
- $this->_params = $params;
52
- foreach ( $known_engines as $engine ) {
53
- $this->register( $engine );
54
- }
55
- }
56
 
57
- /**
58
- * Register an import-export engine.
59
- *
60
- * @param string $engine
61
- */
62
- public function register( $engine ) {
63
- $this->_engines[$engine] = true;
64
- }
65
 
66
- /**
67
- * Import events into the calendar.
68
- *
69
- * @param string $engine
70
- * @param array $args
71
- *
72
- * @throws Ai1ec_Engine_Not_Set_Exception If the engine is not set.
73
- * @throws Ai1ec_Parse_Exception If an error happens during parse.
74
- *
75
- * @return int The number of imported events
76
- */
77
- public function import_events( $engine, array $args ) {
78
- if ( ! isset( $this->_engines[$engine] ) ) {
79
- throw new Ai1ec_Engine_Not_Set_Exception(
80
- 'The engine ' . $engine . 'is not registered.'
81
- );
82
- }
83
- // external engines must register themselves into the registry.
84
- $engine = $this->_registry->get( 'import-export.' . $engine );
85
- $exception = null;
86
- try {
87
- return $engine->import( $args );
88
- } catch ( Ai1ec_Parse_Exception $parse_exception ) {
89
- $exception = $parse_exception;
90
- }
91
- throw $exception;
92
- }
93
 
94
- /**
95
- * Export the events using the specified engine.
96
- *
97
- * @param string $engine
98
- * @param array $args
99
- *
100
- * @throws Ai1ec_Engine_Not_Set_Exception
101
- */
102
- public function export_events( $engine, array $args ) {
103
- if ( ! isset( $this->_engines[$engine] ) ) {
104
- throw new Ai1ec_Engine_Not_Set_Exception(
105
- 'The engine ' . $engine . 'is not registered.'
106
- );
107
- }
108
- // external engines must register themselves into the registry.
109
- $engine = $this->_registry->get( 'import-export.' . $engine );
110
- return $engine->export( $args, $this->_params );
111
- }
112
  }
11
  */
12
  class Ai1ec_Import_Export_Controller {
13
 
14
+ /**
15
+ * @var array The registered engines.
16
+ */
17
+ protected $_engines = array();
18
 
19
 
20
+ /**
21
+ * @var Ai1ec_Registry_Object
22
+ */
23
+ protected $_registry;
24
 
25
+ /**
26
+ * @var array Import / export params.
27
+ */
28
+ protected $_params;
29
 
30
+ /**
31
+ * This controller is instanciated only if we need to import/export something.
32
+ *
33
+ * When it is instanciated it allows other engines to be injected through a
34
+ * filter. If we do not plan to ship core engines, let's skip the
35
+ * $core_engines param.
36
+ *
37
+ * @param Ai1ec_Registry_Object $registry
38
+ * @param array $core_engines
39
+ * @param array $params
40
+ */
41
+ public function __construct(
42
+ Ai1ec_Registry_Object $registry,
43
+ array $core_engines = array( 'ics', 'api-ics' ),
44
+ array $params = array()
45
+ ) {
46
+ $this->_registry = $registry;
47
+ $known_engines = apply_filters(
48
+ 'ai1ec_register_import_export_engines',
49
+ $core_engines
50
+ );
51
+ $this->_params = $params;
52
+ foreach ( $known_engines as $engine ) {
53
+ $this->register( $engine );
54
+ }
55
+ }
56
 
57
+ /**
58
+ * Register an import-export engine.
59
+ *
60
+ * @param string $engine
61
+ */
62
+ public function register( $engine ) {
63
+ $this->_engines[$engine] = true;
64
+ }
65
 
66
+ /**
67
+ * Import events into the calendar.
68
+ *
69
+ * @param string $engine
70
+ * @param array $args
71
+ *
72
+ * @throws Ai1ec_Engine_Not_Set_Exception If the engine is not set.
73
+ * @throws Ai1ec_Parse_Exception If an error happens during parse.
74
+ *
75
+ * @return int The number of imported events
76
+ */
77
+ public function import_events( $engine, array $args ) {
78
+ if ( ! isset( $this->_engines[$engine] ) ) {
79
+ throw new Ai1ec_Engine_Not_Set_Exception(
80
+ 'The engine ' . $engine . 'is not registered.'
81
+ );
82
+ }
83
+ // external engines must register themselves into the registry.
84
+ $engine = $this->_registry->get( 'import-export.' . $engine );
85
+ $exception = null;
86
+ try {
87
+ return $engine->import( $args );
88
+ } catch ( Ai1ec_Parse_Exception $parse_exception ) {
89
+ $exception = $parse_exception;
90
+ }
91
+ throw $exception;
92
+ }
93
 
94
+ /**
95
+ * Export the events using the specified engine.
96
+ *
97
+ * @param string $engine
98
+ * @param array $args
99
+ *
100
+ * @throws Ai1ec_Engine_Not_Set_Exception
101
+ */
102
+ public function export_events( $engine, array $args ) {
103
+ if ( ! isset( $this->_engines[$engine] ) ) {
104
+ throw new Ai1ec_Engine_Not_Set_Exception(
105
+ 'The engine ' . $engine . 'is not registered.'
106
+ );
107
+ }
108
+ // external engines must register themselves into the registry.
109
+ $engine = $this->_registry->get( 'import-export.' . $engine );
110
+ return $engine->export( $args, $this->_params );
111
+ }
112
  }
app/controller/javascript-widget.php CHANGED
@@ -11,238 +11,238 @@
11
  */
12
  class Ai1ec_Controller_Javascript_Widget extends Ai1ec_Base {
13
 
14
- const WIDGET_PARAMETER = 'ai1ec_js_widget';
15
- const LEGACY_WIDGET_PARAMETER = 'ai1ec_super_widget';
16
- const WIDGET_JS_CACHE_FILE = '/public/js_cache/ai1ec_js_widget.js';
17
-
18
- protected $_widgets = array();
19
-
20
-
21
- public function add_widget( $widget_id, $widget_class ) {
22
- $this->_widgets[$widget_id] = $widget_class;
23
- }
24
-
25
- public function get_widgets() {
26
- return $this->_widgets;
27
- }
28
-
29
- /**
30
- * Adds Super Widget JS to admin screen.
31
- *
32
- * @param array $files
33
- * @param string $page_to_load
34
- *
35
- * @return array
36
- */
37
- public function add_js( array $files, $page_to_load ) {
38
- if ( 'admin_settings.js' === $page_to_load ) {
39
- $files[] = AI1ECSW_PATH . '/public/js/pages/admin_settings.js';
40
- }
41
- return $files;
42
- }
43
-
44
- /**
45
- * @param array $data
46
- * @return array
47
- */
48
- public function add_js_translation( array $data ) {
49
- $data['set_calendar_page'] = __(
50
- 'You must choose the Calendar page before using the Super Widget',
51
- AI1EC_PLUGIN_NAME
52
- );
53
- return $data;
54
- }
55
-
56
- /**
57
- * Sets the flag to revalidate cached js files on next render.
58
- */
59
- public function revalidate_cache() {
60
- $this->_registry->get( 'model.option' )->set( 'jswidgetupdated', '0' );
61
- }
62
-
63
- /**
64
- * Renders everything that's needed for the embedded widget.
65
- */
66
- public function render_js_widget() {
67
-
68
- if ( isset( $_GET['render'] ) && 'true' === $_GET['render'] ) {
69
- if ( isset( $_GET[self::WIDGET_PARAMETER] ) ){
70
- $widget = $_GET[self::WIDGET_PARAMETER];
71
- } else if ( isset( $_GET[self::LEGACY_WIDGET_PARAMETER] ) ) {
72
- $widget = $_GET[self::LEGACY_WIDGET_PARAMETER];
73
- }
74
- $widget_class = null;
75
- if ( isset( $this->_widgets[$widget] ) ) {
76
- $widget_class = $this->_widgets[$widget];
77
- }
78
- if ( null === $widget_class ) {
79
- return;
80
- }
81
- $widget_instance = $this->_registry->get( $widget_class );
82
- $this->render_content( $widget_instance );
83
- } else {
84
- if (
85
- ! $this->_registry->get( 'model.settings' )->get( 'cache_dynamic_js' ) ||
86
- '1' != $this->_registry->get( 'model.option' )->get( 'jswidgetupdated' ) ||
87
- ! $this->_registry->get( 'filesystem.checker' )->check_file_exists(
88
- AI1EC_PATH . self::WIDGET_JS_CACHE_FILE,
89
- true
90
- )
91
- ) {
92
- $this->render_javascript();
93
- } else {
94
- header(
95
- 'Location: '
96
- . plugin_dir_url( 'all-in-one-event-calendar/public/js_cache/.' )
97
- . 'ai1ec_js_widget.js'
98
- );
99
- exit( 0 );
100
- }
101
- }
102
- }
103
-
104
- public function render_javascript() {
105
-
106
- header( 'Content-Type: application/javascript' );
107
- header(
108
- 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 31536000 ) . ' GMT'
109
- );
110
- header( 'Cache-Control: public, max-age=31536000' );
111
-
112
- $jscontroller = $this->_registry->get( 'controller.javascript' );
113
- $css_controller = $this->_registry->get( 'css.frontend' );
114
- $require_main = AI1EC_ADMIN_THEME_JS_PATH . DIRECTORY_SEPARATOR . 'require.js';
115
- $widget_file = AI1EC_PATH . '/public/js/widget/common_widget.js';
116
- $translation = $jscontroller->get_frontend_translation_data();
117
- $page_id = $this->_registry->get(
118
- 'model.settings'
119
- )->get( 'calendar_page_id' );
120
- $permalink = get_permalink(
121
- $page_id
122
- );
123
- $full_permalink = $this->_registry->get(
124
- 'template.link.helper'
125
- )->get_full_permalink( $page_id );
126
- // load the css to hardcode, saving a call
127
- $css_rules = $css_controller->get_compiled_css();
128
- $css_rules = addslashes( $css_rules );
129
- $translation['permalinks_structure'] = $this->
130
- _registry->get( 'model.option' )->get( 'permalink_structure' );
131
- $translation['calendar_url'] = preg_replace( '/^https?:/', '', $permalink );
132
- $translation['full_calendar_url'] = preg_replace( '/^https?:/', '', $full_permalink );
133
- // Let extensions add their scripts.
134
- // look at Extended Views or Super Widget for examples
135
- $extension_urls = array();
136
- $extension_urls = apply_filters(
137
- 'ai1ec_render_js',
138
- $extension_urls,
139
- 'ai1ec_widget.js'
140
- );
141
- // Removing http:// or https:// from extension URLs
142
- foreach ( $extension_urls as &$extension_url ) {
143
- $extension_url = preg_replace( '/https?:/', '', $extension_url );
144
- }
145
-
146
- $translation['extension_urls'] = $extension_urls;
147
- // the single event page js is loaded dinamically.
148
- $translation['event_page'] = array(
149
- 'id' => 'ai1ec_event',
150
- 'url' => preg_replace( '/^https?:/', '', AI1EC_URL ) . '/public/js/pages/event.js',
151
- );
152
- $translation_module = $jscontroller->create_require_js_module(
153
- Ai1ec_Javascript_Controller::FRONTEND_CONFIG_MODULE,
154
- $translation
155
- );
156
- // get requirejs
157
- $require = file_get_contents( $require_main );
158
- $main_widget = file_get_contents( $widget_file );
159
- $require_config = $jscontroller->create_require_js_config_object();
160
- $config = $jscontroller->create_require_js_module(
161
- 'ai1ec_config',
162
- $jscontroller->get_translation_data()
163
- );
164
- // get jquery
165
- $jquery = $jscontroller->get_jquery_version_based_on_browser(
166
- isset( $_SERVER['HTTP_USER_AGENT'] )
167
- ? $_SERVER['HTTP_USER_AGENT']
168
- : ''
169
- );
170
-
171
- $domready = $jscontroller->get_module(
172
- 'domReady.js'
173
- );
174
- $frontend = $jscontroller->get_module(
175
- 'scripts/common_scripts/frontend/common_frontend.js'
176
- );
177
-
178
- // compress data if possible
179
- $compatibility_ob = $this->_registry->get( 'compatibility.ob' );
180
- $js = <<<JS
181
- /******** Called once Require.js has loaded ******/
182
-
183
- (function() {
184
-
185
- var timely_css = document.createElement( 'style' );
186
- timely_css.innerHTML = '$css_rules';
187
- ( document.getElementsByTagName( "head" )[0] || document.documentElement ).appendChild( timely_css );
188
- // bring in requires
189
- $require
190
- // make timely global
191
- window.timely = timely;
192
- $require_config
193
- // Load other modules
194
- $translation_module
195
- $config
196
- $jquery
197
- $frontend
198
-
199
- // start up the widget
200
- $main_widget
201
- })(); // We call our anonymous function immediately
202
  JS;
203
- $compatibility_ob->gzip_if_possible( $js );
204
-
205
- if (
206
- $this->_registry->get( 'model.settings' )->get( 'cache_dynamic_js' ) &&
207
- (
208
- '0' === $this->_registry->get( 'model.option' )->get( 'jswidgetupdated' ) ||
209
- ! $this->_registry->get( 'filesystem.checker' )->check_file_exists(
210
- AI1EC_PATH . self::WIDGET_JS_CACHE_FILE,
211
- true
212
- )
213
- )
214
- ) {
215
- try {
216
- $js_path = AI1EC_ADMIN_THEME_JS_PATH . DIRECTORY_SEPARATOR;
217
- $js_saved = file_put_contents(
218
- $js_path . '../js_cache/ai1ec_js_widget.js',
219
- $js
220
- );
221
- if ( $js_saved ) {
222
- $this->_registry->get( 'model.option' )->set( 'jswidgetupdated', '1' );
223
- }
224
- } catch ( Exception $e ) {
225
- $this->_registry->get( 'model.settings' )->set( 'cache_dynamic_js', false );
226
- }
227
- }
228
- exit( 0 );
229
- }
230
-
231
- public function render_content( Ai1ec_Embeddable $widget_instance ) {
232
- $args = array();
233
- $defaults = $widget_instance->get_js_widget_configurable_defaults();
234
- foreach ( $defaults as $id => $value ) {
235
- if ( isset( $_GET[$id] ) ) {
236
- $args[$id] = $_GET[$id];
237
- }
238
- }
239
- $html = $widget_instance->javascript_widget( $args );
240
- $jsonp = $this->_registry->get( 'http.response.render.strategy.jsonp' );
241
- $jsonp->render(
242
- array(
243
- 'data' => array( 'html' => $html )
244
- )
245
- );
246
- }
247
 
248
  }
11
  */
12
  class Ai1ec_Controller_Javascript_Widget extends Ai1ec_Base {
13
 
14
+ const WIDGET_PARAMETER = 'ai1ec_js_widget';
15
+ const LEGACY_WIDGET_PARAMETER = 'ai1ec_super_widget';
16
+ const WIDGET_JS_CACHE_FILE = '/public/js_cache/ai1ec_js_widget.js';
17
+
18
+ protected $_widgets = array();
19
+
20
+
21
+ public function add_widget( $widget_id, $widget_class ) {
22
+ $this->_widgets[$widget_id] = $widget_class;
23
+ }
24
+
25
+ public function get_widgets() {
26
+ return $this->_widgets;
27
+ }
28
+
29
+ /**
30
+ * Adds Super Widget JS to admin screen.
31
+ *
32
+ * @param array $files
33
+ * @param string $page_to_load
34
+ *
35
+ * @return array
36
+ */
37
+ public function add_js( array $files, $page_to_load ) {
38
+ if ( 'admin_settings.js' === $page_to_load ) {
39
+ $files[] = AI1ECSW_PATH . '/public/js/pages/admin_settings.js';
40
+ }
41
+ return $files;
42
+ }
43
+
44
+ /**
45
+ * @param array $data
46
+ * @return array
47
+ */
48
+ public function add_js_translation( array $data ) {
49
+ $data['set_calendar_page'] = __(
50
+ 'You must choose the Calendar page before using the Super Widget',
51
+ AI1EC_PLUGIN_NAME
52
+ );
53
+ return $data;
54
+ }
55
+
56
+ /**
57
+ * Sets the flag to revalidate cached js files on next render.
58
+ */
59
+ public function revalidate_cache() {
60
+ $this->_registry->get( 'model.option' )->set( 'jswidgetupdated', '0' );
61
+ }
62
+
63
+ /**
64
+ * Renders everything that's needed for the embedded widget.
65
+ */
66
+ public function render_js_widget() {
67
+
68
+ if ( isset( $_GET['render'] ) && 'true' === $_GET['render'] ) {
69
+ if ( isset( $_GET[self::WIDGET_PARAMETER] ) ){
70
+ $widget = $_GET[self::WIDGET_PARAMETER];
71
+ } else if ( isset( $_GET[self::LEGACY_WIDGET_PARAMETER] ) ) {
72
+ $widget = $_GET[self::LEGACY_WIDGET_PARAMETER];
73
+ }
74
+ $widget_class = null;
75
+ if ( isset( $this->_widgets[$widget] ) ) {
76
+ $widget_class = $this->_widgets[$widget];
77
+ }
78
+ if ( null === $widget_class ) {
79
+ return;
80
+ }
81
+ $widget_instance = $this->_registry->get( $widget_class );
82
+ $this->render_content( $widget_instance );
83
+ } else {
84
+ if (
85
+ ! $this->_registry->get( 'model.settings' )->get( 'cache_dynamic_js' ) ||
86
+ '1' != $this->_registry->get( 'model.option' )->get( 'jswidgetupdated' ) ||
87
+ ! $this->_registry->get( 'filesystem.checker' )->check_file_exists(
88
+ AI1EC_PATH . self::WIDGET_JS_CACHE_FILE,
89
+ true
90
+ )
91
+ ) {
92
+ $this->render_javascript();
93
+ } else {
94
+ header(
95
+ 'Location: '
96
+ . plugin_dir_url( 'all-in-one-event-calendar/public/js_cache/.' )
97
+ . 'ai1ec_js_widget.js'
98
+ );
99
+ exit( 0 );
100
+ }
101
+ }
102
+ }
103
+
104
+ public function render_javascript() {
105
+
106
+ header( 'Content-Type: application/javascript' );
107
+ header(
108
+ 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 31536000 ) . ' GMT'
109
+ );
110
+ header( 'Cache-Control: public, max-age=31536000' );
111
+
112
+ $jscontroller = $this->_registry->get( 'controller.javascript' );
113
+ $css_controller = $this->_registry->get( 'css.frontend' );
114
+ $require_main = AI1EC_ADMIN_THEME_JS_PATH . DIRECTORY_SEPARATOR . 'require.js';
115
+ $widget_file = AI1EC_PATH . '/public/js/widget/common_widget.js';
116
+ $translation = $jscontroller->get_frontend_translation_data();
117
+ $page_id = $this->_registry->get(
118
+ 'model.settings'
119
+ )->get( 'calendar_page_id' );
120
+ $permalink = get_permalink(
121
+ $page_id
122
+ );
123
+ $full_permalink = $this->_registry->get(
124
+ 'template.link.helper'
125
+ )->get_full_permalink( $page_id );
126
+ // load the css to hardcode, saving a call
127
+ $css_rules = $css_controller->get_compiled_css();
128
+ $css_rules = addslashes( $css_rules );
129
+ $translation['permalinks_structure'] = $this->
130
+ _registry->get( 'model.option' )->get( 'permalink_structure' );
131
+ $translation['calendar_url'] = preg_replace( '/^https?:/', '', $permalink );
132
+ $translation['full_calendar_url'] = preg_replace( '/^https?:/', '', $full_permalink );
133
+ // Let extensions add their scripts.
134
+ // look at Extended Views or Super Widget for examples
135
+ $extension_urls = array();
136
+ $extension_urls = apply_filters(
137
+ 'ai1ec_render_js',
138
+ $extension_urls,
139
+ 'ai1ec_widget.js'
140
+ );
141
+ // Removing http:// or https:// from extension URLs
142
+ foreach ( $extension_urls as &$extension_url ) {
143
+ $extension_url = preg_replace( '/https?:/', '', $extension_url );
144
+ }
145
+
146
+ $translation['extension_urls'] = $extension_urls;
147
+ // the single event page js is loaded dinamically.
148
+ $translation['event_page'] = array(
149
+ 'id' => 'ai1ec_event',
150
+ 'url' => preg_replace( '/^https?:/', '', AI1EC_URL ) . '/public/js/pages/event.js',
151
+ );
152
+ $translation_module = $jscontroller->create_require_js_module(
153
+ Ai1ec_Javascript_Controller::FRONTEND_CONFIG_MODULE,
154
+ $translation
155
+ );
156
+ // get requirejs
157
+ $require = file_get_contents( $require_main );
158
+ $main_widget = file_get_contents( $widget_file );
159
+ $require_config = $jscontroller->create_require_js_config_object();
160
+ $config = $jscontroller->create_require_js_module(
161
+ 'ai1ec_config',
162
+ $jscontroller->get_translation_data()
163
+ );
164
+ // get jquery
165
+ $jquery = $jscontroller->get_jquery_version_based_on_browser(
166
+ isset( $_SERVER['HTTP_USER_AGENT'] )
167
+ ? $_SERVER['HTTP_USER_AGENT']
168
+ : ''
169
+ );
170
+
171
+ $domready = $jscontroller->get_module(
172
+ 'domReady.js'
173
+ );
174
+ $frontend = $jscontroller->get_module(
175
+ 'scripts/common_scripts/frontend/common_frontend.js'
176
+ );
177
+
178
+ // compress data if possible
179
+ $compatibility_ob = $this->_registry->get( 'compatibility.ob' );
180
+ $js = <<<JS
181
+ /******** Called once Require.js has loaded ******/
182
+
183
+ (function() {
184
+
185
+ var timely_css = document.createElement( 'style' );
186
+ timely_css.innerHTML = '$css_rules';
187
+ ( document.getElementsByTagName( "head" )[0] || document.documentElement ).appendChild( timely_css );
188
+ // bring in requires
189
+ $require
190
+ // make timely global
191
+ window.timely = timely;
192
+ $require_config
193
+ // Load other modules
194
+ $translation_module
195
+ $config
196
+ $jquery
197
+ $frontend
198
+
199
+ // start up the widget
200
+ $main_widget
201
+ })(); // We call our anonymous function immediately
202
  JS;
203
+ $compatibility_ob->gzip_if_possible( $js );
204
+
205
+ if (
206
+ $this->_registry->get( 'model.settings' )->get( 'cache_dynamic_js' ) &&
207
+ (
208
+ '0' === $this->_registry->get( 'model.option' )->get( 'jswidgetupdated' ) ||
209
+ ! $this->_registry->get( 'filesystem.checker' )->check_file_exists(
210
+ AI1EC_PATH . self::WIDGET_JS_CACHE_FILE,
211
+ true
212
+ )
213
+ )
214
+ ) {
215
+ try {
216
+ $js_path = AI1EC_ADMIN_THEME_JS_PATH . DIRECTORY_SEPARATOR;
217
+ $js_saved = file_put_contents(
218
+ $js_path . '../js_cache/ai1ec_js_widget.js',
219
+ $js
220
+ );
221
+ if ( $js_saved ) {
222
+ $this->_registry->get( 'model.option' )->set( 'jswidgetupdated', '1' );
223
+ }
224
+ } catch ( Exception $e ) {
225
+ $this->_registry->get( 'model.settings' )->set( 'cache_dynamic_js', false );
226
+ }
227
+ }
228
+ exit( 0 );
229
+ }
230
+
231
+ public function render_content( Ai1ec_Embeddable $widget_instance ) {
232
+ $args = array();
233
+ $defaults = $widget_instance->get_js_widget_configurable_defaults();
234
+ foreach ( $defaults as $id => $value ) {
235
+ if ( isset( $_GET[$id] ) ) {
236
+ $args[$id] = $_GET[$id];
237
+ }
238
+ }
239
+ $html = $widget_instance->javascript_widget( $args );
240
+ $jsonp = $this->_registry->get( 'http.response.render.strategy.jsonp' );
241
+ $jsonp->render(
242
+ array(
243
+ 'data' => array( 'html' => $html )
244
+ )
245
+ );
246
+ }
247
 
248
  }
app/controller/javascript.php CHANGED
@@ -10,908 +10,908 @@
10
  */
11
  class Ai1ec_Javascript_Controller {
12
 
13
- // The js handle used when enqueueing
14
- const JS_HANDLE = 'ai1ec_requirejs';
15
 
16
- // The namespace for require.js functions
17
- const REQUIRE_NAMESPACE = 'timely';
18
-
19
- // the name of the configuration module for the frontend
20
- const FRONTEND_CONFIG_MODULE = 'ai1ec_calendar';
21
-
22
- //the name of the get parameter we use for loading js
23
- const LOAD_JS_PARAMETER = 'ai1ec_render_js';
24
-
25
- // just load backend scripts
26
- const LOAD_ONLY_BACKEND_SCRIPTS = 'common_backend';
27
-
28
- // just load backend scripts
29
- const LOAD_ONLY_FRONTEND_SCRIPTS = 'common_frontend';
30
-
31
- // Are we in the backend
32
- const IS_BACKEND_PARAMETER = 'is_backend';
33
-
34
- // Are we on the calendar page
35
- const IS_CALENDAR_PAGE = 'is_calendar_page';
36
-
37
- // this is the value of IS_BACKEND_PARAMETER which triggers loading of backend script
38
- const TRUE_PARAM = 'true';
39
-
40
- // the javascript file for event page
41
- const EVENT_PAGE_JS = 'event.js';
42
-
43
- // the javascript file for calendar page
44
- const CALENDAR_PAGE_JS = 'calendar.js';
45
-
46
- // the file for the calendar feedsa page
47
- const CALENDAR_FEEDS_PAGE = 'calendar_feeds.js';
48
-
49
- // add new event page js
50
- const ADD_NEW_EVENT_PAGE = 'add_new_event.js';
51
-
52
- // event category page js
53
- const EVENT_CATEGORY_PAGE = 'event_category.js';
54
-
55
- // less variable editing page
56
- const LESS_VARIBALES_PAGE = 'less_variables_editing.js';
57
-
58
- // settings page
59
- const SETTINGS_PAGE = 'admin_settings.js';
60
-
61
- //widget creator page
62
- const WIDGET_CREATOR = 'widget-creator.js';
63
-
64
- //ticketing page
65
- const TICKETING = 'ticketing.js';
66
-
67
- //cache file
68
- const CALENDAR_JS_CACHE_FILE = '/public/js_cache/calendar.js';
69
-
70
- /**
71
- * @var Ai1ec_Registry_Object
72
- */
73
- private $_registry;
74
-
75
- /**
76
- * The core js pages to load.
77
- * Used to avoid errors when extensions add pages.
78
- *
79
- * @var array
80
- */
81
- private $_core_pages = array(
82
- self::CALENDAR_FEEDS_PAGE => true,
83
- self::ADD_NEW_EVENT_PAGE => true,
84
- self::EVENT_CATEGORY_PAGE => true,
85
- self::LESS_VARIBALES_PAGE => true,
86
- self::SETTINGS_PAGE => true,
87
- self::EVENT_PAGE_JS => true,
88
- self::CALENDAR_PAGE_JS => true,
89
- self::WIDGET_CREATOR => true,
90
- self::TICKETING => true,
91
- );
92
-
93
- /**
94
- * Holds an instance of the settings object
95
- *
96
- * @var Ai1ec_Settings
97
- */
98
- private $_settings;
99
-
100
- /**
101
- * @var Ai1ec_Locale
102
- */
103
- private $_locale;
104
-
105
- /**
106
- * @var Ai1ec_Scripts
107
- */
108
- private $_scripts_helper;
109
-
110
- /**
111
- * @var Ai1ec_Acl_Aco
112
- */
113
- private $_aco;
114
-
115
- /**
116
- * @var Ai1ec_Template_Link_Helper
117
- */
118
- private $_template_link_helper;
119
-
120
- /**
121
- * @var bool
122
- */
123
- protected $_frontend_scripts_loaded = false;
124
-
125
- /**
126
- * Public constructor.
127
- *
128
- * @param Ai1ec_Registry_Object $registry
129
- *
130
- * @return void
131
- */
132
- public function __construct( Ai1ec_Registry_Object $registry ) {
133
- $this->_registry = $registry;
134
- $this->_settings = $registry->get( 'model.settings' );
135
- $this->_locale = $registry->get( 'p28n.wpml' );
136
- $this->_aco = $registry->get( 'acl.aco' );
137
- $this->_template_link_helper = $registry->get( 'template.link.helper' );
138
- // this will need to be modified
139
- $this->_scripts_helper = $registry->get( 'script.helper' );
140
- }
141
-
142
- /**
143
- * Load javascript files for frontend pages.
144
- *
145
- * @wp-hook ai1ec_load_frontend_js
146
- *
147
- * @param $is_calendar_page boolean Whether we are displaying the main
148
- * calendar page or not
149
- *
150
- * @return void
151
- */
152
- public function load_frontend_js( $is_calendar_page, $is_shortcode = false ) {
153
- $page = null;
154
-
155
- // ======
156
- // = JS =
157
- // ======
158
- if( $this->_are_we_accessing_the_single_event_page() === true ) {
159
- $page = self::EVENT_PAGE_JS;
160
- }
161
- if( $is_calendar_page === true ) {
162
- $page = self::CALENDAR_PAGE_JS;
163
- }
164
- if( null !== $page ) {
165
- $this->add_link_to_render_js( $page, false );
166
- }
167
- }
168
-
169
- /**
170
- * Render the javascript for the appropriate page.
171
- *
172
- * @return void
173
- */
174
- public function render_js() {
175
- $js_path = AI1EC_ADMIN_THEME_JS_PATH . DIRECTORY_SEPARATOR;
176
- $common_js = '';
177
- $js_cache = $this->_settings->get( 'cache_dynamic_js' );
178
-
179
- if ( ! isset( $_GET[self::LOAD_JS_PARAMETER] ) ) {
180
- return null;
181
- }
182
- $page_to_load = $_GET[self::LOAD_JS_PARAMETER];
183
- $scripts_updated = $this->_registry->get( 'model.option' )->get( 'calendarjsupdated' );
184
-
185
- if (
186
- $js_cache &&
187
- $page_to_load === self::CALENDAR_PAGE_JS &&
188
- '1' === $scripts_updated &&
189
- $this->_registry->get( 'filesystem.checker' )->check_file_exists(
190
- AI1EC_PATH . self::CALENDAR_JS_CACHE_FILE,
191
- true
192
- )
193
- ) {
194
- Ai1ec_Http_Response_Helper::stop( 0 );
195
- return;
196
- }
197
-
198
- if (
199
- isset( $_GET[self::IS_BACKEND_PARAMETER] ) &&
200
- $_GET[self::IS_BACKEND_PARAMETER] === self::TRUE_PARAM
201
- ) {
202
- $common_js = file_get_contents( $js_path . 'pages/common_backend.js' );
203
- } else if (
204
- $page_to_load === self::EVENT_PAGE_JS ||
205
- $page_to_load === self::CALENDAR_PAGE_JS ||
206
- $page_to_load === self::LOAD_ONLY_FRONTEND_SCRIPTS
207
- ) {
208
- if (
209
- $page_to_load === self::LOAD_ONLY_FRONTEND_SCRIPTS &&
210
- true === $this->_frontend_scripts_loaded
211
- ) {
212
- return;
213
- }
214
- if ( false === $this->_frontend_scripts_loaded ) {
215
- $common_js = file_get_contents(
216
- $js_path . 'pages/common_frontend.js'
217
- );
218
- $this->_frontend_scripts_loaded = true;
219
- }
220
- }
221
-
222
- // Create the config object for Require.js.
223
- $require_config = $this->create_require_js_config_object();
224
-
225
- // Load Require.js script.
226
- $require = file_get_contents( $js_path . 'require.js' );
227
-
228
- // Load appropriate jQuery script based on browser.
229
- $jquery = $this->get_jquery_version_based_on_browser(
230
- isset( $_SERVER['HTTP_USER_AGENT'] )
231
- ? $_SERVER['HTTP_USER_AGENT']
232
- : ''
233
- );
234
-
235
- // Load the main script for the page.
236
- $page_js = '';
237
- if ( isset( $this->_core_pages[$page_to_load] ) ) {
238
- $page_js = file_get_contents( $js_path . 'pages/' . $page_to_load );
239
- }
240
-
241
- // Load translation module.
242
- $translation = $this->get_frontend_translation_data();
243
- $permalink = $this->_template_link_helper
244
- ->get_permalink( $this->_settings->get( 'calendar_page_id' ) );
245
- $full_permalink = $this->_template_link_helper
246
- ->get_full_permalink( $this->_settings->get( 'calendar_page_id' ) );
247
- $translation['calendar_url'] = $permalink;
248
- $translation['full_calendar_url'] = $full_permalink;
249
- $translation_module = $this->create_require_js_module(
250
- self::FRONTEND_CONFIG_MODULE,
251
- $translation
252
- );
253
-
254
- // Load Ai1ec config script.
255
- $config = $this->create_require_js_module(
256
- 'ai1ec_config',
257
- $this->get_translation_data()
258
- );
259
-
260
- // Let extensions add their scripts.
261
- $extension_files = array();
262
- $extension_files = apply_filters(
263
- 'ai1ec_render_js',
264
- $extension_files,
265
- $page_to_load
266
- );
267
- $ext_js = '';
268
-
269
- foreach ( $extension_files as $file ) {
270
- $ext_js .= file_get_contents( $file );
271
- }
272
-
273
- // Finally, load the page_ready script to execute code that must run after
274
- // all scripts have been loaded.
275
- $page_ready = file_get_contents(
276
- $js_path . 'scripts/common_scripts/page_ready.js'
277
- );
278
-
279
- $javascript = $require . $require_config . $translation_module .
280
- $config . $jquery . $common_js . $ext_js . $page_js . $page_ready;
281
- // add to blank spaces to fix issues with js
282
- // being truncated onn some installs
283
- $javascript .= ' ';
284
-
285
- if (
286
- $js_cache &&
287
- $page_to_load === self::CALENDAR_PAGE_JS &&
288
- (
289
- '0' === $scripts_updated ||
290
- ! $this->_registry->get( 'filesystem.checker' )->check_file_exists(
291
- AI1EC_PATH . self::CALENDAR_JS_CACHE_FILE,
292
- true
293
- )
294
- )
295
- ) {
296
- $js_saved = false;
297
- try {
298
- $js_saved = file_put_contents(
299
- $js_path . '../js_cache/' . self::CALENDAR_PAGE_JS,
300
- $javascript
301
- );
302
- if ( $js_saved ) {
303
- $this->_registry->get( 'model.option' )->set( 'calendarjsupdated', '1' );
304
- }
305
- } catch ( Exception $e ) {
306
- $this->_settings->set( 'cache_dynamic_js', false );
307
- }
308
- }
309
-
310
- $this->_echo_javascript( $javascript );
311
- }
312
-
313
-
314
- /**
315
- * Sets the flag to revalidate cached js files on next render.
316
- */
317
- public function revalidate_cache() {
318
- $this->_registry->get( 'model.option' )->set( 'calendarjsupdated', '0' );
319
- }
320
-
321
- /**
322
- * Get a compiled javascript file ( used by extensions )
323
- *
324
- * @param string $name
325
- *
326
- * @return string
327
- */
328
- public function get_module( $name ) {
329
- $js_path = AI1EC_ADMIN_THEME_JS_PATH . DIRECTORY_SEPARATOR;
330
- return file_get_contents( $js_path . $name );
331
- }
332
-
333
- /**
334
- * Check what file needs to be loaded and add the correct link.
335
- *
336
- * @wp-hook init
337
- *
338
- * @return void
339
- */
340
- public function load_admin_js() {
341
- // Initialize dashboard view
342
-
343
- $script_to_load = FALSE;
344
- if ( $this->are_we_on_calendar_feeds_page() === TRUE ) {
345
- // Load script for the importer plugins
346
- $script_to_load[] = self::CALENDAR_FEEDS_PAGE;
347
- }
348
- // Start the scripts for the event category page
349
- if ( $this->_are_we_editing_event_categories() === TRUE ) {
350
- // Load script required when editing categories
351
- $script_to_load[] = self::EVENT_CATEGORY_PAGE;
352
- }
353
- if ( $this->_are_we_editing_less_variables() === TRUE ) {
354
- // Load script required when editing categories
355
- $script_to_load[] = self::LESS_VARIBALES_PAGE;
356
- }
357
- // Load the js needed when you edit an event / add a new event
358
- if (
359
- true === $this->_are_we_creating_a_new_event() ||
360
- true === $this->_are_we_editing_an_event()
361
- ) {
362
- // Load script for adding / modifying events
363
- $script_to_load[] = self::ADD_NEW_EVENT_PAGE;
364
- }
365
- if ( true === $this->_are_we_accessing_the_calendar_settings_page() ) {
366
- $script_to_load[] = self::SETTINGS_PAGE;
367
- }
368
- if ( true === $this->_are_we_creating_widgets() ) {
369
- $script_to_load[] = self::WIDGET_CREATOR;
370
- }
371
- if (
372
- true === $this->_are_we_managing_tickets() ||
373
- true === $this->_are_we_managing_events_list()
374
- ) {
375
- $script_to_load[] = self::TICKETING;
376
- }
377
- if ( false === $script_to_load ) {
378
- $script_to_load[] = apply_filters( 'ai1ec_backend_js', self::LOAD_ONLY_BACKEND_SCRIPTS );
379
- }
380
- foreach ($script_to_load as $value) {
381
- $this->add_link_to_render_js( $value, true );
382
- }
383
- }
384
-
385
- /**
386
- * Loads version 1.9 or 2.0 of jQuery based on user agent.
387
- * If $user_agent is null (due to lack of HTTP header) we always serve
388
- * jQuery 2.0.
389
- *
390
- * @param string $user_agent
391
- *
392
- * @return string
393
- */
394
- public function get_jquery_version_based_on_browser( $user_agent ) {
395
- $js_path = AI1EC_ADMIN_THEME_JS_PATH . DIRECTORY_SEPARATOR;
396
- $jquery = 'jquery_timely20.js';
397
-
398
- preg_match( '/MSIE (.*?);/', $user_agent, $matches );
399
- if ( count( $matches ) > 1 ) {
400
- //Then we're using IE
401
- $version = (int) $matches[1];
402
- if ( $version <= 8 ) {
403
- //IE 8 or under!
404
- $jquery = 'jquery_timely19.js';
405
- }
406
- }
407
- return file_get_contents( $js_path . $jquery );
408
- }
409
-
410
- /**
411
- * Creates a requirejs module that can be used for translations
412
- *
413
- * @param string $object_name
414
- * @param array $data
415
- *
416
- * @return string
417
- */
418
- public function create_require_js_module( $object_name, array $data ) {
419
- foreach ( (array) $data as $key => $value ) {
420
- if ( ! is_scalar( $value ) )
421
- continue;
422
- $data[$key] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8');
423
- }
424
- $json_data = json_encode( $data );
425
- $prefix = self::REQUIRE_NAMESPACE;
426
- $script = "$prefix.define( '$object_name', $json_data );";
427
-
428
- return $script;
429
- }
430
-
431
- /**
432
- * Create the array needed for translation and passing other settings to JS.
433
- *
434
- * @return $data array the dynamic data array
435
- */
436
- public function get_translation_data() {
437
-
438
- $force_ssl_admin = force_ssl_admin();
439
- if ( $force_ssl_admin && ! is_ssl() ) {
440
- force_ssl_admin( false );
441
- }
442
- $ajax_url = ai1ec_admin_url( 'admin-ajax.php' );
443
- force_ssl_admin( $force_ssl_admin );
444
- $settings = $this->_registry->get( 'model.settings' );
445
- $locale = $this->_registry->get( 'p28n.wpml' );
446
- $blog_timezone = $this->_registry->get( 'model.option' )
447
- ->get( 'gmt_offset' );
448
- $application = $this->_registry->get( 'bootstrap.registry.application' );
449
- $data = array(
450
- 'calendar_feeds_nonce' => wp_create_nonce( 'ai1ec_ics_feed_nonce'),
451
- // ICS feed error messages
452
- 'duplicate_feed_message' => esc_html(
453
- Ai1ec_I18n::__( 'This feed is already being imported.' )
454
- ),
455
- 'invalid_url_message' => esc_html(
456
- Ai1ec_I18n::__( 'Please enter a valid iCalendar URL.' )
457
- ),
458
- 'invalid_website_message' => esc_html(
459
- Ai1ec_I18n::__( 'Please enter a valid Website URL.' )
460
- ),
461
- 'invalid_registration_message' => esc_html(
462
- Ai1ec_I18n::__( 'Please enter a valid Registration URL, starting with https:// or http://.' )
463
- ),
464
- 'invalid_email_message' => esc_html(
465
- Ai1ec_I18n::__( 'Please enter a valid email address.' )
466
- ),
467
- 'choose_image_message' => Ai1ec_I18n::__( 'Choose Image' ),
468
- 'now' => $this->_registry->get( 'date.system' )
469
- ->current_time(),
470
- 'size_less_variable_not_ok' => Ai1ec_I18n::__(
471
- 'The value you have entered is not a valid CSS length.'
472
- ),
473
- 'confirm_reset_theme' => Ai1ec_I18n::__(
474
- 'Are you sure you want to reset your theme options to their default values?'
475
- ),
476
- 'error_message_not_valid_lat' => Ai1ec_I18n::__(
477
- 'Please enter a valid latitude. A valid latitude is comprised between +90 and -90.'
478
- ),
479
- 'error_message_not_valid_long' => Ai1ec_I18n::__(
480
- 'Please enter a valid longitude. A valid longitude is comprised between +180 and -180.'
481
- ),
482
- 'error_message_not_entered_lat' => Ai1ec_I18n::__(
483
- 'When the "Input coordinates" checkbox is checked, "Latitude" is a required field.'
484
- ),
485
- 'error_message_not_entered_long' => Ai1ec_I18n::__(
486
- 'When the "Input coordinates" checkbox is checked, "Longitude" is a required field.'
487
- ),
488
- 'ai1ec_contact_url_not_valid' => Ai1ec_I18n::__(
489
- 'The URL you have entered in the <b>Organizer Contact Info</b> &gt; <b>Website URL</b> seems to be invalid.'
490
- ),
491
- 'ai1ec_ticket_ext_url_not_valid' => Ai1ec_I18n::__(
492
- 'The URL you have entered in the <b>Event Cost and Tickets</b> &gt; <b>Tickets or Registration URL</b> seems to be invalid.'
493
- ),
494
- 'ai1ec_contact_email_not_valid' => Ai1ec_I18n::__(
495
- 'The Email you have entered in the <b>Organizer Contact Info</b> &gt; <b>E-mail</b> seems to be invalid.'
496
- ),
497
- 'general_url_not_valid' => Ai1ec_I18n::__(
498
- 'Please remember that URLs must start with either "http://" or "https://".'
499
- ),
500
- 'calendar_loading' => Ai1ec_I18n::__(
501
- 'Loading&hellip;'
502
- ),
503
- 'ticketing_required_fields' => Ai1ec_I18n::__(
504
- '<b>Required or incorrect fields for Ticketing are outlined red.</b>'
505
- ),
506
- 'ticketing_repeat_not_supported' => Ai1ec_I18n::__( '<b>The Repeat option was selected but recurrence is not supported by Event with Tickets.</b>'
507
- ),
508
- 'ticketing_no_tickets_included' => Ai1ec_I18n::__( '<b>
509
- The Event has the cost option Tickets selected but no ticket was included.</b>'
510
- ),
511
- 'discovery_event_success' => Ai1ec_I18n::__(
512
- 'Event was imported successfully.'
513
- ),
514
- 'discovery_event_error' => Ai1ec_I18n::__(
515
- 'An error occurred when importing event. Please, try later.'
516
- ),
517
- 'language' => $this->_registry->get( 'p28n.wpml' )->get_lang(),
518
- 'ajax_url' => $ajax_url,
519
- // 24h time format for time pickers
520
- 'twentyfour_hour' => $settings->get( 'input_24h_time' ),
521
- // Date format for date pickers
522
- 'date_format' => $settings->get( 'input_date_format' ),
523
- // Names for months in date picker header (escaping is done in wp_localize_script)
524
- 'month_names' => $locale->get_localized_month_names(),
525
- // Names for days in date picker header (escaping is done in wp_localize_script)
526
- 'day_names' => $locale->get_localized_week_names(),
527
- // Start the week on this day in the date picker
528
- 'week_start_day' => $settings->get( 'week_start_day' ),
529
- 'week_view_starts_at' => $settings->get( 'week_view_starts_at' ),
530
- 'week_view_ends_at' => $settings->get( 'week_view_ends_at' ),
531
- 'google_maps_api_key' => $settings->get( 'google_maps_api_key' ),
532
- 'blog_timezone' => $blog_timezone,
533
- 'affix_filter_menu' => $settings->get( 'affix_filter_menu' ),
534
- 'affix_vertical_offset_md' => $settings->get( 'affix_vertical_offset_md' ),
535
- 'affix_vertical_offset_lg' => $settings->get( 'affix_vertical_offset_lg' ),
536
- 'affix_vertical_offset_sm' => $settings->get( 'affix_vertical_offset_sm' ),
537
- 'affix_vertical_offset_xs' => $settings->get( 'affix_vertical_offset_xs' ),
538
- 'calendar_page_id' => $settings->get( 'calendar_page_id' ),
539
- 'region' => ( $settings->get( 'geo_region_biasing' ) ) ? $locale->get_region() : '',
540
- 'site_url' => trailingslashit(
541
- ai1ec_get_site_url()
542
- ),
543
- 'javascript_widgets' => array(),
544
- 'widget_creator' => array(
545
- 'preview' => Ai1ec_I18n::__( 'Preview:' ),
546
- 'preview_loading' => Ai1ec_I18n::__(
547
- 'Loading preview&nbsp;<i class="ai1ec-fa ai1ec-fa-spin ai1ec-fa-spinner"></i>'
548
- )
549
- ),
550
- 'ticketing' => array(
551
- 'details' => Ai1ec_I18n::__( 'Ticketing Details' ),
552
- 'hide_details' => Ai1ec_I18n::__( 'Hide Ticketing Details' ),
553
- 'loading_details' => Ai1ec_I18n::__( 'Loading tickets details...' ),
554
- 'type_and_price' => Ai1ec_I18n::__( 'Type and price' ),
555
- 'info' => Ai1ec_I18n::__( 'Info' ),
556
- 'information' => Ai1ec_I18n::__( 'Information' ),
557
- 'report' => Ai1ec_I18n::__( 'Report' ),
558
- 'sale_dates' => Ai1ec_I18n::__( 'Sale dates' ),
559
- 'limits' => Ai1ec_I18n::__( 'Limits' ),
560
- 'actions' => Ai1ec_I18n::__( 'Actions' ),
561
- 'sold' => Ai1ec_I18n::__( 'Sold:' ),
562
- 'left' => Ai1ec_I18n::__( 'Left:' ),
563
- 'start' => Ai1ec_I18n::__( 'Start:' ),
564
- 'end' => Ai1ec_I18n::__( 'End:' ),
565
- 'min' => Ai1ec_I18n::__( 'Min:' ),
566
- 'max' => Ai1ec_I18n::__( 'Max:' ),
567
- 'attendees' => Ai1ec_I18n::__( 'Attendees' ),
568
- 'hide_attendees' => Ai1ec_I18n::__( 'Hide Attendees' ),
569
- 'attendees_list' => Ai1ec_I18n::__( 'Attendees List' ),
570
- 'guest_name' => Ai1ec_I18n::__( 'Guest Name' ),
571
- 'status' => Ai1ec_I18n::__( 'Status' ),
572
- 'email' => Ai1ec_I18n::__( 'Email' ),
573
- 'no_attendees' => Ai1ec_I18n::__( 'No attendees for this ticket type.' ),
574
- 'edit' => Ai1ec_I18n::__( 'Edit' ),
575
- 'code' => Ai1ec_I18n::__( 'Code' ),
576
- 'unlimited' => Ai1ec_I18n::__( 'Unlimited' ),
577
- 'open_for_sale' => Ai1ec_I18n::__( 'Open for sale' ),
578
- 'no_delete_text' => Ai1ec_I18n::__( 'You have sold tickets for this ticket type. Please change it\'s status to "Canceled" and make refunds to all users that purchased tickets.' ),
579
- 'cancel_message' => Ai1ec_I18n::__( 'You have sold tickets for this ticket type. Please make refunds to all users that purchased tickets' )
580
- ),
581
- 'review' => array(
582
- 'message_sent' => Ai1ec_I18n::__( 'Your message has been sent. Thank you for your feedback.' ),
583
- 'message_error' => Ai1ec_I18n::__( 'Your message has not been sent. Please try again or contact us.' )
584
- ),
585
- 'load_views_error' => Ai1ec_I18n::__(
586
- 'Something went wrong while fetching events.<br>The request status is: %STATUS% <br>The error thrown was: %ERROR%'
587
- ),
588
- 'load_views_error_popup_title' => Ai1ec_I18n::__( 'Response text received from server' ),
589
- 'load_views_error_link_popup' => Ai1ec_I18n::__( 'Click here for technical details' ),
590
- 'cookie_path' => $this->_registry->get(
591
- 'cookie.utility'
592
- )->get_path_for_cookie(),
593
- 'disable_autocompletion' => $settings->get( 'disable_autocompletion' ),
594
- 'end_must_be_after_start' => __( 'The end date can\'t be earlier than the start date.', AI1EC_PLUGIN_NAME ),
595
- 'show_at_least_six_hours' => __( 'For week and day view, you must select an interval of at least 6 hours.', AI1EC_PLUGIN_NAME ),
596
- 'ai1ec_permalinks_enabled' => $application->get( 'permalinks_enabled' ),
597
- );
598
- return apply_filters( 'ai1ec_js_translations', $data );
599
- }
600
-
601
- /**
602
- * Get the array with translated data for the frontend
603
- *
604
- * @return array
605
- */
606
- public function get_frontend_translation_data() {
607
- $data = array(
608
- 'export_url' => AI1EC_EXPORT_URL,
609
- );
610
-
611
- // Replace desired CSS selector with calendar, if selector has been set
612
- $calendar_selector = $this->_settings->get( 'calendar_css_selector' );
613
- if( $calendar_selector ) {
614
- $page = get_post(
615
- $this->_settings->get( 'calendar_page_id' )
616
- );
617
- $data['selector'] = $calendar_selector;
618
- $data['title'] = $page->post_title;
619
- }
620
-
621
- // DEPRECATED: Only still here for backwards compatibility with Ai1ec 1.x.
622
- $data['fonts'] = array();
623
- $fonts_dir = AI1EC_DEFAULT_THEME_URL . 'font_css/';
624
- $data['fonts'][] = array(
625
- 'name' => 'League Gothic',
626
- 'url' => $fonts_dir . 'font-league-gothic.css',
627
- );
628
- $data['fonts'][] = array(
629
- 'name' => 'fontawesome',
630
- 'url' => $fonts_dir . 'font-awesome.css',
631
- );
632
- return $data;
633
- }
634
-
635
- /**
636
- * Echoes the Javascript if not cached.
637
- *
638
- * Echoes the javascript with the correct content.
639
- * Since the content is dinamic, i use the hash function.
640
- *
641
- * @param string $javascript
642
- *
643
- * @return void
644
- */
645
- private function _echo_javascript( $javascript ) {
646
- $conditional_get = new HTTP_ConditionalGet( array(
647
- 'contentHash' => md5( $javascript )
648
- )
649
- );
650
- $conditional_get->sendHeaders();
651
- if ( ! $conditional_get->cacheIsValid ) {
652
- $http_encoder = $this->_registry->get(
653
- 'http.encoder',
654
- array(
655
- 'content' => $javascript,
656
- 'type' => 'text/javascript'
657
- )
658
- );
659
- $compression_level = null;
660
- if ( $this->_registry->get( 'model.settings' )->get( 'disable_gzip_compression' ) ) {
661
- // set the compression level to 0 to disable it.
662
- $compression_level = 0;
663
- }
664
- $http_encoder->encode( $compression_level );
665
- $http_encoder->sendAll();
666
- }
667
- Ai1ec_Http_Response_Helper::stop( 0 );
668
- }
669
-
670
- /**
671
- * Create the config object for requirejs.
672
- *
673
- * @return string
674
- */
675
- public function create_require_js_config_object() {
676
- $js_url = AI1EC_ADMIN_THEME_JS_URL;
677
- $version = AI1EC_VERSION;
678
- $namespace = self::REQUIRE_NAMESPACE;
679
- $config = <<<JSC
680
- $namespace.require.config( {
681
- waitSeconds : 15,
682
- urlArgs : 'ver=$version',
683
- baseUrl : '$js_url'
684
- } );
685
  JSC;
686
- return $config;
687
- }
688
-
689
- /**
690
- * Check if we are in the calendar feeds page
691
- *
692
- * @return boolean TRUE if we are in the calendar feeds page FALSE otherwise
693
- */
694
- public function are_we_on_calendar_feeds_page() {
695
- if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
696
- return FALSE;
697
- }
698
-
699
- $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
700
- $post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : FALSE;
701
- $page = isset( $_GET['page'] ) ? $_GET['page'] : FALSE;
702
- if( $post_type === FALSE || $page === FALSE ) {
703
- return FALSE;
704
- }
705
- $is_calendar_feed_page = $path_details['basename'] === 'edit.php' &&
706
- $post_type === 'ai1ec_event' &&
707
- $page === 'all-in-one-event-calendar-feeds';
708
- return $is_calendar_feed_page;
709
- }
710
-
711
- /**
712
- * Add the link to render the javascript
713
- *
714
- * @param string $page
715
- * @param boolean $backend
716
- *
717
- * @return void
718
- */
719
- public function add_link_to_render_js( $page, $backend ) {
720
- $load_backend_script = 'false';
721
- if ( true === $backend ) {
722
- $load_backend_script = self::TRUE_PARAM;
723
- }
724
- $is_calendar_page = false;
725
- if(
726
- true === is_page( $this->_settings->get( 'calendar_page_id' ) ) ||
727
- self::CALENDAR_PAGE_JS === $page
728
- ) {
729
- $is_calendar_page = self::TRUE_PARAM;
730
- }
731
-
732
- $url = add_query_arg(
733
- array(
734
- // Add the page to load
735
- self::LOAD_JS_PARAMETER => $page,
736
- // If we are in the backend, we must load the common scripts
737
- self::IS_BACKEND_PARAMETER => $load_backend_script,
738
- // If we are on the calendar page we must load the correct option
739
- self::IS_CALENDAR_PAGE => $is_calendar_page,
740
- ),
741
- trailingslashit( ai1ec_get_site_url() )
742
- );
743
- if (
744
- $this->_settings->get( 'cache_dynamic_js' ) &&
745
- $is_calendar_page &&
746
- '1' === $this->_registry->get( 'model.option' )->get( 'calendarjsupdated' ) &&
747
- $this->_registry->get( 'filesystem.checker' )->check_file_exists(
748
- AI1EC_PATH . self::CALENDAR_JS_CACHE_FILE,
749
- true
750
- )
751
- ) {
752
- $url = plugin_dir_url( 'all-in-one-event-calendar/public/js_cache/.' ) . $page;
753
- }
754
-
755
- if ( true === $backend ) {
756
- $this->_scripts_helper->enqueue_script(
757
- self::JS_HANDLE,
758
- $url,
759
- array( 'postbox' ),
760
- true
761
- );
762
- } else {
763
- $this->_scripts_helper->enqueue_script(
764
- self::JS_HANDLE,
765
- $url,
766
- array(),
767
- false
768
- );
769
- }
770
- }
771
-
772
- /**
773
- * check if we are editing an event
774
- *
775
- * @return boolean TRUE if we are editing an event FALSE otherwise
776
- */
777
- private function _are_we_editing_an_event() {
778
- if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
779
- return FALSE;
780
- }
781
-
782
- $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
783
- $post_id = isset( $_GET['post'] ) ? $_GET['post'] : FALSE;
784
- $action = isset( $_GET['action'] ) ? $_GET['action'] : FALSE;
785
- if( $post_id === FALSE || $action === FALSE ) {
786
- return FALSE;
787
- }
788
-
789
- $editing = (
790
- 'post.php' === $path_details['basename'] &&
791
- 'edit' === $action &&
792
- $this->_aco->is_our_post_type( $post_id )
793
- );
794
- return $editing;
795
- }
796
-
797
- /**
798
- * check if we are viewing events list
799
- *
800
- * @return boolean TRUE if we are on the events list FALSE otherwise
801
- */
802
- private function _are_we_managing_events_list() {
803
- if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
804
- return FALSE;
805
- }
806
-
807
- $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
808
- $post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : FALSE;
809
- if ( FALSE === $post_type ) {
810
- return FALSE;
811
- }
812
- $page = isset( $_GET['page'] ) ? $_GET['page'] : '';
813
- $events_list = (
814
- 'edit.php' === $path_details['basename'] &&
815
- 'ai1ec_event' === $post_type &&
816
- ai1ec_is_blank( $page )
817
- );
818
- return $events_list;
819
- }
820
-
821
- /**
822
- * check if we are creating a new event
823
- *
824
- * @return boolean TRUE if we are creating a new event FALSE otherwise
825
- */
826
- private function _are_we_creating_a_new_event() {
827
- if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
828
- return FALSE;
829
- }
830
-
831
- $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
832
- $post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : '';
833
- return $path_details['basename'] === 'post-new.php' &&
834
- $post_type === AI1EC_POST_TYPE;
835
- }
836
-
837
- /**
838
- * Check if we are accessing the settings page
839
- *
840
- * @return boolean TRUE if we are accessing the settings page FALSE otherwise
841
- */
842
- private function _are_we_accessing_the_calendar_settings_page() {
843
- if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
844
- return FALSE;
845
- }
846
-
847
- $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
848
- $page = isset( $_GET['page'] ) ? $_GET['page'] : '';
849
- return $path_details['basename'] === 'edit.php' &&
850
- $page === AI1EC_PLUGIN_NAME . '-settings';
851
- }
852
-
853
- protected function _are_we_creating_widgets() {
854
- if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
855
- return FALSE;
856
- }
857
-
858
- $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
859
- $page = isset( $_GET['page'] ) ? $_GET['page'] : '';
860
- return $path_details['basename'] === 'edit.php' &&
861
- $page === AI1EC_PLUGIN_NAME . '-widget-creator';
862
- }
863
-
864
- protected function _are_we_managing_tickets() {
865
- if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
866
- return FALSE;
867
- }
868
-
869
- $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
870
- $page = isset( $_GET['page'] ) ? $_GET['page'] : '';
871
- return $path_details['basename'] === 'edit.php' &&
872
- $page === AI1EC_PLUGIN_NAME . '-tickets';
873
- }
874
-
875
- /**
876
- * Check if we are editing less variables
877
- *
878
- * @return boolean TRUE if we are accessing a single event page FALSE otherwise
879
- */
880
- private function _are_we_editing_less_variables() {
881
- if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
882
- return FALSE;
883
- }
884
-
885
- $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
886
- $page = isset( $_GET['page'] ) ? $_GET['page'] : '';
887
- return $path_details['basename'] === 'edit.php' && $page === AI1EC_PLUGIN_NAME . '-edit-css';
888
- }
889
-
890
- /**
891
- * Check if we are accessing the events category page
892
- *
893
- * @return boolean TRUE if we are accessing the events category page FALSE otherwise
894
- */
895
- private function _are_we_editing_event_categories() {
896
- if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
897
- return FALSE;
898
- }
899
-
900
- $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
901
- $post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : '';
902
- return (
903
- $path_details['basename'] === 'edit-tags.php' ||
904
- $path_details['basename'] === 'term.php'
905
- ) && $post_type === AI1EC_POST_TYPE;
906
- }
907
-
908
- /**
909
- * Check if we are accessing a single event page
910
- *
911
- * @return boolean TRUE if we are accessing a single event page FALSE otherwise
912
- */
913
- private function _are_we_accessing_the_single_event_page() {
914
- return $this->_aco->is_our_post_type();
915
- }
916
 
917
  }
10
  */
11
  class Ai1ec_Javascript_Controller {
12
 
13
+ // The js handle used when enqueueing
14
+ const JS_HANDLE = 'ai1ec_requirejs';
15
 
16
+ // The namespace for require.js functions
17
+ const REQUIRE_NAMESPACE = 'timely';
18
+
19
+ // the name of the configuration module for the frontend
20
+ const FRONTEND_CONFIG_MODULE = 'ai1ec_calendar';
21
+
22
+ //the name of the get parameter we use for loading js
23
+ const LOAD_JS_PARAMETER = 'ai1ec_render_js';
24
+
25
+ // just load backend scripts
26
+ const LOAD_ONLY_BACKEND_SCRIPTS = 'common_backend';
27
+
28
+ // just load backend scripts
29
+ const LOAD_ONLY_FRONTEND_SCRIPTS = 'common_frontend';
30
+
31
+ // Are we in the backend
32
+ const IS_BACKEND_PARAMETER = 'is_backend';
33
+
34
+ // Are we on the calendar page
35
+ const IS_CALENDAR_PAGE = 'is_calendar_page';
36
+
37
+ // this is the value of IS_BACKEND_PARAMETER which triggers loading of backend script
38
+ const TRUE_PARAM = 'true';
39
+
40
+ // the javascript file for event page
41
+ const EVENT_PAGE_JS = 'event.js';
42
+
43
+ // the javascript file for calendar page
44
+ const CALENDAR_PAGE_JS = 'calendar.js';
45
+
46
+ // the file for the calendar feedsa page
47
+ const CALENDAR_FEEDS_PAGE = 'calendar_feeds.js';
48
+
49
+ // add new event page js
50
+ const ADD_NEW_EVENT_PAGE = 'add_new_event.js';
51
+
52
+ // event category page js
53
+ const EVENT_CATEGORY_PAGE = 'event_category.js';
54
+
55
+ // less variable editing page
56
+ const LESS_VARIBALES_PAGE = 'less_variables_editing.js';
57
+
58
+ // settings page
59
+ const SETTINGS_PAGE = 'admin_settings.js';
60
+
61
+ //widget creator page
62
+ const WIDGET_CREATOR = 'widget-creator.js';
63
+
64
+ //ticketing page
65
+ const TICKETING = 'ticketing.js';
66
+
67
+ //cache file
68
+ const CALENDAR_JS_CACHE_FILE = '/public/js_cache/calendar.js';
69
+
70
+ /**
71
+ * @var Ai1ec_Registry_Object
72
+ */
73
+ private $_registry;
74
+
75
+ /**
76
+ * The core js pages to load.
77
+ * Used to avoid errors when extensions add pages.
78
+ *
79
+ * @var array
80
+ */
81
+ private $_core_pages = array(
82
+ self::CALENDAR_FEEDS_PAGE => true,
83
+ self::ADD_NEW_EVENT_PAGE => true,
84
+ self::EVENT_CATEGORY_PAGE => true,
85
+ self::LESS_VARIBALES_PAGE => true,
86
+ self::SETTINGS_PAGE => true,
87
+ self::EVENT_PAGE_JS => true,
88
+ self::CALENDAR_PAGE_JS => true,
89
+ self::WIDGET_CREATOR => true,
90
+ self::TICKETING => true,
91
+ );
92
+
93
+ /**
94
+ * Holds an instance of the settings object
95
+ *
96
+ * @var Ai1ec_Settings
97
+ */
98
+ private $_settings;
99
+
100
+ /**
101
+ * @var Ai1ec_Locale
102
+ */
103
+ private $_locale;
104
+
105
+ /**
106
+ * @var Ai1ec_Scripts
107
+ */
108
+ private $_scripts_helper;
109
+
110
+ /**
111
+ * @var Ai1ec_Acl_Aco
112
+ */
113
+ private $_aco;
114
+
115
+ /**
116
+ * @var Ai1ec_Template_Link_Helper
117
+ */
118
+ private $_template_link_helper;
119
+
120
+ /**
121
+ * @var bool
122
+ */
123
+ protected $_frontend_scripts_loaded = false;
124
+
125
+ /**
126
+ * Public constructor.
127
+ *
128
+ * @param Ai1ec_Registry_Object $registry
129
+ *
130
+ * @return void
131
+ */
132
+ public function __construct( Ai1ec_Registry_Object $registry ) {
133
+ $this->_registry = $registry;
134
+ $this->_settings = $registry->get( 'model.settings' );
135
+ $this->_locale = $registry->get( 'p28n.wpml' );
136
+ $this->_aco = $registry->get( 'acl.aco' );
137
+ $this->_template_link_helper = $registry->get( 'template.link.helper' );
138
+ // this will need to be modified
139
+ $this->_scripts_helper = $registry->get( 'script.helper' );
140
+ }
141
+
142
+ /**
143
+ * Load javascript files for frontend pages.
144
+ *
145
+ * @wp-hook ai1ec_load_frontend_js
146
+ *
147
+ * @param $is_calendar_page boolean Whether we are displaying the main
148
+ * calendar page or not
149
+ *
150
+ * @return void
151
+ */
152
+ public function load_frontend_js( $is_calendar_page, $is_shortcode = false ) {
153
+ $page = null;
154
+
155
+ // ======
156
+ // = JS =
157
+ // ======
158
+ if( $this->_are_we_accessing_the_single_event_page() === true ) {
159
+ $page = self::EVENT_PAGE_JS;
160
+ }
161
+ if( $is_calendar_page === true ) {
162
+ $page = self::CALENDAR_PAGE_JS;
163
+ }
164
+ if( null !== $page ) {
165
+ $this->add_link_to_render_js( $page, false );
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Render the javascript for the appropriate page.
171
+ *
172
+ * @return void
173
+ */
174
+ public function render_js() {
175
+ $js_path = AI1EC_ADMIN_THEME_JS_PATH . DIRECTORY_SEPARATOR;
176
+ $common_js = '';
177
+ $js_cache = $this->_settings->get( 'cache_dynamic_js' );
178
+
179
+ if ( ! isset( $_GET[self::LOAD_JS_PARAMETER] ) ) {
180
+ return null;
181
+ }
182
+ $page_to_load = $_GET[self::LOAD_JS_PARAMETER];
183
+ $scripts_updated = $this->_registry->get( 'model.option' )->get( 'calendarjsupdated' );
184
+
185
+ if (
186
+ $js_cache &&
187
+ $page_to_load === self::CALENDAR_PAGE_JS &&
188
+ '1' === $scripts_updated &&
189
+ $this->_registry->get( 'filesystem.checker' )->check_file_exists(
190
+ AI1EC_PATH . self::CALENDAR_JS_CACHE_FILE,
191
+ true
192
+ )
193
+ ) {
194
+ Ai1ec_Http_Response_Helper::stop( 0 );
195
+ return;
196
+ }
197
+
198
+ if (
199
+ isset( $_GET[self::IS_BACKEND_PARAMETER] ) &&
200
+ $_GET[self::IS_BACKEND_PARAMETER] === self::TRUE_PARAM
201
+ ) {
202
+ $common_js = file_get_contents( $js_path . 'pages/common_backend.js' );
203
+ } else if (
204
+ $page_to_load === self::EVENT_PAGE_JS ||
205
+ $page_to_load === self::CALENDAR_PAGE_JS ||
206
+ $page_to_load === self::LOAD_ONLY_FRONTEND_SCRIPTS
207
+ ) {
208
+ if (
209
+ $page_to_load === self::LOAD_ONLY_FRONTEND_SCRIPTS &&
210
+ true === $this->_frontend_scripts_loaded
211
+ ) {
212
+ return;
213
+ }
214
+ if ( false === $this->_frontend_scripts_loaded ) {
215
+ $common_js = file_get_contents(
216
+ $js_path . 'pages/common_frontend.js'
217
+ );
218
+ $this->_frontend_scripts_loaded = true;
219
+ }
220
+ }
221
+
222
+ // Create the config object for Require.js.
223
+ $require_config = $this->create_require_js_config_object();
224
+
225
+ // Load Require.js script.
226
+ $require = file_get_contents( $js_path . 'require.js' );
227
+
228
+ // Load appropriate jQuery script based on browser.
229
+ $jquery = $this->get_jquery_version_based_on_browser(
230
+ isset( $_SERVER['HTTP_USER_AGENT'] )
231
+ ? $_SERVER['HTTP_USER_AGENT']
232
+ : ''
233
+ );
234
+
235
+ // Load the main script for the page.
236
+ $page_js = '';
237
+ if ( isset( $this->_core_pages[$page_to_load] ) ) {
238
+ $page_js = file_get_contents( $js_path . 'pages/' . $page_to_load );
239
+ }
240
+
241
+ // Load translation module.
242
+ $translation = $this->get_frontend_translation_data();
243
+ $permalink = $this->_template_link_helper
244
+ ->get_permalink( $this->_settings->get( 'calendar_page_id' ) );
245
+ $full_permalink = $this->_template_link_helper
246
+ ->get_full_permalink( $this->_settings->get( 'calendar_page_id' ) );
247
+ $translation['calendar_url'] = $permalink;
248
+ $translation['full_calendar_url'] = $full_permalink;
249
+ $translation_module = $this->create_require_js_module(
250
+ self::FRONTEND_CONFIG_MODULE,
251
+ $translation
252
+ );
253
+
254
+ // Load Ai1ec config script.
255
+ $config = $this->create_require_js_module(
256
+ 'ai1ec_config',
257
+ $this->get_translation_data()
258
+ );
259
+
260
+ // Let extensions add their scripts.
261
+ $extension_files = array();
262
+ $extension_files = apply_filters(
263
+ 'ai1ec_render_js',
264
+ $extension_files,
265
+ $page_to_load
266
+ );
267
+ $ext_js = '';
268
+
269
+ foreach ( $extension_files as $file ) {
270
+ $ext_js .= file_get_contents( $file );
271
+ }
272
+
273
+ // Finally, load the page_ready script to execute code that must run after
274
+ // all scripts have been loaded.
275
+ $page_ready = file_get_contents(
276
+ $js_path . 'scripts/common_scripts/page_ready.js'
277
+ );
278
+
279
+ $javascript = $require . $require_config . $translation_module .
280
+ $config . $jquery . $common_js . $ext_js . $page_js . $page_ready;
281
+ // add to blank spaces to fix issues with js
282
+ // being truncated onn some installs
283
+ $javascript .= ' ';
284
+
285
+ if (
286
+ $js_cache &&
287
+ $page_to_load === self::CALENDAR_PAGE_JS &&
288
+ (
289
+ '0' === $scripts_updated ||
290
+ ! $this->_registry->get( 'filesystem.checker' )->check_file_exists(
291
+ AI1EC_PATH . self::CALENDAR_JS_CACHE_FILE,
292
+ true
293
+ )
294
+ )
295
+ ) {
296
+ $js_saved = false;
297
+ try {
298
+ $js_saved = file_put_contents(
299
+ $js_path . '../js_cache/' . self::CALENDAR_PAGE_JS,
300
+ $javascript
301
+ );
302
+ if ( $js_saved ) {
303
+ $this->_registry->get( 'model.option' )->set( 'calendarjsupdated', '1' );
304
+ }
305
+ } catch ( Exception $e ) {
306
+ $this->_settings->set( 'cache_dynamic_js', false );
307
+ }
308
+ }
309
+
310
+ $this->_echo_javascript( $javascript );
311
+ }
312
+
313
+
314
+ /**
315
+ * Sets the flag to revalidate cached js files on next render.
316
+ */
317
+ public function revalidate_cache() {
318
+ $this->_registry->get( 'model.option' )->set( 'calendarjsupdated', '0' );
319
+ }
320
+
321
+ /**
322
+ * Get a compiled javascript file ( used by extensions )
323
+ *
324
+ * @param string $name
325
+ *
326
+ * @return string
327
+ */
328
+ public function get_module( $name ) {
329
+ $js_path = AI1EC_ADMIN_THEME_JS_PATH . DIRECTORY_SEPARATOR;
330
+ return file_get_contents( $js_path . $name );
331
+ }
332
+
333
+ /**
334
+ * Check what file needs to be loaded and add the correct link.
335
+ *
336
+ * @wp-hook init
337
+ *
338
+ * @return void
339
+ */
340
+ public function load_admin_js() {
341
+ // Initialize dashboard view
342
+
343
+ $script_to_load = FALSE;
344
+ if ( $this->are_we_on_calendar_feeds_page() === TRUE ) {
345
+ // Load script for the importer plugins
346
+ $script_to_load[] = self::CALENDAR_FEEDS_PAGE;
347
+ }
348
+ // Start the scripts for the event category page
349
+ if ( $this->_are_we_editing_event_categories() === TRUE ) {
350
+ // Load script required when editing categories
351
+ $script_to_load[] = self::EVENT_CATEGORY_PAGE;
352
+ }
353
+ if ( $this->_are_we_editing_less_variables() === TRUE ) {
354
+ // Load script required when editing categories
355
+ $script_to_load[] = self::LESS_VARIBALES_PAGE;
356
+ }
357
+ // Load the js needed when you edit an event / add a new event
358
+ if (
359
+ true === $this->_are_we_creating_a_new_event() ||
360
+ true === $this->_are_we_editing_an_event()
361
+ ) {
362
+ // Load script for adding / modifying events
363
+ $script_to_load[] = self::ADD_NEW_EVENT_PAGE;
364
+ }
365
+ if ( true === $this->_are_we_accessing_the_calendar_settings_page() ) {
366
+ $script_to_load[] = self::SETTINGS_PAGE;
367
+ }
368
+ if ( true === $this->_are_we_creating_widgets() ) {
369
+ $script_to_load[] = self::WIDGET_CREATOR;
370
+ }
371
+ if (
372
+ true === $this->_are_we_managing_tickets() ||
373
+ true === $this->_are_we_managing_events_list()
374
+ ) {
375
+ $script_to_load[] = self::TICKETING;
376
+ }
377
+ if ( false === $script_to_load ) {
378
+ $script_to_load[] = apply_filters( 'ai1ec_backend_js', self::LOAD_ONLY_BACKEND_SCRIPTS );
379
+ }
380
+ foreach ($script_to_load as $value) {
381
+ $this->add_link_to_render_js( $value, true );
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Loads version 1.9 or 2.0 of jQuery based on user agent.
387
+ * If $user_agent is null (due to lack of HTTP header) we always serve
388
+ * jQuery 2.0.
389
+ *
390
+ * @param string $user_agent
391
+ *
392
+ * @return string
393
+ */
394
+ public function get_jquery_version_based_on_browser( $user_agent ) {
395
+ $js_path = AI1EC_ADMIN_THEME_JS_PATH . DIRECTORY_SEPARATOR;
396
+ $jquery = 'jquery_timely20.js';
397
+
398
+ preg_match( '/MSIE (.*?);/', $user_agent, $matches );
399
+ if ( count( $matches ) > 1 ) {
400
+ //Then we're using IE
401
+ $version = (int) $matches[1];
402
+ if ( $version <= 8 ) {
403
+ //IE 8 or under!
404
+ $jquery = 'jquery_timely19.js';
405
+ }
406
+ }
407
+ return file_get_contents( $js_path . $jquery );
408
+ }
409
+
410
+ /**
411
+ * Creates a requirejs module that can be used for translations
412
+ *
413
+ * @param string $object_name
414
+ * @param array $data
415
+ *
416
+ * @return string
417
+ */
418
+ public function create_require_js_module( $object_name, array $data ) {
419
+ foreach ( (array) $data as $key => $value ) {
420
+ if ( ! is_scalar( $value ) )
421
+ continue;
422
+ $data[$key] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8');
423
+ }
424
+ $json_data = json_encode( $data );
425
+ $prefix = self::REQUIRE_NAMESPACE;
426
+ $script = "$prefix.define( '$object_name', $json_data );";
427
+
428
+ return $script;
429
+ }
430
+
431
+ /**
432
+ * Create the array needed for translation and passing other settings to JS.
433
+ *
434
+ * @return $data array the dynamic data array
435
+ */
436
+ public function get_translation_data() {
437
+
438
+ $force_ssl_admin = force_ssl_admin();
439
+ if ( $force_ssl_admin && ! is_ssl() ) {
440
+ force_ssl_admin( false );
441
+ }
442
+ $ajax_url = ai1ec_admin_url( 'admin-ajax.php' );
443
+ force_ssl_admin( $force_ssl_admin );
444
+ $settings = $this->_registry->get( 'model.settings' );
445
+ $locale = $this->_registry->get( 'p28n.wpml' );
446
+ $blog_timezone = $this->_registry->get( 'model.option' )
447
+ ->get( 'gmt_offset' );
448
+ $application = $this->_registry->get( 'bootstrap.registry.application' );
449
+ $data = array(
450
+ 'calendar_feeds_nonce' => wp_create_nonce( 'ai1ec_ics_feed_nonce'),
451
+ // ICS feed error messages
452
+ 'duplicate_feed_message' => esc_html(
453
+ Ai1ec_I18n::__( 'This feed is already being imported.' )
454
+ ),
455
+ 'invalid_url_message' => esc_html(
456
+ Ai1ec_I18n::__( 'Please enter a valid iCalendar URL.' )
457
+ ),
458
+ 'invalid_website_message' => esc_html(
459
+ Ai1ec_I18n::__( 'Please enter a valid Website URL.' )
460
+ ),
461
+ 'invalid_registration_message' => esc_html(
462
+ Ai1ec_I18n::__( 'Please enter a valid Registration URL, starting with https:// or http://.' )
463
+ ),
464
+ 'invalid_email_message' => esc_html(
465
+ Ai1ec_I18n::__( 'Please enter a valid email address.' )
466
+ ),
467
+ 'choose_image_message' => Ai1ec_I18n::__( 'Choose Image' ),
468
+ 'now' => $this->_registry->get( 'date.system' )
469
+ ->current_time(),
470
+ 'size_less_variable_not_ok' => Ai1ec_I18n::__(
471
+ 'The value you have entered is not a valid CSS length.'
472
+ ),
473
+ 'confirm_reset_theme' => Ai1ec_I18n::__(
474
+ 'Are you sure you want to reset your theme options to their default values?'
475
+ ),
476
+ 'error_message_not_valid_lat' => Ai1ec_I18n::__(
477
+ 'Please enter a valid latitude. A valid latitude is comprised between +90 and -90.'
478
+ ),
479
+ 'error_message_not_valid_long' => Ai1ec_I18n::__(
480
+ 'Please enter a valid longitude. A valid longitude is comprised between +180 and -180.'
481
+ ),
482
+ 'error_message_not_entered_lat' => Ai1ec_I18n::__(
483
+ 'When the "Input coordinates" checkbox is checked, "Latitude" is a required field.'
484
+ ),
485
+ 'error_message_not_entered_long' => Ai1ec_I18n::__(
486
+ 'When the "Input coordinates" checkbox is checked, "Longitude" is a required field.'
487
+ ),
488
+ 'ai1ec_contact_url_not_valid' => Ai1ec_I18n::__(
489
+ 'The URL you have entered in the <b>Organizer Contact Info</b> &gt; <b>Website URL</b> seems to be invalid.'
490
+ ),
491
+ 'ai1ec_ticket_ext_url_not_valid' => Ai1ec_I18n::__(
492
+ 'The URL you have entered in the <b>Event Cost and Tickets</b> &gt; <b>Tickets or Registration URL</b> seems to be invalid.'
493
+ ),
494
+ 'ai1ec_contact_email_not_valid' => Ai1ec_I18n::__(
495
+ 'The Email you have entered in the <b>Organizer Contact Info</b> &gt; <b>E-mail</b> seems to be invalid.'
496
+ ),
497
+ 'general_url_not_valid' => Ai1ec_I18n::__(
498
+ 'Please remember that URLs must start with either "http://" or "https://".'
499
+ ),
500
+ 'calendar_loading' => Ai1ec_I18n::__(
501
+ 'Loading&hellip;'
502
+ ),
503
+ 'ticketing_required_fields' => Ai1ec_I18n::__(
504
+ '<b>Required or incorrect fields for Ticketing are outlined red.</b>'
505
+ ),
506
+ 'ticketing_repeat_not_supported' => Ai1ec_I18n::__( '<b>The Repeat option was selected but recurrence is not supported by Event with Tickets.</b>'
507
+ ),
508
+ 'ticketing_no_tickets_included' => Ai1ec_I18n::__( '<b>
509
+ The Event has the cost option Tickets selected but no ticket was included.</b>'
510
+ ),
511
+ 'discovery_event_success' => Ai1ec_I18n::__(
512
+ 'Event was imported successfully.'
513
+ ),
514
+ 'discovery_event_error' => Ai1ec_I18n::__(
515
+ 'An error occurred when importing event. Please, try later.'
516
+ ),
517
+ 'language' => $this->_registry->get( 'p28n.wpml' )->get_lang(),
518
+ 'ajax_url' => $ajax_url,
519
+ // 24h time format for time pickers
520
+ 'twentyfour_hour' => $settings->get( 'input_24h_time' ),
521
+ // Date format for date pickers
522
+ 'date_format' => $settings->get( 'input_date_format' ),
523
+ // Names for months in date picker header (escaping is done in wp_localize_script)
524
+ 'month_names' => $locale->get_localized_month_names(),
525
+ // Names for days in date picker header (escaping is done in wp_localize_script)
526
+ 'day_names' => $locale->get_localized_week_names(),
527
+ // Start the week on this day in the date picker
528
+ 'week_start_day' => $settings->get( 'week_start_day' ),
529
+ 'week_view_starts_at' => $settings->get( 'week_view_starts_at' ),
530
+ 'week_view_ends_at' => $settings->get( 'week_view_ends_at' ),
531
+ 'google_maps_api_key' => $settings->get( 'google_maps_api_key' ),
532
+ 'blog_timezone' => $blog_timezone,
533
+ 'affix_filter_menu' => $settings->get( 'affix_filter_menu' ),
534
+ 'affix_vertical_offset_md' => $settings->get( 'affix_vertical_offset_md' ),
535
+ 'affix_vertical_offset_lg' => $settings->get( 'affix_vertical_offset_lg' ),
536
+ 'affix_vertical_offset_sm' => $settings->get( 'affix_vertical_offset_sm' ),
537
+ 'affix_vertical_offset_xs' => $settings->get( 'affix_vertical_offset_xs' ),
538
+ 'calendar_page_id' => $settings->get( 'calendar_page_id' ),
539
+ 'region' => ( $settings->get( 'geo_region_biasing' ) ) ? $locale->get_region() : '',
540
+ 'site_url' => trailingslashit(
541
+ ai1ec_get_site_url()
542
+ ),
543
+ 'javascript_widgets' => array(),
544
+ 'widget_creator' => array(
545
+ 'preview' => Ai1ec_I18n::__( 'Preview:' ),
546
+ 'preview_loading' => Ai1ec_I18n::__(
547
+ 'Loading preview&nbsp;<i class="ai1ec-fa ai1ec-fa-spin ai1ec-fa-spinner"></i>'
548
+ )
549
+ ),
550
+ 'ticketing' => array(
551
+ 'details' => Ai1ec_I18n::__( 'Ticketing Details' ),
552
+ 'hide_details' => Ai1ec_I18n::__( 'Hide Ticketing Details' ),
553
+ 'loading_details' => Ai1ec_I18n::__( 'Loading tickets details...' ),
554
+ 'type_and_price' => Ai1ec_I18n::__( 'Type and price' ),
555
+ 'info' => Ai1ec_I18n::__( 'Info' ),
556
+ 'information' => Ai1ec_I18n::__( 'Information' ),
557
+ 'report' => Ai1ec_I18n::__( 'Report' ),
558
+ 'sale_dates' => Ai1ec_I18n::__( 'Sale dates' ),
559
+ 'limits' => Ai1ec_I18n::__( 'Limits' ),
560
+ 'actions' => Ai1ec_I18n::__( 'Actions' ),
561
+ 'sold' => Ai1ec_I18n::__( 'Sold:' ),
562
+ 'left' => Ai1ec_I18n::__( 'Left:' ),
563
+ 'start' => Ai1ec_I18n::__( 'Start:' ),
564
+ 'end' => Ai1ec_I18n::__( 'End:' ),
565
+ 'min' => Ai1ec_I18n::__( 'Min:' ),
566
+ 'max' => Ai1ec_I18n::__( 'Max:' ),
567
+ 'attendees' => Ai1ec_I18n::__( 'Attendees' ),
568
+ 'hide_attendees' => Ai1ec_I18n::__( 'Hide Attendees' ),
569
+ 'attendees_list' => Ai1ec_I18n::__( 'Attendees List' ),
570
+ 'guest_name' => Ai1ec_I18n::__( 'Guest Name' ),
571
+ 'status' => Ai1ec_I18n::__( 'Status' ),
572
+ 'email' => Ai1ec_I18n::__( 'Email' ),
573
+ 'no_attendees' => Ai1ec_I18n::__( 'No attendees for this ticket type.' ),
574
+ 'edit' => Ai1ec_I18n::__( 'Edit' ),
575
+ 'code' => Ai1ec_I18n::__( 'Code' ),
576
+ 'unlimited' => Ai1ec_I18n::__( 'Unlimited' ),
577
+ 'open_for_sale' => Ai1ec_I18n::__( 'Open for sale' ),
578
+ 'no_delete_text' => Ai1ec_I18n::__( 'You have sold tickets for this ticket type. Please change it\'s status to "Canceled" and make refunds to all users that purchased tickets.' ),
579
+ 'cancel_message' => Ai1ec_I18n::__( 'You have sold tickets for this ticket type. Please make refunds to all users that purchased tickets' )
580
+ ),
581
+ 'review' => array(
582
+ 'message_sent' => Ai1ec_I18n::__( 'Your message has been sent. Thank you for your feedback.' ),
583
+ 'message_error' => Ai1ec_I18n::__( 'Your message has not been sent. Please try again or contact us.' )
584
+ ),
585
+ 'load_views_error' => Ai1ec_I18n::__(
586
+ 'Something went wrong while fetching events.<br>The request status is: %STATUS% <br>The error thrown was: %ERROR%'
587
+ ),
588
+ 'load_views_error_popup_title' => Ai1ec_I18n::__( 'Response text received from server' ),
589
+ 'load_views_error_link_popup' => Ai1ec_I18n::__( 'Click here for technical details' ),
590
+ 'cookie_path' => $this->_registry->get(
591
+ 'cookie.utility'
592
+ )->get_path_for_cookie(),
593
+ 'disable_autocompletion' => $settings->get( 'disable_autocompletion' ),
594
+ 'end_must_be_after_start' => __( 'The end date can\'t be earlier than the start date.', AI1EC_PLUGIN_NAME ),
595
+ 'show_at_least_six_hours' => __( 'For week and day view, you must select an interval of at least 6 hours.', AI1EC_PLUGIN_NAME ),
596
+ 'ai1ec_permalinks_enabled' => $application->get( 'permalinks_enabled' ),
597
+ );
598
+ return apply_filters( 'ai1ec_js_translations', $data );
599
+ }
600
+
601
+ /**
602
+ * Get the array with translated data for the frontend
603
+ *
604
+ * @return array
605
+ */
606
+ public function get_frontend_translation_data() {
607
+ $data = array(
608
+ 'export_url' => AI1EC_EXPORT_URL,
609
+ );
610
+
611
+ // Replace desired CSS selector with calendar, if selector has been set
612
+ $calendar_selector = $this->_settings->get( 'calendar_css_selector' );
613
+ if( $calendar_selector ) {
614
+ $page = get_post(
615
+ $this->_settings->get( 'calendar_page_id' )
616
+ );
617
+ $data['selector'] = $calendar_selector;
618
+ $data['title'] = $page->post_title;
619
+ }
620
+
621
+ // DEPRECATED: Only still here for backwards compatibility with Ai1ec 1.x.
622
+ $data['fonts'] = array();
623
+ $fonts_dir = AI1EC_DEFAULT_THEME_URL . 'font_css/';
624
+ $data['fonts'][] = array(
625
+ 'name' => 'League Gothic',
626
+ 'url' => $fonts_dir . 'font-league-gothic.css',
627
+ );
628
+ $data['fonts'][] = array(
629
+ 'name' => 'fontawesome',
630
+ 'url' => $fonts_dir . 'font-awesome.css',
631
+ );
632
+ return $data;
633
+ }
634
+
635
+ /**
636
+ * Echoes the Javascript if not cached.
637
+ *
638
+ * Echoes the javascript with the correct content.
639
+ * Since the content is dinamic, i use the hash function.
640
+ *
641
+ * @param string $javascript
642
+ *
643
+ * @return void
644
+ */
645
+ private function _echo_javascript( $javascript ) {
646
+ $conditional_get = new HTTP_ConditionalGet( array(
647
+ 'contentHash' => md5( $javascript )
648
+ )
649
+ );
650
+ $conditional_get->sendHeaders();
651
+ if ( ! $conditional_get->cacheIsValid ) {
652
+ $http_encoder = $this->_registry->get(
653
+ 'http.encoder',
654
+ array(
655
+ 'content' => $javascript,
656
+ 'type' => 'text/javascript'
657
+ )
658
+ );
659
+ $compression_level = null;
660
+ if ( $this->_registry->get( 'model.settings' )->get( 'disable_gzip_compression' ) ) {
661
+ // set the compression level to 0 to disable it.
662
+ $compression_level = 0;
663
+ }
664
+ $http_encoder->encode( $compression_level );
665
+ $http_encoder->sendAll();
666
+ }
667
+ Ai1ec_Http_Response_Helper::stop( 0 );
668
+ }
669
+
670
+ /**
671
+ * Create the config object for requirejs.
672
+ *
673
+ * @return string
674
+ */
675
+ public function create_require_js_config_object() {
676
+ $js_url = AI1EC_ADMIN_THEME_JS_URL;
677
+ $version = AI1EC_VERSION;
678
+ $namespace = self::REQUIRE_NAMESPACE;
679
+ $config = <<<JSC
680
+ $namespace.require.config( {
681
+ waitSeconds : 15,
682
+ urlArgs : 'ver=$version',
683
+ baseUrl : '$js_url'
684
+ } );
685
  JSC;
686
+ return $config;
687
+ }
688
+
689
+ /**
690
+ * Check if we are in the calendar feeds page
691
+ *
692
+ * @return boolean TRUE if we are in the calendar feeds page FALSE otherwise
693
+ */
694
+ public function are_we_on_calendar_feeds_page() {
695
+ if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
696
+ return FALSE;
697
+ }
698
+
699
+ $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
700
+ $post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : FALSE;
701
+ $page = isset( $_GET['page'] ) ? $_GET['page'] : FALSE;
702
+ if( $post_type === FALSE || $page === FALSE ) {
703
+ return FALSE;
704
+ }
705
+ $is_calendar_feed_page = $path_details['basename'] === 'edit.php' &&
706
+ $post_type === 'ai1ec_event' &&
707
+ $page === 'all-in-one-event-calendar-feeds';
708
+ return $is_calendar_feed_page;
709
+ }
710
+
711
+ /**
712
+ * Add the link to render the javascript
713
+ *
714
+ * @param string $page
715
+ * @param boolean $backend
716
+ *
717
+ * @return void
718
+ */
719
+ public function add_link_to_render_js( $page, $backend ) {
720
+ $load_backend_script = 'false';
721
+ if ( true === $backend ) {
722
+ $load_backend_script = self::TRUE_PARAM;
723
+ }
724
+ $is_calendar_page = false;
725
+ if(
726
+ true === is_page( $this->_settings->get( 'calendar_page_id' ) ) ||
727
+ self::CALENDAR_PAGE_JS === $page
728
+ ) {
729
+ $is_calendar_page = self::TRUE_PARAM;
730
+ }
731
+
732
+ $url = add_query_arg(
733
+ array(
734
+ // Add the page to load
735
+ self::LOAD_JS_PARAMETER => $page,
736
+ // If we are in the backend, we must load the common scripts
737
+ self::IS_BACKEND_PARAMETER => $load_backend_script,
738
+ // If we are on the calendar page we must load the correct option
739
+ self::IS_CALENDAR_PAGE => $is_calendar_page,
740
+ ),
741
+ trailingslashit( ai1ec_get_site_url() )
742
+ );
743
+ if (
744
+ $this->_settings->get( 'cache_dynamic_js' ) &&
745
+ $is_calendar_page &&
746
+ '1' === $this->_registry->get( 'model.option' )->get( 'calendarjsupdated' ) &&
747
+ $this->_registry->get( 'filesystem.checker' )->check_file_exists(
748
+ AI1EC_PATH . self::CALENDAR_JS_CACHE_FILE,
749
+ true
750
+ )
751
+ ) {
752
+ $url = plugin_dir_url( 'all-in-one-event-calendar/public/js_cache/.' ) . $page;
753
+ }
754
+
755
+ if ( true === $backend ) {
756
+ $this->_scripts_helper->enqueue_script(
757
+ self::JS_HANDLE,
758
+ $url,
759
+ array( 'postbox' ),
760
+ true
761
+ );
762
+ } else {
763
+ $this->_scripts_helper->enqueue_script(
764
+ self::JS_HANDLE,
765
+ $url,
766
+ array(),
767
+ false
768
+ );
769
+ }
770
+ }
771
+
772
+ /**
773
+ * check if we are editing an event
774
+ *
775
+ * @return boolean TRUE if we are editing an event FALSE otherwise
776
+ */
777
+ private function _are_we_editing_an_event() {
778
+ if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
779
+ return FALSE;
780
+ }
781
+
782
+ $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
783
+ $post_id = isset( $_GET['post'] ) ? $_GET['post'] : FALSE;
784
+ $action = isset( $_GET['action'] ) ? $_GET['action'] : FALSE;
785
+ if( $post_id === FALSE || $action === FALSE ) {
786
+ return FALSE;
787
+ }
788
+
789
+ $editing = (
790
+ 'post.php' === $path_details['basename'] &&
791
+ 'edit' === $action &&
792
+ $this->_aco->is_our_post_type( $post_id )
793
+ );
794
+ return $editing;
795
+ }
796
+
797
+ /**
798
+ * check if we are viewing events list
799
+ *
800
+ * @return boolean TRUE if we are on the events list FALSE otherwise
801
+ */
802
+ private function _are_we_managing_events_list() {
803
+ if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
804
+ return FALSE;
805
+ }
806
+
807
+ $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
808
+ $post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : FALSE;
809
+ if ( FALSE === $post_type ) {
810
+ return FALSE;
811
+ }
812
+ $page = isset( $_GET['page'] ) ? $_GET['page'] : '';
813
+ $events_list = (
814
+ 'edit.php' === $path_details['basename'] &&
815
+ 'ai1ec_event' === $post_type &&
816
+ ai1ec_is_blank( $page )
817
+ );
818
+ return $events_list;
819
+ }
820
+
821
+ /**
822
+ * check if we are creating a new event
823
+ *
824
+ * @return boolean TRUE if we are creating a new event FALSE otherwise
825
+ */
826
+ private function _are_we_creating_a_new_event() {
827
+ if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
828
+ return FALSE;
829
+ }
830
+
831
+ $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
832
+ $post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : '';
833
+ return $path_details['basename'] === 'post-new.php' &&
834
+ $post_type === AI1EC_POST_TYPE;
835
+ }
836
+
837
+ /**
838
+ * Check if we are accessing the settings page
839
+ *
840
+ * @return boolean TRUE if we are accessing the settings page FALSE otherwise
841
+ */
842
+ private function _are_we_accessing_the_calendar_settings_page() {
843
+ if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
844
+ return FALSE;
845
+ }
846
+
847
+ $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
848
+ $page = isset( $_GET['page'] ) ? $_GET['page'] : '';
849
+ return $path_details['basename'] === 'edit.php' &&
850
+ $page === AI1EC_PLUGIN_NAME . '-settings';
851
+ }
852
+
853
+ protected function _are_we_creating_widgets() {
854
+ if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
855
+ return FALSE;
856
+ }
857
+
858
+ $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
859
+ $page = isset( $_GET['page'] ) ? $_GET['page'] : '';
860
+ return $path_details['basename'] === 'edit.php' &&
861
+ $page === AI1EC_PLUGIN_NAME . '-widget-creator';
862
+ }
863
+
864
+ protected function _are_we_managing_tickets() {
865
+ if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
866
+ return FALSE;
867
+ }
868
+
869
+ $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
870
+ $page = isset( $_GET['page'] ) ? $_GET['page'] : '';
871
+ return $path_details['basename'] === 'edit.php' &&
872
+ $page === AI1EC_PLUGIN_NAME . '-tickets';
873
+ }
874
+
875
+ /**
876
+ * Check if we are editing less variables
877
+ *
878
+ * @return boolean TRUE if we are accessing a single event page FALSE otherwise
879
+ */
880
+ private function _are_we_editing_less_variables() {
881
+ if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
882
+ return FALSE;
883
+ }
884
+
885
+ $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
886
+ $page = isset( $_GET['page'] ) ? $_GET['page'] : '';
887
+ return $path_details['basename'] === 'edit.php' && $page === AI1EC_PLUGIN_NAME . '-edit-css';
888
+ }
889
+
890
+ /**
891
+ * Check if we are accessing the events category page
892
+ *
893
+ * @return boolean TRUE if we are accessing the events category page FALSE otherwise
894
+ */
895
+ private function _are_we_editing_event_categories() {
896
+ if ( !isset( $_SERVER['SCRIPT_NAME'] ) ) {
897
+ return FALSE;
898
+ }
899
+
900
+ $path_details = pathinfo( $_SERVER['SCRIPT_NAME'] );
901
+ $post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : '';
902
+ return (
903
+ $path_details['basename'] === 'edit-tags.php' ||
904
+ $path_details['basename'] === 'term.php'
905
+ ) && $post_type === AI1EC_POST_TYPE;
906
+ }
907
+
908
+ /**
909
+ * Check if we are accessing a single event page
910
+ *
911
+ * @return boolean TRUE if we are accessing a single event page FALSE otherwise
912
+ */
913
+ private function _are_we_accessing_the_single_event_page() {
914
+ return $this->_aco->is_our_post_type();
915
+ }
916
 
917
  }
app/controller/shutdown.php CHANGED
@@ -11,95 +11,95 @@
11
  */
12
  class Ai1ec_Shutdown_Controller {
13
 
14
- /**
15
- * @var array Map of object names and class names they represent to preserve
16
- */
17
- protected $_preserve = array(
18
- 'wpdb' => 'wpdb',
19
- 'wp_object_cache' => 'WP_Object_Cache',
20
- );
21
 
22
- /**
23
- * @var array Map of object names and their representation from global scope
24
- */
25
- protected $_restorables = array();
26
 
27
- /**
28
- * @var array List of callbacks to be executed during shutdown sequence
29
- */
30
- protected $_callbacks = array();
31
 
32
 
33
- /**
34
- * Destructor
35
- *
36
- * Here processing of globals is made - values are replaced, callbacks
37
- * are executed and globals are restored to the previous state.
38
- *
39
- * @return void Destructor does not return
40
- */
41
- public function __destruct() {
42
- // replace globals from our internal store
43
- $restore = array();
44
- foreach ( $this->_preserve as $name => $class ) {
45
- if (
46
- ! isset( $GLOBALS[$name] ) ||
47
- ! ( $GLOBALS[$name] instanceof $class )
48
- ) {
49
- $restore[$name] = NULL;
50
- if ( isset( $GLOBALS[$name] ) ) {
51
- $restore[$name] = $GLOBALS[$name];
52
- }
53
- $GLOBALS[$name] = $this->_restorables[$name];
54
- }
55
- }
56
- // execute callbacks
57
- foreach ( $this->_callbacks as $callback ) {
58
- call_user_func( $callback );
59
- }
60
- // restore globals to previous state
61
- foreach ( $restore as $name => $object ) {
62
- if ( NULL === $object ) {
63
- unset( $GLOBALS[$name] );
64
- } else {
65
- $GLOBALS[$name] = $object;
66
- }
67
- }
68
- // destroy local references
69
- foreach ( $this->_restorables as $name => $object ) {
70
- unset( $object, $this->_restorables[$name] );
71
- }
72
- if ( AI1EC_DEBUG ) {
73
- // __destruct is called twice if facebook extension is installed
74
- // still can't find the reason, this fixes it but prevent other plugins
75
- // __destruct() so let's just use it in dev until we fix this.
76
- exit();
77
- }
78
- }
79
 
80
- /**
81
- * Register a callback to be executed during shutdown sequence
82
- *
83
- * @param callback $callback Valid PHP callback
84
- *
85
- * @return Ai1ec_Shutdown_Utility Self instance for chaining
86
- */
87
- public function register( $callback ) {
88
- $this->_callbacks[] = $callback;
89
- return $this;
90
- }
91
 
92
- /**
93
- * Constructor
94
- *
95
- * Here global variables are referenced locally to ensure their preservation
96
- *
97
- * @return void Constructor does not return
98
- */
99
- public function __construct() {
100
- foreach ( $this->_preserve as $name => $class ) {
101
- $this->_restorables[$name] = $GLOBALS[$name];
102
- }
103
- }
104
 
105
  }
11
  */
12
  class Ai1ec_Shutdown_Controller {
13
 
14
+ /**
15
+ * @var array Map of object names and class names they represent to preserve
16
+ */
17
+ protected $_preserve = array(
18
+ 'wpdb' => 'wpdb',
19
+ 'wp_object_cache' => 'WP_Object_Cache',
20
+ );
21
 
22
+ /**
23
+ * @var array Map of object names and their representation from global scope
24
+ */
25
+ protected $_restorables = array();
26
 
27
+ /**
28
+ * @var array List of callbacks to be executed during shutdown sequence
29
+ */
30
+ protected $_callbacks = array();
31
 
32
 
33
+ /**
34
+ * Destructor
35
+ *
36
+ * Here processing of globals is made - values are replaced, callbacks
37
+ * are executed and globals are restored to the previous state.
38
+ *
39
+ * @return void Destructor does not return
40
+ */
41
+ public function __destruct() {
42
+ // replace globals from our internal store
43
+ $restore = array();
44
+ foreach ( $this->_preserve as $name => $class ) {
45
+ if (
46
+ ! isset( $GLOBALS[$name] ) ||
47
+ ! ( $GLOBALS[$name] instanceof $class )
48
+ ) {
49
+ $restore[$name] = NULL;
50
+ if ( isset( $GLOBALS[$name] ) ) {
51
+ $restore[$name] = $GLOBALS[$name];
52
+ }
53
+ $GLOBALS[$name] = $this->_restorables[$name];
54
+ }
55
+ }
56
+ // execute callbacks
57
+ foreach ( $this->_callbacks as $callback ) {
58
+ call_user_func( $callback );
59
+ }
60
+ // restore globals to previous state
61
+ foreach ( $restore as $name => $object ) {
62
+ if ( NULL === $object ) {
63
+ unset( $GLOBALS[$name] );
64
+ } else {
65
+ $GLOBALS[$name] = $object;
66
+ }
67
+ }
68
+ // destroy local references
69
+ foreach ( $this->_restorables as $name => $object ) {
70
+ unset( $object, $this->_restorables[$name] );
71
+ }
72
+ if ( AI1EC_DEBUG ) {
73
+ // __destruct is called twice if facebook extension is installed
74
+ // still can't find the reason, this fixes it but prevent other plugins
75
+ // __destruct() so let's just use it in dev until we fix this.
76
+ exit();
77
+ }
78
+ }
79
 
80
+ /**
81
+ * Register a callback to be executed during shutdown sequence
82
+ *
83
+ * @param callback $callback Valid PHP callback
84
+ *
85
+ * @return Ai1ec_Shutdown_Utility Self instance for chaining
86
+ */
87
+ public function register( $callback ) {
88
+ $this->_callbacks[] = $callback;
89
+ return $this;
90
+ }
91
 
92
+ /**
93
+ * Constructor
94
+ *
95
+ * Here global variables are referenced locally to ensure their preservation
96
+ *
97
+ * @return void Constructor does not return
98
+ */
99
+ public function __construct() {
100
+ foreach ( $this->_preserve as $name => $class ) {
101
+ $this->_restorables[$name] = $GLOBALS[$name];
102
+ }
103
+ }
104
 
105
  }
app/model/api/api-abstract.php CHANGED
@@ -10,557 +10,616 @@
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
- }
80
-
81
- /**
82
- * Save the Payment settings localy (same saved on the API)
83
- * @param array Preferences to save
84
- */
85
- public function save_payment_settings( array $values ) {
86
- $api_settings = $this->get_ticketing_settings();
87
- if ( null !== $values ) {
88
- $api_settings['payment_settings'] = $values;
89
- } else {
90
- unset( $api_settings['payment_settings'] );
91
- }
92
- return update_option( self::WP_OPTION_KEY, $api_settings );
93
- }
94
-
95
- /**
96
- * Get the saved payments settings (the same saved on the API)
97
- */
98
- public function get_payment_settings() {
99
- return $this->get_ticketing_settings( 'payment_settings' );
100
- }
101
-
102
- /**
 
 
 
103
  * Check if the current WP instance has payments settings configured
104
  */
105
  public function has_payment_settings() {
106
- $payment_settings = $this->get_payment_settings();
107
- if ( null === $payment_settings ) {
108
- //code to migrate the settings save on ticketing api and
109
- //bring them to the core side
110
- $payment_settings = $this->get_payment_preferences();
111
- if ( is_object( $payment_settings ) ) {
112
- $payment_settings = (array) $payment_settings;
113
- }
114
- $this->save_payment_settings( (array) $payment_settings );
115
- }
116
- return ( null !== $payment_settings &&
117
- 'paypal' === $payment_settings['payment_method'] &&
118
- false === ai1ec_is_blank( $payment_settings['paypal_email'] ) ) ;
119
- }
120
-
121
-
122
- /**
123
- * @return object Response from API, or empty defaults
124
- */
125
- public function get_payment_preferences() {
126
- $calendar_id = $this->_get_ticket_calendar();
127
- $settings = null;
128
- if ( 0 < $calendar_id ) {
129
- $response = $this->request_api( 'GET', AI1EC_API_URL . "calendars/$calendar_id/payment",
130
- null, //no body
131
- true //decode response body
132
- );
133
- if ( $this->is_response_success( $response ) ) {
134
- $settings = $response->body;
135
- }
136
- }
137
- if ( is_null( $settings ) ) {
138
- return (object) array( 'payment_method'=>'paypal', 'paypal_email'=> '', 'first_name'=>'', 'last_name'=>'', 'currency'=> 'USD' );
139
- } else {
140
- if ( ! isset( $settings->currency ) ) {
141
- $settings->currency = 'USD';
142
- }
143
- return $settings;
144
- }
145
- }
146
 
147
 
148
  public function get_timely_token() {
149
  return $this->get_ticketing_settings( 'token' );
150
- }
151
-
152
- protected function save_calendar_id ( $calendar_id ) {
153
- $api_settings = $this->get_ticketing_settings();
154
- $api_settings['calendar_id'] = $calendar_id;
155
- return update_option( self::WP_OPTION_KEY, $api_settings );
156
- }
157
-
158
- /**
159
- * Get the header array with authorization token
160
- */
161
- protected function _get_headers( $custom_headers = null ) {
162
- $headers = array(
163
- 'content-type' => 'application/json'
164
- );
165
- $headers['Authorization'] = 'Basic ' . $this->get_ticketing_settings( 'token', '' );
166
- if ( null !== $custom_headers ) {
167
- foreach ( $custom_headers as $key => $value ) {
168
- if ( null === $value ) {
169
- unset( $headers[$key] );
170
- } else {
171
- $headers[$key] = $value;
172
- }
173
- }
174
- }
175
- return $headers;
176
- }
177
-
178
- /**
179
- * Create a standarized message to return
180
- * 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.
181
- * 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.
182
- */
183
- protected function _transform_error_message( $base_message, $response, $url, $ask_for_reload = false ) {
184
- $api_error = $this->get_api_error_msg( $response );
185
- $result = null;
186
- if ( false === ai1ec_is_blank( $api_error ) ) {
187
- $result = sprintf(
188
- __( '%s.<br/>Detail: %s.', AI1EC_PLUGIN_NAME ),
189
- $base_message, $api_error
190
- );
191
- } else {
192
- if ( is_wp_error( $response ) ) {
193
- $error_message = sprintf(
194
- __( 'API URL: %s.<br/>Detail: %s', AI1EC_PLUGIN_NAME ),
195
- $url,
196
- $response->get_error_message()
197
- );
198
- } else {
199
- $error_message = sprintf(
200
- __( 'API URL: %s.<br/>Detail: %s - %s', AI1EC_PLUGIN_NAME ),
201
- $url,
202
- wp_remote_retrieve_response_code( $response ),
203
- wp_remote_retrieve_response_message( $response )
204
- );
205
- $mailto = '<a href="mailto:labs@time.ly" target="_top">labs@time.ly</a>';
206
- if ( true === $ask_for_reload ) {
207
- $result = sprintf(
208
- __( '%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 ),
209
- $base_message,
210
- $mailto,
211
- $error_message
212
- );
213
- } else {
214
- $result = sprintf(
215
- __( '%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 ),
216
- $base_message,
217
- $mailto,
218
- $error_message
219
- );
220
- }
221
- }
222
- }
223
- $result = trim( $result );
224
- $result = str_replace( '..', '.', $result );
225
- $result = str_replace( '.,', '.', $result );
226
- return $result;
227
- }
228
-
229
-
230
- /**
231
- * Search for the API message error
232
- */
233
- public function get_api_error_msg( $response ) {
234
- if ( isset( $response ) && false === is_wp_error( $response ) ) {
235
- $response_body = json_decode( $response['body'], true );
236
- if ( is_array( $response_body ) &&
237
- isset( $response_body['errors'] ) ) {
238
- $errors = $response_body['errors'];
239
- if ( false === is_array( $errors )) {
240
- $errors = array( $errors );
241
- }
242
- $messages = null;
243
- foreach ($errors as $key => $value) {
244
- if ( false === ai1ec_is_blank( $value ) ) {
245
- if ( is_array( $value ) ) {
246
- $value = implode ( ', ', $value );
247
- }
248
- $messages[] = $value;
249
- }
250
- }
251
- if ( null !== $messages && false === empty( $messages ) ) {
252
- return implode ( ', ', $messages);
253
- }
254
- }
255
- }
256
- return null;
257
- }
258
-
259
- /**
260
- * Get the ticket calendar from settings, if the calendar does not exists in
261
- * settings, then we will try to find on the API
262
- * @return string JSON.
263
- */
264
- protected function _get_ticket_calendar( $createIfNotExists = true ) {
265
- $ticketing_calendar_id = $this->get_ticketing_settings( 'calendar_id', 0 );
266
- if ( 0 < $ticketing_calendar_id ) {
267
- return $ticketing_calendar_id;
268
- } else {
269
- if ( ! $createIfNotExists ) {
270
- return 0;
271
- }
272
- // Try to find the calendar in the API
273
- $ticketing_calendar_id = $this->_find_user_calendar();
274
- if ( 0 < $ticketing_calendar_id ) {
275
- $this->save_calendar_id( $ticketing_calendar_id );
276
-
277
- return $ticketing_calendar_id;
278
- } else {
279
- // If the calendar doesn't exist in the API, create a new one
280
- $ticketing_calendar_id = $this->_create_calendar();
281
- if ( 0 < $ticketing_calendar_id ) {
282
- $this->save_calendar_id( $ticketing_calendar_id );
283
-
284
- return $ticketing_calendar_id;
285
- } else {
286
- return 0;
287
- }
288
- }
289
- }
290
- }
291
-
292
- /**
293
- * Find the existent calendar when the user is signing in
294
- */
295
- protected function _find_user_calendar() {
296
- $body = array(
297
- 'title' => get_bloginfo( 'name' ),
298
- 'url' => ai1ec_site_url()
299
- );
300
- $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars',
301
- json_encode( $body )
302
- );
303
- if ( $this->is_response_success( $response ) ) {
304
- if ( is_array( $response->body ) ) {
305
- return $response->body[0]->id;
306
- } else {
307
- return $response->body->id;
308
- }
309
- } else {
310
- return 0;
311
- }
312
- }
313
-
314
- /**
315
- * Create a calendar when the user is signup
316
- */
317
- protected function _create_calendar() {
318
- $body = array(
319
- 'title' => get_bloginfo( 'name' ),
320
- 'url' => ai1ec_site_url(),
321
- 'timezone' => $this->_settings->get( 'timezone_string' )
322
- );
323
- $response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars',
324
- json_encode( $body )
325
- );
326
- if ( $this->is_response_success( $response ) ) {
327
- return $response->body->id;
328
- } else {
329
- $this->log_error( $response, 'Created calendar' );
330
- return 0;
331
- }
332
- }
333
 
334
  /**
335
  * Check if the current WP instance is signed into the API
336
  */
337
  public function is_signed() {
338
- return ( true === $this->get_ticketing_settings( 'enabled', false ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  }
340
 
341
  /**
342
  * Get the current email account
343
  */
344
- public function get_current_account() {
345
- return $this->get_ticketing_settings( 'account', '' );
346
- }
347
 
348
- /**
349
- * Get the current calendar id
350
- */
351
  public function get_current_calendar() {
352
- return $this->get_ticketing_settings( 'calendar_id', 0 );
353
  }
354
 
355
  /**
356
  * Get the last message return by Signup or Signup process
357
  */
358
  public function get_sign_message() {
359
- return $this->get_ticketing_settings( 'message', '' );
360
- }
361
-
362
- /**
363
- * Clear the last message return by Signup or Signup process
364
- */
365
- public function clear_sign_message() {
366
- $api_settings = $this->get_ticketing_settings();
367
- $api_settings['message'] = '';
368
- return update_option( self::WP_OPTION_KEY, $api_settings );
369
- }
370
-
371
- /**
372
- * @return array List of subscriptions and limits
373
- */
374
- protected function get_subscriptions( $force_refresh = false ) {
375
- $subscriptions = get_transient( 'ai1ec_api_subscriptions' );
376
-
377
- if ( false === $subscriptions || $force_refresh || ( defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG ) ) {
378
- $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/subscriptions',
379
- null,
380
- true
381
- );
382
- if ( $this->is_response_success( $response ) ) {
383
- $subscriptions = (array) $response->body;
384
- } else {
385
- $subscriptions = array();
386
- }
387
-
388
- // Save for 5 minutes
389
- $minutes = 5;
390
- set_transient( 'ai1ec_api_subscriptions', $subscriptions, $minutes * 60 );
391
- }
392
-
393
- return $subscriptions;
394
- }
395
-
396
- /**
397
- * Check if calendar should have a specific feature enabled
398
- */
399
- public function has_subscription_active( $feature ) {
400
- $subscriptions = $this->get_subscriptions();
401
-
402
- return array_key_exists( $feature, $subscriptions );
403
- }
404
-
405
- /**
406
- * Check if feature has reached its limit
407
- */
408
- public function subscription_has_reached_limit( $feature ) {
409
- $has_reached_limit = true;
410
-
411
- $provided = $this->subscription_get_quantity_limit( $feature );
412
- $used = $this->subscription_get_used_quantity( $feature );
413
-
414
- if ( $provided - $used > 0 ) {
415
- $has_reached_limit = false;
416
- }
417
-
418
- return $has_reached_limit;
419
- }
420
-
421
- /**
422
- * Get feature quantity limit
423
- */
424
- public function subscription_get_quantity_limit( $feature ) {
425
- $provided = 0;
426
-
427
- $subscriptions = $this->get_subscriptions();
428
-
429
- if ( array_key_exists( $feature, $subscriptions ) ) {
430
- $quantity = (array) $subscriptions[$feature];
431
-
432
- $provided = $quantity['provided'];
433
- }
434
-
435
- return $provided;
436
- }
437
-
438
- /**
439
- * Get feature used quantity
440
- */
441
- public function subscription_get_used_quantity( $feature ) {
442
- $used = 0;
443
-
444
- $subscriptions = $this->get_subscriptions();
445
-
446
- if ( array_key_exists( $feature, $subscriptions ) ) {
447
- $quantity = (array) $subscriptions[$feature];
448
-
449
- $used = $quantity['used'];
450
- }
451
-
452
- return $used;
453
- }
454
-
455
- /**
456
- * Make the request to the API endpons
457
- * @param $url The end part of the url to make the request.
458
- * $body The body to send the message
459
- * $method POST | GET | PUT, etc
460
- * or send a customized message to be showed in case of error
461
- * $decode_response_body TRUE (default) to decode the body response
462
- * @return stdClass with the the fields:
463
- * is_error TRUE or FALSE
464
- * error in case of is_error be true
465
- * body in case of is_error be false
466
- */
467
- protected function request_api( $method, $url, $body = null, $decode_response_body = true, $custom_headers = null ) {
468
- $request = array(
469
- 'method' => $method,
470
- 'headers' => $this->_get_headers( $custom_headers ),
471
- 'timeout' => self::DEFAULT_TIMEOUT
472
- );
473
- if ( ! is_null( $body ) ) {
474
- $request[ 'body' ] = $body;
475
- }
476
- $response = wp_remote_request( $url, $request );
477
- $result = new stdClass();
478
- $result->url = $url;
479
- $result->raw = $response;
480
- if ( is_wp_error( $response ) ) {
481
- $result->is_error = true;
482
- $result->error = $response->get_error_message();
483
- } else {
484
- $result->response_code = wp_remote_retrieve_response_code( $response );
485
- if ( 200 === $result->response_code ) {
486
- if ( true === $decode_response_body ) {
487
- $result->body = json_decode( $response['body'] );
488
- if ( false === is_null( $result->body ) ) {
489
- $result->is_error = false;
490
- } else {
491
- $result->is_error = true;
492
- $result->error = __( 'Error decoding the response', AI1EC_PLUGIN_NAME );
493
- unset( $result->body );
494
- }
495
- } else {
496
- $result->is_error = false;
497
- $result->body = $response['body'];
498
- }
499
- } else {
500
- $result->is_error = true;
501
- $result->error = wp_remote_retrieve_response_message( $response );
502
- }
503
- }
504
- return $result;
505
- }
506
-
507
- /**
508
- * Make a post request to the api
509
- * @param rest_endpoint Partial URL that can include {calendar_id} that will be replaced by the current calendar signed
510
- */
511
- public function call_api( $method, $endpoint, $body = null, $decode_response_body = true, $custom_headers = null ) {
512
- $calendar_id = $this->_get_ticket_calendar();
513
- if ( 0 >= $calendar_id ) {
514
- return false;
515
- }
516
- $url = AI1EC_API_URL . str_replace( '{calendar_id}', $calendar_id, $endpoint );
517
- $body = json_encode( $body );
518
- return $this->request_api( $method, $url, $body, $decode_response_body, $custom_headers );
519
- }
520
-
521
- /**
522
- * Save an error notification to be showed to the user on WP header of the page
523
- * @param $response The response got from request_api method.
524
- * $custom_error_message The custom message to show before the detailed message
525
- * @return full error message
526
- */
527
- protected function save_error_notification( $response, $custom_error_response ) {
528
- $error_message = $this->_transform_error_message(
529
- $custom_error_response,
530
- $response->raw,
531
- $response->url,
532
- true
533
- );
534
- $response->error_message = $error_message;
535
- $notification = $this->_registry->get( 'notification.admin' );
536
- $notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
537
- error_log( $custom_error_response . ': ' . $error_message . ' - raw error: ' . print_r( $response->raw, true ) );
538
- return $error_message;
539
- }
540
-
541
- /**
542
- * Save an error notification to be showed to the user on WP header of the page
543
- * @param $response The response got from request_api method.
544
- * $custom_error_message The custom message to show before the detailed message
545
- * @return full error message
546
- */
547
- protected function log_error( $response, $custom_error_response ) {
548
- $error_message = $this->_transform_error_message(
549
- $custom_error_response,
550
- $response->raw,
551
- $response->url,
552
- true
553
- );
554
- error_log( $custom_error_response . ': ' . $error_message . ' - raw error: ' . print_r( $response->raw, true ) );
555
- return $error_message;
556
- }
557
-
558
- /**
559
- * Useful method to check if the response of request_api is a successful message
560
- */
561
- public function is_response_success( $response ) {
562
- return $response != null &&
563
- ( !isset( $response->is_error ) || ( isset( $response->is_error ) && false === $response->is_error ) );
564
- }
 
 
 
 
 
 
 
565
 
566
  }
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
+ $message .= '- ' . explode( '/', $failed )[0] . '<br />';
384
+ }
385
+
386
+ $this->show_error( $message );
387
+ }
388
+
389
+ set_transient( 'ai1ec_api_checked', true, 5 * 60 );
390
+ }
391
  }
392
 
393
  /**
394
  * Get the current email account
395
  */
396
+ public function get_current_account() {
397
+ return $this->get_ticketing_settings( 'account', '' );
398
+ }
399
 
400
+ /**
401
+ * Get the current calendar id
402
+ */
403
  public function get_current_calendar() {
404
+ return $this->get_ticketing_settings( 'calendar_id', 0 );
405
  }
406
 
407
  /**
408
  * Get the last message return by Signup or Signup process
409
  */
410
  public function get_sign_message() {
411
+ return $this->get_ticketing_settings( 'message', '' );
412
+ }
413
+
414
+ /**
415
+ * Clear the last message return by Signup or Signup process
416
+ */
417
+ public function clear_sign_message() {
418
+ $api_settings = $this->get_ticketing_settings();
419
+ $api_settings['message'] = '';
420
+ return update_option( self::WP_OPTION_KEY, $api_settings );
421
+ }
422
+
423
+ /**
424
+ * @return array List of subscriptions and limits
425
+ */
426
+ protected function get_subscriptions( $force_refresh = false ) {
427
+ $subscriptions = get_transient( 'ai1ec_api_subscriptions' );
428
+
429
+ if ( false === $subscriptions || $force_refresh || ( defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG ) ) {
430
+ $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/subscriptions',
431
+ null,
432
+ true
433
+ );
434
+ if ( $this->is_response_success( $response ) ) {
435
+ $subscriptions = (array) $response->body;
436
+ } else {
437
+ $subscriptions = array();
438
+ }
439
+
440
+ // Save for 5 minutes
441
+ $minutes = 5;
442
+ set_transient( 'ai1ec_api_subscriptions', $subscriptions, $minutes * 60 );
443
+ }
444
+
445
+ return $subscriptions;
446
+ }
447
+
448
+ /**
449
+ * Check if calendar should have a specific feature enabled
450
+ */
451
+ public function has_subscription_active( $feature ) {
452
+ $subscriptions = $this->get_subscriptions();
453
+
454
+ return array_key_exists( $feature, $subscriptions );
455
+ }
456
+
457
+ /**
458
+ * Check if feature has reached its limit
459
+ */
460
+ public function subscription_has_reached_limit( $feature ) {
461
+ $has_reached_limit = true;
462
+
463
+ $provided = $this->subscription_get_quantity_limit( $feature );
464
+ $used = $this->subscription_get_used_quantity( $feature );
465
+
466
+ if ( $provided - $used > 0 ) {
467
+ $has_reached_limit = false;
468
+ }
469
+
470
+ return $has_reached_limit;
471
+ }
472
+
473
+ /**
474
+ * Get feature quantity limit
475
+ */
476
+ public function subscription_get_quantity_limit( $feature ) {
477
+ $provided = 0;
478
+
479
+ $subscriptions = $this->get_subscriptions();
480
+
481
+ if ( array_key_exists( $feature, $subscriptions ) ) {
482
+ $quantity = (array) $subscriptions[$feature];
483
+
484
+ $provided = $quantity['provided'];
485
+ }
486
+
487
+ return $provided;
488
+ }
489
+
490
+ /**
491
+ * Get feature used quantity
492
+ */
493
+ public function subscription_get_used_quantity( $feature ) {
494
+ $used = 0;
495
+
496
+ $subscriptions = $this->get_subscriptions();
497
+
498
+ if ( array_key_exists( $feature, $subscriptions ) ) {
499
+ $quantity = (array) $subscriptions[$feature];
500
+
501
+ $used = $quantity['used'];
502
+ }
503
+
504
+ return $used;
505
+ }
506
+
507
+ /**
508
+ * Make the request to the API endpons
509
+ * @param $url The end part of the url to make the request.
510
+ * $body The body to send the message
511
+ * $method POST | GET | PUT, etc
512
+ * or send a customized message to be showed in case of error
513
+ * $decode_response_body TRUE (default) to decode the body response
514
+ * @return stdClass with the the fields:
515
+ * is_error TRUE or FALSE
516
+ * error in case of is_error be true
517
+ * body in case of is_error be false
518
+ */
519
+ protected function request_api( $method, $url, $body = null, $decode_response_body = true, $custom_headers = null ) {
520
+ $request = array(
521
+ 'method' => $method,
522
+ 'headers' => $this->_get_headers( $custom_headers ),
523
+ 'timeout' => self::DEFAULT_TIMEOUT
524
+ );
525
+ if ( ! is_null( $body ) ) {
526
+ $request[ 'body' ] = $body;
527
+ }
528
+ $response = wp_remote_request( $url, $request );
529
+ $result = new stdClass();
530
+ $result->url = $url;
531
+ $result->raw = $response;
532
+ if ( is_wp_error( $response ) ) {
533
+ $result->is_error = true;
534
+ $result->error = $response->get_error_message();
535
+ } else {
536
+ $result->response_code = wp_remote_retrieve_response_code( $response );
537
+ if ( 200 === $result->response_code ) {
538
+ if ( true === $decode_response_body ) {
539
+ $result->body = json_decode( $response['body'] );
540
+ if ( false === is_null( $result->body ) ) {
541
+ $result->is_error = false;
542
+ } else {
543
+ $result->is_error = true;
544
+ $result->error = __( 'Error decoding the response', AI1EC_PLUGIN_NAME );
545
+ unset( $result->body );
546
+ }
547
+ } else {
548
+ $result->is_error = false;
549
+ $result->body = $response['body'];
550
+ }
551
+ } else {
552
+ $result->is_error = true;
553
+ $result->error = wp_remote_retrieve_response_message( $response );
554
+ }
555
+ }
556
+ return $result;
557
+ }
558
+
559
+ /**
560
+ * Make a post request to the api
561
+ * @param rest_endpoint Partial URL that can include {calendar_id} that will be replaced by the current calendar signed
562
+ */
563
+ public function call_api( $method, $endpoint, $body = null, $decode_response_body = true, $custom_headers = null ) {
564
+ $calendar_id = $this->_get_ticket_calendar();
565
+ if ( 0 >= $calendar_id ) {
566
+ return false;
567
+ }
568
+ $url = AI1EC_API_URL . str_replace( '{calendar_id}', $calendar_id, $endpoint );
569
+ $body = json_encode( $body );
570
+ return $this->request_api( $method, $url, $body, $decode_response_body, $custom_headers );
571
+ }
572
+
573
+ /**
574
+ * Save an error notification to be showed to the user on WP header of the page
575
+ * @param $response The response got from request_api method.
576
+ * $custom_error_message The custom message to show before the detailed message
577
+ * @return full error message
578
+ */
579
+ protected function save_error_notification( $response, $custom_error_response ) {
580
+ $error_message = $this->_transform_error_message(
581
+ $custom_error_response,
582
+ $response->raw,
583
+ $response->url,
584
+ true
585
+ );
586
+ $response->error_message = $error_message;
587
+ $notification = $this->_registry->get( 'notification.admin' );
588
+ $notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
589
+ error_log( $custom_error_response . ': ' . $error_message . ' - raw error: ' . print_r( $response->raw, true ) );
590
+ return $error_message;
591
+ }
592
+
593
+ /**
594
+ * Save an error notification to be showed to the user on WP header of the page
595
+ * @param $response The response got from request_api method.
596
+ * $custom_error_message The custom message to show before the detailed message
597
+ * @return full error message
598
+ */
599
+ protected function log_error( $response, $custom_error_response ) {
600
+ $error_message = $this->_transform_error_message(
601
+ $custom_error_response,
602
+ $response->raw,
603
+ $response->url,
604
+ true
605
+ );
606
+ error_log( $custom_error_response . ': ' . $error_message . ' - raw error: ' . print_r( $response->raw, true ) );
607
+ return $error_message;
608
+ }
609
+
610
+ protected function show_error( $error_message ) {
611
+ $notification = $this->_registry->get( 'notification.admin' );
612
+ $notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
613
+ error_log( $error_message);
614
+ return $error_message;
615
+ }
616
+
617
+ /**
618
+ * Useful method to check if the response of request_api is a successful message
619
+ */
620
+ public function is_response_success( $response ) {
621
+ return $response != null &&
622
+ ( !isset( $response->is_error ) || ( isset( $response->is_error ) && false === $response->is_error ) );
623
+ }
624
 
625
  }
app/model/api/api-features.php CHANGED
@@ -1,22 +1,45 @@
1
  <?php
2
  class Ai1ec_Api_Features {
3
- const CODE_API_ACCESS = 'api-access';
4
- const CODE_TICKETING = 'ticketing';
5
- const CODE_TWITTER = 'twitter';
6
- const CODE_FRONTEND_SUBMISSIONS = 'frontend-submissions';
7
- const CODE_CSV_IMPORT = 'csv-import';
8
- const CODE_SUPER_WIDGET = 'super-widget';
9
- const CODE_EXTENDED_VIEWS = 'extended-views';
10
- const CODE_BIG_FILTERING = 'big-filtering';
11
- const CODE_CUSTOM_FILTERS = 'custom-filter-groups';
12
- const CODE_DISCOVER_EVENTS = 'discover-events';
13
- const CODE_EVENT_PROMOTE = 'event-promote';
14
- const CODE_FACEBOOK_INTEGRATION = 'facebook-integration';
15
- const CODE_FEATURED_EVENTS = 'featured-events';
16
- const CODE_MAILCHIMP = 'mailchimp';
17
- const CODE_PHRASE_OVERRIDE = 'phrase-override';
18
- const CODE_POPOVERS = 'popovers';
19
- const CODE_SAVE_AND_SHARE = 'save-and-share';
20
- const CODE_VENUES = 'venues';
21
- const CODE_IMPORT_FEEDS = 'import-feeds';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
1
  <?php
2
  class Ai1ec_Api_Features {
3
+
4
+ const CODE_API_ACCESS = 'api-access';
5
+ const CODE_TICKETING = 'ticketing';
6
+ const CODE_TWITTER = 'twitter';
7
+ const CODE_FRONTEND_SUBMISSIONS = 'frontend-submissions';
8
+ const CODE_CSV_IMPORT = 'csv-import';
9
+ const CODE_SUPER_WIDGET = 'super-widget';
10
+ const CODE_EXTENDED_VIEWS = 'extended-views';
11
+ const CODE_BIG_FILTERING = 'big-filtering';
12
+ const CODE_CUSTOM_FILTERS = 'custom-filter-groups';
13
+ const CODE_DISCOVER_EVENTS = 'discover-events';
14
+ const CODE_EVENT_PROMOTE = 'event-promote';
15
+ const CODE_FACEBOOK_INTEGRATION = 'facebook-integration';
16
+ const CODE_FEATURED_EVENTS = 'featured-events';
17
+ const CODE_MAILCHIMP = 'mailchimp';
18
+ const CODE_PHRASE_OVERRIDE = 'phrase-override';
19
+ const CODE_POPOVERS = 'popovers';
20
+ const CODE_SAVE_AND_SHARE = 'save-and-share';
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',
28
+ self::CODE_FRONTEND_SUBMISSIONS => 'all-in-one-event-calendar-frontend-submissions/all-in-one-event-calendar-frontend-submissions.php',
29
+ self::CODE_CSV_IMPORT => 'all-in-one-event-calendar-csv-feed/all-in-one-event-calendar-csv-feed.php',
30
+ self::CODE_SUPER_WIDGET => 'all-in-one-event-calendar-super-widget/all-in-one-event-calendar-super-widget.php',
31
+ self::CODE_EXTENDED_VIEWS => 'all-in-one-event-calendar-extended-views/all-in-one-event-calendar-extended-views.php',
32
+ self::CODE_BIG_FILTERING => 'all-in-one-event-calendar-big-filtering/all-in-one-event-calendar-big-filtering.php',
33
+ self::CODE_CUSTOM_FILTERS => 'all-in-one-event-calendar-custom-filter-groups/all-in-one-event-calendar-custom-filter-groups.php',
34
+ self::CODE_DISCOVER_EVENTS => '',
35
+ self::CODE_EVENT_PROMOTE => 'all-in-one-event-calendar-event-promote/all-in-one-event-calendar-event-promote.php',
36
+ self::CODE_FACEBOOK_INTEGRATION => 'all-in-one-event-calendar-facebook-integration/all-in-one-event-calendar-facebook-integration.php',
37
+ self::CODE_FEATURED_EVENTS => 'all-in-one-event-calendar-featured-events/all-in-one-event-calendar-featured-events.php',
38
+ self::CODE_MAILCHIMP => 'all-in-one-event-calendar-mailchimp/all-in-one-event-calendar-mailchimp.php',
39
+ self::CODE_PHRASE_OVERRIDE => 'all-in-one-event-calendar-phrase-override/all-in-one-event-calendar-phrase-override.php',
40
+ self::CODE_POPOVERS => 'all-in-one-event-calendar-popovers/all-in-one-event-calendar-popovers.php',
41
+ self::CODE_SAVE_AND_SHARE => 'all-in-one-event-calendar-save-and-share/all-in-one-event-calendar-save-and-share.php',
42
+ self::CODE_VENUES => 'all-in-one-event-calendar-venue/all-in-one-event-calendar-venue.php',
43
+ self::CODE_IMPORT_FEEDS => '',
44
+ );
45
  }
app/model/api/api-feeds.php CHANGED
@@ -10,312 +10,312 @@
10
  */
11
  class Ai1ec_Api_Feeds extends Ai1ec_Api_Abstract {
12
 
13
- // Feed status
14
- // c = Feed not migrated yet to API
15
- // a = Feed migrated to API (all events)
16
- // b = Feed migrated to API (individual events were selected)
17
- public static $FEED_NOT_MIGRATED_CODE = 'c';
18
- public static $FEED_API_ALL_EVENTS_CODE = 'a';
19
- public static $FEED_API_SOME_EVENTS_CODE = 'b';
20
-
21
- /**
22
- * Post construction routine.
23
- *
24
- * Override this method to perform post-construction tasks.
25
- *
26
- * @return void Return from this method is ignored.
27
- */
28
- protected function _initialize() {
29
- parent::_initialize();
30
- }
31
-
32
- /**
33
- * Get static var (for PHP 5.2 compatibility)
34
- *
35
- * @param String $var
36
- */
37
- public function getStaticVar($var) {
38
- return self::$$var;
39
- }
40
-
41
- /**
42
- * Getting a suggested events list.
43
- * @return stClass Response using the following format:
44
- * [total] => 10
45
- * [per_page] => 8
46
- * [current_page] => 1
47
- * [last_page] => 2
48
- * [next_page_url] =>
49
- * [prev_page_url] =>
50
- * [from] => 1
51
- * [to] => 8
52
- * [data] => Array list of suggested events
53
- */
54
- public function get_suggested_events() {
55
- $calendar_id = $this->_get_ticket_calendar();
56
- if ( 0 >= $calendar_id ) {
57
- throw new Exception( 'Calendar ID not found' );
58
- }
59
-
60
- $body = null;
61
- if (
62
- isset( $_POST[ 'lat' ] ) &&
63
- isset( $_POST[ 'lng' ] ) &&
64
- isset( $_POST[ 'radius' ] )
65
- ) {
66
- $body = array(
67
- 'lat' => $_POST[ 'lat' ],
68
- 'lng' => $_POST[ 'lng' ],
69
- 'radius' => $_POST[ 'radius' ]
70
- );
71
- }
72
-
73
- $page = isset( $_POST[ 'page' ] ) ? $_POST[ 'page' ] : 1;
74
- $max = isset( $_POST[ 'max' ] ) ? $_POST[ 'max' ] : 8;
75
- $term = isset( $_POST[ 'term' ] ) && $_POST[ 'term' ]
76
- ? urlencode( $_POST[ 'term' ] )
77
- : '*';
78
- $location = isset( $_POST[ 'location' ] ) && $_POST[ 'location' ]
79
- ? '&location=' . urlencode( $_POST[ 'location' ] )
80
- : '';
81
-
82
- $url = AI1EC_API_URL .
83
- "calendars/$calendar_id/discover/events?page=$page&max=$max&term=$term" .
84
- $location;
85
-
86
- $response = $this->request_api( 'GET', $url,
87
- null !== $body ? json_encode( $body ) : null,
88
- true //decode body response
89
- );
90
-
91
- if ( $this->is_response_success( $response ) ) {
92
- return $response->body;
93
- } else {
94
- $this->save_error_notification(
95
- $response,
96
- __( 'We were unable to get the Suggested Events from Time.ly Network', AI1EC_PLUGIN_NAME )
97
- );
98
- throw new Exception( 'We were unable to get the Suggested Events from Time.ly Network' );
99
- }
100
- }
101
-
102
- /**
103
- * Call the API to Process and Import the Feed
104
- */
105
- public function import_feed( $entry ) {
106
- $calendar_id = $this->_get_ticket_calendar();
107
- if ( 0 >= $calendar_id ) {
108
- throw new Exception( 'Calendar ID not found' );
109
- }
110
- $response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/import',
111
- json_encode( array(
112
- 'url' => $entry['feed_url'],
113
- 'categories' => $entry['feed_category'],
114
- 'tags' => $entry['feed_tags'],
115
- 'allow_comments' => $entry['comments_enabled'],
116
- 'show_maps' => $entry['map_display_enabled'],
117
- 'import_any_tag_and_categories' => $entry['keep_tags_categories'],
118
- 'preserve_imported_events' => $entry['keep_old_events'],
119
- 'assign_default_utc' => $entry['import_timezone']
120
- ) )
121
- );
122
-
123
- if ( $this->is_response_success( $response ) ) {
124
- // Refresh list of subscriptions and limits
125
- $this->get_subscriptions( true );
126
-
127
- return $response->body;
128
- } else {
129
- $this->save_error_notification(
130
- $response,
131
- __( 'We were unable to import feed', AI1EC_PLUGIN_NAME )
132
- );
133
- throw new Exception( $this->get_api_error_msg( $response->raw ) );
134
- }
135
- }
136
-
137
- /**
138
- * Call the API to get the feed
139
- */
140
- public function get_feed( $feed_id ) {
141
- $calendar_id = $this->_get_ticket_calendar();
142
- if ( 0 >= $calendar_id ) {
143
- throw new Exception( 'Calendar ID not found' );
144
- }
145
- $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/get/' . $feed_id,
146
- json_encode( array( "max" => "9999" ) )
147
- );
148
-
149
- if ( $this->is_response_success( $response ) ) {
150
- return $response->body;
151
- } else {
152
- $this->save_error_notification(
153
- $response,
154
- __( 'We were unable to get feed data', AI1EC_PLUGIN_NAME )
155
- );
156
- throw new Exception( $this->get_api_error_msg( $response->raw ) );
157
- }
158
- }
159
-
160
- /**
161
- * Call the API to get list of feed subscriptions
162
- */
163
- public function get_feed_subscriptions( $force_refresh = false ) {
164
- $feeds_subscriptions = get_transient( 'ai1ec_api_feeds_subscriptions' );
165
-
166
- if ( $force_refresh || false === $feeds_subscriptions ) {
167
- $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/feeds/list',
168
- null,
169
- true
170
- );
171
-
172
- if ( $this->is_response_success( $response ) ) {
173
- $feeds_subscriptions = (array) $response->body;
174
- } else {
175
- $feeds_subscriptions = array();
176
- }
177
-
178
- // Save for 5 minutes
179
- $minutes = 5;
180
- set_transient( 'ai1ec_api_feeds_subscriptions', $feeds_subscriptions, $minutes * 60 );
181
- }
182
-
183
- return $feeds_subscriptions;
184
- }
185
-
186
- /**
187
- * Sync feed subscriptions
188
- */
189
- public function get_and_sync_feed_subscriptions() {
190
- $feeds_subscriptions = $this->get_feed_subscriptions();
191
-
192
- $db = $this->_registry->get( 'dbi.dbi' );
193
- $table_name = $db->get_table_name( 'ai1ec_event_feeds' );
194
-
195
- // Select all feeds
196
- $rows = $db->select(
197
- $table_name,
198
- array(
199
- 'feed_id',
200
- 'feed_url',
201
- 'feed_name',
202
- 'feed_category',
203
- 'feed_tags',
204
- 'comments_enabled',
205
- 'map_display_enabled',
206
- 'keep_tags_categories',
207
- 'keep_old_events',
208
- 'import_timezone'
209
- )
210
- );
211
-
212
- // Iterate over API response
213
- foreach( $feeds_subscriptions as $api_feed ) {
214
- $found = false;
215
-
216
- foreach ( $rows as $row ) {
217
- // Check if URL is the same
218
- if ( trim( $row->feed_url ) === trim( $api_feed->url ) ) {
219
- $found = true;
220
-
221
- // Update feed
222
- $db->update(
223
- $table_name,
224
- array(
225
- 'comments_enabled' => $api_feed->allow_comments,
226
- 'map_display_enabled' => $api_feed->show_maps,
227
- 'keep_tags_categories' => $api_feed->import_any_tag_and_categories,
228
- 'keep_old_events' => $api_feed->preserve_imported_events,
229
- 'import_timezone' => $api_feed->assign_default_utc,
230
- 'feed_name' => $api_feed->feed_id
231
- ),
232
- array(
233
- 'feed_id' => $row->feed_id
234
- )
235
- );
236
- }
237
- }
238
-
239
- // Not found in local database.. Insert
240
- if ( ! $found ) {
241
- $entry = array(
242
- 'feed_url' => $api_feed->url,
243
- 'feed_name' => $api_feed->feed_id,
244
- 'feed_category' => $api_feed->categories,
245
- 'feed_tags' => $api_feed->tags,
246
- 'comments_enabled' => $api_feed->allow_comments,
247
- 'map_display_enabled' => $api_feed->show_maps,
248
- 'keep_tags_categories' => $api_feed->import_any_tag_and_categories,
249
- 'keep_old_events' => $api_feed->preserve_imported_events,
250
- 'import_timezone' => $api_feed->assign_default_utc
251
- );
252
- $format = array( '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%d', '%d' );
253
- $db->insert(
254
- $table_name,
255
- $entry,
256
- $format
257
- );
258
- }
259
- }
260
- }
261
-
262
- /**
263
- * Call the API to subscribe feed
264
- */
265
- public function subscribe_feed( $feed_id, $feed_event_uid = '' ) {
266
- $calendar_id = $this->_get_ticket_calendar();
267
- if ( 0 >= $calendar_id ) {
268
- throw new Exception( 'Calendar ID not found' );
269
- }
270
-
271
- $response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/subscribe',
272
- json_encode( array(
273
- 'feed_id' => $feed_id,
274
- 'feed_event_uid' => $feed_event_uid
275
- ) )
276
- );
277
-
278
- // Refresh list of subscriptions and limits
279
- $this->get_subscriptions( true );
280
-
281
- if ( $this->is_response_success( $response ) ) {
282
- return $response->body;
283
- } else {
284
- $this->save_error_notification(
285
- $response,
286
- __( 'We were unable to subscribe feed', AI1EC_PLUGIN_NAME )
287
- );
288
- throw new Exception( $this->get_api_error_msg( $response->raw ) );
289
- }
290
- }
291
-
292
- /**
293
- * Call the API to unsubscribe feed
294
- */
295
- public function unsubscribe_feed( $feed_id, $feed_event_uid = '' ) {
296
- $calendar_id = $this->_get_ticket_calendar();
297
- if ( 0 >= $calendar_id ) {
298
- throw new Exception( 'Calendar ID not found' );
299
- }
300
-
301
- $response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/unsubscribe',
302
- json_encode( array(
303
- 'feed_id' => $feed_id,
304
- 'feed_event_uid' => $feed_event_uid
305
- ) )
306
- );
307
-
308
- // Refresh list of subscriptions and limits
309
- $this->get_subscriptions( true );
310
-
311
- if ( $this->is_response_success( $response ) ) {
312
- return $response->body;
313
- } else {
314
- $this->save_error_notification(
315
- $response,
316
- __( 'We were unable to unsubscribe feed', AI1EC_PLUGIN_NAME )
317
- );
318
- throw new Exception( $this->get_api_error_msg( $response->raw ) );
319
- }
320
- }
321
  }
10
  */
11
  class Ai1ec_Api_Feeds extends Ai1ec_Api_Abstract {
12
 
13
+ // Feed status
14
+ // c = Feed not migrated yet to API
15
+ // a = Feed migrated to API (all events)
16
+ // b = Feed migrated to API (individual events were selected)
17
+ public static $FEED_NOT_MIGRATED_CODE = 'c';
18
+ public static $FEED_API_ALL_EVENTS_CODE = 'a';
19
+ public static $FEED_API_SOME_EVENTS_CODE = 'b';
20
+
21
+ /**
22
+ * Post construction routine.
23
+ *
24
+ * Override this method to perform post-construction tasks.
25
+ *
26
+ * @return void Return from this method is ignored.
27
+ */
28
+ protected function _initialize() {
29
+ parent::_initialize();
30
+ }
31
+
32
+ /**
33
+ * Get static var (for PHP 5.2 compatibility)
34
+ *
35
+ * @param String $var
36
+ */
37
+ public function getStaticVar($var) {
38
+ return self::$$var;
39
+ }
40
+
41
+ /**
42
+ * Getting a suggested events list.
43
+ * @return stClass Response using the following format:
44
+ * [total] => 10
45
+ * [per_page] => 8
46
+ * [current_page] => 1
47
+ * [last_page] => 2
48
+ * [next_page_url] =>
49
+ * [prev_page_url] =>
50
+ * [from] => 1
51
+ * [to] => 8
52
+ * [data] => Array list of suggested events
53
+ */
54
+ public function get_suggested_events() {
55
+ $calendar_id = $this->_get_ticket_calendar();
56
+ if ( 0 >= $calendar_id ) {
57
+ throw new Exception( 'Calendar ID not found' );
58
+ }
59
+
60
+ $body = null;
61
+ if (
62
+ isset( $_POST[ 'lat' ] ) &&
63
+ isset( $_POST[ 'lng' ] ) &&
64
+ isset( $_POST[ 'radius' ] )
65
+ ) {
66
+ $body = array(
67
+ 'lat' => $_POST[ 'lat' ],
68
+ 'lng' => $_POST[ 'lng' ],
69
+ 'radius' => $_POST[ 'radius' ]
70
+ );
71
+ }
72
+
73
+ $page = isset( $_POST[ 'page' ] ) ? $_POST[ 'page' ] : 1;
74
+ $max = isset( $_POST[ 'max' ] ) ? $_POST[ 'max' ] : 8;
75
+ $term = isset( $_POST[ 'term' ] ) && $_POST[ 'term' ]
76
+ ? urlencode( $_POST[ 'term' ] )
77
+ : '*';
78
+ $location = isset( $_POST[ 'location' ] ) && $_POST[ 'location' ]
79
+ ? '&location=' . urlencode( $_POST[ 'location' ] )
80
+ : '';
81
+
82
+ $url = AI1EC_API_URL .
83
+ "calendars/$calendar_id/discover/events?page=$page&max=$max&term=$term" .
84
+ $location;
85
+
86
+ $response = $this->request_api( 'GET', $url,
87
+ null !== $body ? json_encode( $body ) : null,
88
+ true //decode body response
89
+ );
90
+
91
+ if ( $this->is_response_success( $response ) ) {
92
+ return $response->body;
93
+ } else {
94
+ $this->save_error_notification(
95
+ $response,
96
+ __( 'We were unable to get the Suggested Events from Time.ly Network', AI1EC_PLUGIN_NAME )
97
+ );
98
+ throw new Exception( 'We were unable to get the Suggested Events from Time.ly Network' );
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Call the API to Process and Import the Feed
104
+ */
105
+ public function import_feed( $entry ) {
106
+ $calendar_id = $this->_get_ticket_calendar();
107
+ if ( 0 >= $calendar_id ) {
108
+ throw new Exception( 'Calendar ID not found' );
109
+ }
110
+ $response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/import',
111
+ json_encode( array(
112
+ 'url' => $entry['feed_url'],
113
+ 'categories' => $entry['feed_category'],
114
+ 'tags' => $entry['feed_tags'],
115
+ 'allow_comments' => $entry['comments_enabled'],
116
+ 'show_maps' => $entry['map_display_enabled'],
117
+ 'import_any_tag_and_categories' => $entry['keep_tags_categories'],
118
+ 'preserve_imported_events' => $entry['keep_old_events'],
119
+ 'assign_default_utc' => $entry['import_timezone']
120
+ ) )
121
+ );
122
+
123
+ if ( $this->is_response_success( $response ) ) {
124
+ // Refresh list of subscriptions and limits
125
+ $this->get_subscriptions( true );
126
+
127
+ return $response->body;
128
+ } else {
129
+ $this->save_error_notification(
130
+ $response,
131
+ __( 'We were unable to import feed', AI1EC_PLUGIN_NAME )
132
+ );
133
+ throw new Exception( $this->get_api_error_msg( $response->raw ) );
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Call the API to get the feed
139
+ */
140
+ public function get_feed( $feed_id ) {
141
+ $calendar_id = $this->_get_ticket_calendar();
142
+ if ( 0 >= $calendar_id ) {
143
+ throw new Exception( 'Calendar ID not found' );
144
+ }
145
+ $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/get/' . $feed_id,
146
+ json_encode( array( "max" => "9999" ) )
147
+ );
148
+
149
+ if ( $this->is_response_success( $response ) ) {
150
+ return $response->body;
151
+ } else {
152
+ $this->save_error_notification(
153
+ $response,
154
+ __( 'We were unable to get feed data', AI1EC_PLUGIN_NAME )
155
+ );
156
+ throw new Exception( $this->get_api_error_msg( $response->raw ) );
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Call the API to get list of feed subscriptions
162
+ */
163
+ public function get_feed_subscriptions( $force_refresh = false ) {
164
+ $feeds_subscriptions = get_transient( 'ai1ec_api_feeds_subscriptions' );
165
+
166
+ if ( $force_refresh || false === $feeds_subscriptions ) {
167
+ $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/feeds/list',
168
+ null,
169
+ true
170
+ );
171
+
172
+ if ( $this->is_response_success( $response ) ) {
173
+ $feeds_subscriptions = (array) $response->body;
174
+ } else {
175
+ $feeds_subscriptions = array();
176
+ }
177
+
178
+ // Save for 5 minutes
179
+ $minutes = 5;
180
+ set_transient( 'ai1ec_api_feeds_subscriptions', $feeds_subscriptions, $minutes * 60 );
181
+ }
182
+
183
+ return $feeds_subscriptions;
184
+ }
185
+
186
+ /**
187
+ * Sync feed subscriptions
188
+ */
189
+ public function get_and_sync_feed_subscriptions() {
190
+ $feeds_subscriptions = $this->get_feed_subscriptions();
191
+
192
+ $db = $this->_registry->get( 'dbi.dbi' );
193
+ $table_name = $db->get_table_name( 'ai1ec_event_feeds' );
194
+
195
+ // Select all feeds
196
+ $rows = $db->select(
197
+ $table_name,
198
+ array(
199
+ 'feed_id',
200
+ 'feed_url',
201
+ 'feed_name',
202
+ 'feed_category',
203
+ 'feed_tags',
204
+ 'comments_enabled',
205
+ 'map_display_enabled',
206
+ 'keep_tags_categories',
207
+ 'keep_old_events',
208
+ 'import_timezone'
209
+ )
210
+ );
211
+
212
+ // Iterate over API response
213
+ foreach( $feeds_subscriptions as $api_feed ) {
214
+ $found = false;
215
+
216
+ foreach ( $rows as $row ) {
217
+ // Check if URL is the same
218
+ if ( trim( $row->feed_url ) === trim( $api_feed->url ) ) {
219
+ $found = true;
220
+
221
+ // Update feed
222
+ $db->update(
223
+ $table_name,
224
+ array(
225
+ 'comments_enabled' => $api_feed->allow_comments,
226
+ 'map_display_enabled' => $api_feed->show_maps,
227
+ 'keep_tags_categories' => $api_feed->import_any_tag_and_categories,
228
+ 'keep_old_events' => $api_feed->preserve_imported_events,
229
+ 'import_timezone' => $api_feed->assign_default_utc,
230
+ 'feed_name' => $api_feed->feed_id
231
+ ),
232
+ array(
233
+ 'feed_id' => $row->feed_id
234
+ )
235
+ );
236
+ }
237
+ }
238
+
239
+ // Not found in local database.. Insert
240
+ if ( ! $found ) {
241
+ $entry = array(
242
+ 'feed_url' => $api_feed->url,
243
+ 'feed_name' => $api_feed->feed_id,
244
+ 'feed_category' => $api_feed->categories,
245
+ 'feed_tags' => $api_feed->tags,
246
+ 'comments_enabled' => $api_feed->allow_comments,
247
+ 'map_display_enabled' => $api_feed->show_maps,
248
+ 'keep_tags_categories' => $api_feed->import_any_tag_and_categories,
249
+ 'keep_old_events' => $api_feed->preserve_imported_events,
250
+ 'import_timezone' => $api_feed->assign_default_utc
251
+ );
252
+ $format = array( '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%d', '%d' );
253
+ $db->insert(
254
+ $table_name,
255
+ $entry,
256
+ $format
257
+ );
258
+ }
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Call the API to subscribe feed
264
+ */
265
+ public function subscribe_feed( $feed_id, $feed_event_uid = '' ) {
266
+ $calendar_id = $this->_get_ticket_calendar();
267
+ if ( 0 >= $calendar_id ) {
268
+ throw new Exception( 'Calendar ID not found' );
269
+ }
270
+
271
+ $response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/subscribe',
272
+ json_encode( array(
273
+ 'feed_id' => $feed_id,
274
+ 'feed_event_uid' => $feed_event_uid
275
+ ) )
276
+ );
277
+
278
+ // Refresh list of subscriptions and limits
279
+ $this->get_subscriptions( true );
280
+
281
+ if ( $this->is_response_success( $response ) ) {
282
+ return $response->body;
283
+ } else {
284
+ $this->save_error_notification(
285
+ $response,
286
+ __( 'We were unable to subscribe feed', AI1EC_PLUGIN_NAME )
287
+ );
288
+ throw new Exception( $this->get_api_error_msg( $response->raw ) );
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Call the API to unsubscribe feed
294
+ */
295
+ public function unsubscribe_feed( $feed_id, $feed_event_uid = '' ) {
296
+ $calendar_id = $this->_get_ticket_calendar();
297
+ if ( 0 >= $calendar_id ) {
298
+ throw new Exception( 'Calendar ID not found' );
299
+ }
300
+
301
+ $response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/unsubscribe',
302
+ json_encode( array(
303
+ 'feed_id' => $feed_id,
304
+ 'feed_event_uid' => $feed_event_uid
305
+ ) )
306
+ );
307
+
308
+ // Refresh list of subscriptions and limits
309
+ $this->get_subscriptions( true );
310
+
311
+ if ( $this->is_response_success( $response ) ) {
312
+ return $response->body;
313
+ } else {
314
+ $this->save_error_notification(
315
+ $response,
316
+ __( 'We were unable to unsubscribe feed', AI1EC_PLUGIN_NAME )
317
+ );
318
+ throw new Exception( $this->get_api_error_msg( $response->raw ) );
319
+ }
320
+ }
321
  }
app/model/api/api-registration.php CHANGED
@@ -10,243 +10,243 @@
10
  */
11
  class Ai1ec_Api_Registration extends Ai1ec_Api_Abstract {
12
 
13
- /**
14
- * Post construction routine.
15
- *
16
- * Override this method to perform post-construction tasks.
17
- *
18
- * @return void Return from this method is ignored.
19
- */
20
- protected function _initialize() {
21
- parent::_initialize();
22
- }
23
-
24
- /**
25
- * @return object Response body in JSON.
26
- */
27
- public function signin() {
28
- $body['email'] = $_POST['ai1ec_email'];
29
- $body['password'] = $_POST['ai1ec_password'];
30
- $body['calendar_type'] = $_POST['ai1ec_calendar_type'];
31
- $response = $this->request_api( 'POST', AI1EC_API_URL . 'auth/authenticate', json_encode( $body ), true, array( 'Authorization' => null ) );
32
- if ( $this->is_response_success( $response ) ) {
33
- $response_body = (array) $response->body;
34
- // Save calendar ID as 0 first, otherwise the auth data won't be saved in the database before creating/finding the calendar
35
- $this->save_ticketing_settings( $response_body['message'], true, $response_body['auth_token'], 0, $body['email'] );
36
- // Now save the calendar ID
37
- $this->save_calendar_id( $this->_get_ticket_calendar() );
38
- $this->has_payment_settings();
39
- $this->get_subscriptions( true );
40
- $this->sync_api_settings();
41
- } else {
42
- $error_message = $this->save_error_notification( $response, __( 'We were unable to Sign you In for Time.ly Network', AI1EC_PLUGIN_NAME ) );
43
- $this->save_ticketing_settings( $error_message, false, '', 0, null );
44
- }
45
- return $response;
46
- }
47
-
48
- /**
49
- * @return object Response body in JSON.
50
- */
51
- public function signup() {
52
- $body['name'] = $_POST['ai1ec_name'];
53
- $body['email'] = $_POST['ai1ec_email'];
54
- $body['password'] = $_POST['ai1ec_password'];
55
- $body['password_confirmation'] = $_POST['ai1ec_password_confirmation'];
56
- $body['phone'] = $_POST['ai1ec_phone'];
57
- $body['calendar_type'] = $_POST['ai1ec_calendar_type'];
58
- $body['terms'] = $_POST['ai1ec_terms'];
59
- $response = $this->request_api( 'POST', AI1EC_API_URL . 'auth/register', json_encode( $body ), true );
60
- if ( $this->is_response_success( $response ) ) {
61
- $response_body = (array) $response->body;
62
- // Save calendar ID as 0 first, otherwise the auth data won't be saved in the database before creating the calendar
63
- $this->save_ticketing_settings( $response_body['Registration'], true, $response_body['auth_token'] , 0, $body['email'] );
64
- // Now save the calendar ID
65
- $this->save_calendar_id( $this->_create_calendar() );
66
- $this->has_payment_settings();
67
- $this->get_subscriptions( true );
68
- $this->sync_api_settings();
69
- } else {
70
- $error_message = $this->save_error_notification( $response, __( 'We were unable to Sign you Up for Time.ly Network', AI1EC_PLUGIN_NAME ) );
71
- $this->save_ticketing_settings( $error_message, false, '', 0, null );
72
- }
73
- return $response;
74
- }
75
-
76
- /**
77
- * @return object Response body in JSON.
78
- */
79
- protected function availability() {
80
- $api_features = get_transient( 'ai1ec_api_features' );
81
-
82
- if ( false === $api_features || ( defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG ) ) {
83
- $response = $this->request_api( 'GET', AI1EC_API_URL . 'feature/availability', null, true );
84
-
85
- if ( $this->is_response_success( $response ) ) {
86
- $api_features = (array) $response->body;
87
- } else {
88
- $api_features = array();
89
- }
90
-
91
- // Save for 5 minutes
92
- $minutes = 5;
93
- set_transient( 'ai1ec_api_features', $api_features, $minutes * 60 );
94
- }
95
-
96
- return $api_features;
97
- }
98
-
99
- protected function is_feature_available( $feature_code ) {
100
- $availability = $this->availability();
101
-
102
- if ( ! is_null( $availability ) ) {
103
- foreach ( $availability as $value ) {
104
- if ( isset( $value->code ) && $feature_code === $value->code
105
- && isset( $value->available ) && true === $value->available ) {
106
- return true;
107
- }
108
- }
109
- }
110
- return false;
111
- }
112
-
113
- /**
114
- * @return object Response body in JSON.
115
- */
116
- protected function settings() {
117
- $calendar_settings = get_transient( 'ai1ec_calendar_settings' );
118
-
119
- if ( false === $calendar_settings || ( defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG ) ) {
120
- $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/settings', null, true );
121
-
122
- if ( $this->is_response_success( $response ) ) {
123
- $calendar_settings = (array) $response->body;
124
- } else {
125
- $calendar_settings = array();
126
- }
127
-
128
- // Save for 5 minutes
129
- $minutes = 5;
130
- set_transient( 'ai1ec_calendar_settings', $calendar_settings, $minutes * 60 );
131
- }
132
-
133
- return $calendar_settings;
134
- }
135
-
136
- public function is_api_sign_up_available() {
137
- return $this->is_feature_available( Ai1ec_Api_Features::CODE_API_ACCESS );
138
- }
139
-
140
- public function is_ticket_available() {
141
- return $this->is_feature_available( Ai1ec_Api_Features::CODE_TICKETING );
142
- }
143
-
144
- public function is_ticket_enabled() {
145
- return $this->has_subscription_active( Ai1ec_Api_Features::CODE_TICKETING );
146
- }
147
-
148
- /**
149
- * Clean the ticketing settings on WP database only
150
- */
151
- public function signout() {
152
- $calendar_id = $this->_get_ticket_calendar( false );
153
- if ( 0 >= $calendar_id ) {
154
- $this->clear_ticketing_settings();
155
- return false;
156
- }
157
- $response = $this->request_api( 'GET', AI1EC_API_URL . "calendars/$calendar_id/signout", null, true );
158
- // Consider "Unauthorized" status (401) a valid response
159
- if ( $this->is_response_success( $response ) || 401 === wp_remote_retrieve_response_code( $response->raw ) ) {
160
- $this->clear_ticketing_settings();
161
- return array( 'message' => '' );
162
- } else {
163
- $error_message = $this->save_error_notification( $response, __( 'We were unable to Sign you Out of Time.ly Network', AI1EC_PLUGIN_NAME ) );
164
- return array( 'message' => $error_message );
165
- }
166
- }
167
-
168
- /**
169
- * @return object Response body from API.
170
- */
171
- public function save_payment_preferences() {
172
- $calendar_id = $this->_get_ticket_calendar();
173
- if ( 0 >= $calendar_id ) {
174
- return false;
175
- }
176
- $settings = array(
177
- 'payment_method' => $_POST['ai1ec_payment_method'],
178
- 'paypal_email' => $_POST['ai1ec_paypal_email'],
179
- 'first_name' => $_POST['ai1ec_first_name'],
180
- 'last_name' => $_POST['ai1ec_last_name'],
181
- 'currency' => $_POST['ai1ec_currency']
182
- );
183
- $custom_headers['content-type'] = 'application/x-www-form-urlencoded';
184
- $response = $this->request_api( 'PUT', AI1EC_API_URL . 'calendars/' . $calendar_id . '/payment',
185
- $settings,
186
- true, //decode response body
187
- $custom_headers
188
- );
189
- if ( $this->is_response_success( $response ) ) {
190
- $this->save_payment_settings( $settings );
191
- $notification = $this->_registry->get( 'notification.admin' );
192
- $notification->store(
193
- __( 'Payment preferences were saved.', AI1EC_PLUGIN_NAME ),
194
- 'updated',
195
- 0,
196
- array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
197
- false
198
- );
199
- return $response->body;
200
- } else {
201
- $this->save_error_notification( $response,
202
- __( 'Payment preferences were not saved.', AI1EC_PLUGIN_NAME )
203
- );
204
- return false;
205
- }
206
- }
207
-
208
- public function _order_comparator( $order1, $order2 ) {
209
- return strcmp( $order1->created_at, $order2->created_at ) * -1;
210
- }
211
-
212
- /**
213
- * @return object Response body in JSON.
214
- */
215
- public function get_purchases() {
216
- $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/sales',
217
- null, //body
218
- true //decode response body
219
- );
220
- if ( $this->is_response_success( $response ) ) {
221
- $result = $response->body;
222
- if ( isset( $result->orders ) ) {
223
- usort( $result->orders, array( "Ai1ec_Api_Registration", "_order_comparator" ) );
224
- return $result->orders;
225
- } else {
226
- return array();
227
- }
228
- } else {
229
- $this->save_error_notification( $response,
230
- __( 'We were unable to get the Sales information from Time.ly Network', AI1EC_PLUGIN_NAME )
231
- );
232
- return array();
233
- }
234
- }
235
-
236
- /**
237
- * Sync settings from API after signing in
238
- */
239
- public function sync_api_settings() {
240
- // Sync feeds subscriptions
241
- try {
242
- $api_feed = $this->_registry->get( 'model.api.api-feeds' );
243
- $api_feed->get_and_sync_feed_subscriptions();
244
- } catch ( Exception $e ) {
245
- $error_message = 'Some feeds were not imported to Time.ly Network. Error: ' . $e->getMessage();
246
-
247
- $notification = $this->_registry->get( 'notification.admin' );
248
- $notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
249
- }
250
- }
251
 
252
  }
10
  */
11
  class Ai1ec_Api_Registration extends Ai1ec_Api_Abstract {
12
 
13
+ /**
14
+ * Post construction routine.
15
+ *
16
+ * Override this method to perform post-construction tasks.
17
+ *
18
+ * @return void Return from this method is ignored.
19
+ */
20
+ protected function _initialize() {
21
+ parent::_initialize();
22
+ }
23
+
24
+ /**
25
+ * @return object Response body in JSON.
26
+ */
27
+ public function signin() {
28
+ $body['email'] = $_POST['ai1ec_email'];
29
+ $body['password'] = $_POST['ai1ec_password'];
30
+ $body['calendar_type'] = $_POST['ai1ec_calendar_type'];
31
+ $response = $this->request_api( 'POST', AI1EC_API_URL . 'auth/authenticate', json_encode( $body ), true, array( 'Authorization' => null ) );
32
+ if ( $this->is_response_success( $response ) ) {
33
+ $response_body = (array) $response->body;
34
+ // Save calendar ID as 0 first, otherwise the auth data won't be saved in the database before creating/finding the calendar
35
+ $this->save_ticketing_settings( $response_body['message'], true, $response_body['auth_token'], 0, $body['email'] );
36
+ // Now save the calendar ID
37
+ $this->save_calendar_id( $this->_get_ticket_calendar() );
38
+ $this->has_payment_settings();
39
+ $this->get_subscriptions( true );
40
+ $this->sync_api_settings();
41
+ } else {
42
+ $error_message = $this->save_error_notification( $response, __( 'We were unable to Sign you In for Time.ly Network', AI1EC_PLUGIN_NAME ) );
43
+ $this->save_ticketing_settings( $error_message, false, '', 0, null );
44
+ }
45
+ return $response;
46
+ }
47
+
48
+ /**
49
+ * @return object Response body in JSON.
50
+ */
51
+ public function signup() {
52
+ $body['name'] = $_POST['ai1ec_name'];
53
+ $body['email'] = $_POST['ai1ec_email'];
54
+ $body['password'] = $_POST['ai1ec_password'];
55
+ $body['password_confirmation'] = $_POST['ai1ec_password_confirmation'];
56
+ $body['phone'] = $_POST['ai1ec_phone'];
57
+ $body['calendar_type'] = $_POST['ai1ec_calendar_type'];
58
+ $body['terms'] = $_POST['ai1ec_terms'];
59
+ $response = $this->request_api( 'POST', AI1EC_API_URL . 'auth/register', json_encode( $body ), true );
60
+ if ( $this->is_response_success( $response ) ) {
61
+ $response_body = (array) $response->body;
62
+ // Save calendar ID as 0 first, otherwise the auth data won't be saved in the database before creating the calendar
63
+ $this->save_ticketing_settings( $response_body['Registration'], true, $response_body['auth_token'] , 0, $body['email'] );
64
+ // Now save the calendar ID
65
+ $this->save_calendar_id( $this->_create_calendar() );
66
+ $this->has_payment_settings();
67
+ $this->get_subscriptions( true );
68
+ $this->sync_api_settings();
69
+ } else {
70
+ $error_message = $this->save_error_notification( $response, __( 'We were unable to Sign you Up for Time.ly Network', AI1EC_PLUGIN_NAME ) );
71
+ $this->save_ticketing_settings( $error_message, false, '', 0, null );
72
+ }
73
+ return $response;
74
+ }
75
+
76
+ /**
77
+ * @return object Response body in JSON.
78
+ */
79
+ protected function availability() {
80
+ $api_features = get_transient( 'ai1ec_api_features' );
81
+
82
+ if ( false === $api_features || ( defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG ) ) {
83
+ $response = $this->request_api( 'GET', AI1EC_API_URL . 'feature/availability', null, true );
84
+
85
+ if ( $this->is_response_success( $response ) ) {
86
+ $api_features = (array) $response->body;
87
+ } else {
88
+ $api_features = array();
89
+ }
90
+
91
+ // Save for 5 minutes
92
+ $minutes = 5;
93
+ set_transient( 'ai1ec_api_features', $api_features, $minutes * 60 );
94
+ }
95
+
96
+ return $api_features;
97
+ }
98
+
99
+ protected function is_feature_available( $feature_code ) {
100
+ $availability = $this->availability();
101
+
102
+ if ( ! is_null( $availability ) ) {
103
+ foreach ( $availability as $value ) {
104
+ if ( isset( $value->code ) && $feature_code === $value->code
105
+ && isset( $value->available ) && true === $value->available ) {
106
+ return true;
107
+ }
108
+ }
109
+ }
110
+ return false;
111
+ }
112
+
113
+ /**
114
+ * @return object Response body in JSON.
115
+ */
116
+ protected function settings() {
117
+ $calendar_settings = get_transient( 'ai1ec_calendar_settings' );
118
+
119
+ if ( false === $calendar_settings || ( defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG ) ) {
120
+ $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/settings', null, true );
121
+
122
+ if ( $this->is_response_success( $response ) ) {
123
+ $calendar_settings = (array) $response->body;
124
+ } else {
125
+ $calendar_settings = array();
126
+ }
127
+
128
+ // Save for 5 minutes
129
+ $minutes = 5;
130
+ set_transient( 'ai1ec_calendar_settings', $calendar_settings, $minutes * 60 );
131
+ }
132
+
133
+ return $calendar_settings;
134
+ }
135
+
136
+ public function is_api_sign_up_available() {
137
+ return $this->is_feature_available( Ai1ec_Api_Features::CODE_API_ACCESS );
138
+ }
139
+
140
+ public function is_ticket_available() {
141
+ return $this->is_feature_available( Ai1ec_Api_Features::CODE_TICKETING );
142
+ }
143
+
144
+ public function is_ticket_enabled() {
145
+ return $this->has_subscription_active( Ai1ec_Api_Features::CODE_TICKETING );
146
+ }
147
+
148
+ /**
149
+ * Clean the ticketing settings on WP database only
150
+ */
151
+ public function signout() {
152
+ $calendar_id = $this->_get_ticket_calendar( false );
153
+ if ( 0 >= $calendar_id ) {
154
+ $this->clear_ticketing_settings();
155
+ return false;
156
+ }
157
+ $response = $this->request_api( 'GET', AI1EC_API_URL . "calendars/$calendar_id/signout", null, true );
158
+ // Consider "Unauthorized" status (401) a valid response
159
+ if ( $this->is_response_success( $response ) || 401 === wp_remote_retrieve_response_code( $response->raw ) ) {
160
+ $this->clear_ticketing_settings();
161
+ return array( 'message' => '' );
162
+ } else {
163
+ $error_message = $this->save_error_notification( $response, __( 'We were unable to Sign you Out of Time.ly Network', AI1EC_PLUGIN_NAME ) );
164
+ return array( 'message' => $error_message );
165
+ }
166
+ }
167
+
168
+ /**
169
+ * @return object Response body from API.
170
+ */
171
+ public function save_payment_preferences() {
172
+ $calendar_id = $this->_get_ticket_calendar();
173
+ if ( 0 >= $calendar_id ) {
174
+ return false;
175
+ }
176
+ $settings = array(
177
+ 'payment_method' => $_POST['ai1ec_payment_method'],
178
+ 'paypal_email' => $_POST['ai1ec_paypal_email'],
179
+ 'first_name' => $_POST['ai1ec_first_name'],
180
+ 'last_name' => $_POST['ai1ec_last_name'],
181
+ 'currency' => $_POST['ai1ec_currency']
182
+ );
183
+ $custom_headers['content-type'] = 'application/x-www-form-urlencoded';
184
+ $response = $this->request_api( 'PUT', AI1EC_API_URL . 'calendars/' . $calendar_id . '/payment',
185
+ $settings,
186
+ true, //decode response body
187
+ $custom_headers
188
+ );
189
+ if ( $this->is_response_success( $response ) ) {
190
+ $this->save_payment_settings( $settings );
191
+ $notification = $this->_registry->get( 'notification.admin' );
192
+ $notification->store(
193
+ __( 'Payment preferences were saved.', AI1EC_PLUGIN_NAME ),
194
+ 'updated',
195
+ 0,
196
+ array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
197
+ false
198
+ );
199
+ return $response->body;
200
+ } else {
201
+ $this->save_error_notification( $response,
202
+ __( 'Payment preferences were not saved.', AI1EC_PLUGIN_NAME )
203
+ );
204
+ return false;
205
+ }
206
+ }
207
+
208
+ public function _order_comparator( $order1, $order2 ) {
209
+ return strcmp( $order1->created_at, $order2->created_at ) * -1;
210
+ }
211
+
212
+ /**
213
+ * @return object Response body in JSON.
214
+ */
215
+ public function get_purchases() {
216
+ $response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/sales',
217
+ null, //body
218
+ true //decode response body
219
+ );
220
+ if ( $this->is_response_success( $response ) ) {
221
+ $result = $response->body;
222
+ if ( isset( $result->orders ) ) {
223
+ usort( $result->orders, array( "Ai1ec_Api_Registration", "_order_comparator" ) );
224
+ return $result->orders;
225
+ } else {
226
+ return array();
227
+ }
228
+ } else {
229
+ $this->save_error_notification( $response,
230
+ __( 'We were unable to get the Sales information from Time.ly Network', AI1EC_PLUGIN_NAME )
231
+ );
232
+ return array();
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Sync settings from API after signing in
238
+ */
239
+ public function sync_api_settings() {
240
+ // Sync feeds subscriptions
241
+ try {
242
+ $api_feed = $this->_registry->get( 'model.api.api-feeds' );
243
+ $api_feed->get_and_sync_feed_subscriptions();
244
+ } catch ( Exception $e ) {
245
+ $error_message = 'Some feeds were not imported to Time.ly Network. Error: ' . $e->getMessage();
246
+
247
+ $notification = $this->_registry->get( 'notification.admin' );
248
+ $notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
249
+ }
250
+ }
251
 
252
  }
app/model/api/api-settings.php CHANGED
@@ -1,4 +1,4 @@
1
  <?php
2
  class Ai1ec_Api_Settings {
3
- const FACEBOOK_API_KEY = 'facebook_api_key';
4
  }
1
  <?php
2
  class Ai1ec_Api_Settings {
3
+ const FACEBOOK_API_KEY = 'facebook_api_key';
4
  }
app/model/api/api-ticketing.php CHANGED
@@ -10,648 +10,648 @@
10
  */
11
  class Ai1ec_Api_Ticketing extends Ai1ec_Api_Abstract {
12
 
13
- const API_EVENT_DATA = '_ai1ec_api_event_id';
14
-
15
- const ATTR_EVENT_ID = 'api_event_id';
16
- const ATTR_THUMBNAIL_ID = 'thumbnail_id';
17
- const ATTR_ICS_CHECKOUT_URL = 'ics_checkout_url';
18
- const ATTR_ICS_API_URL = 'ics_api_url';
19
- const ATTR_ACCOUNT = 'account';
20
- const ATTR_CALENDAR_ID = 'calendar_id';
21
- const ATTR_CURRENCY = 'currency';
22
-
23
- const MAX_TICKET_TO_BUY_DEFAULT = 25;
24
-
25
- /**
26
- * Post construction routine.
27
- *
28
- * Override this method to perform post-construction tasks.
29
- *
30
- * @return void Return from this method is ignored.
31
- */
32
- protected function _initialize() {
33
- parent::_initialize();
34
- }
35
-
36
- /**
37
- * Count the valid Tickets Types (not removed) included inside the Ticket Event
38
- */
39
- private function _count_valid_tickets( $post_ticket_types ) {
40
- if (false === isset( $post_ticket_types ) || 0 === count( $post_ticket_types ) ) {
41
- return 0;
42
- } else {
43
- $count = 0;
44
- foreach ( $post_ticket_types as $ticket_type_ite ) {
45
- if ( !isset( $ticket_type_ite['remove'] ) ) {
46
- $count++;
47
- }
48
- }
49
- return $count;
50
- }
51
- }
52
-
53
- /**
54
- * Return an error if the Ticket Event is not owned by the Current account
55
- */
56
- private function _prevent_update_ticket_event( Ai1ec_Event $event, $ajax_action = false ) {
57
- if ( $this->is_ticket_event_imported( $event->get( 'post_id' ) ) ) {
58
- //prevent changes on Ticket Events that were imported
59
- $error = __( 'This Event was replicated from another site. Changes are not allowed.', AI1EC_PLUGIN_NAME );
60
- if ( ! $ajax_action ) {
61
- $notification = $this->_registry->get( 'notification.admin' );
62
- $notification->store(
63
- $error,
64
- 'error',
65
- 0,
66
- array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
67
- false
68
- );
69
- }
70
- return $error;
71
- }
72
- if ( $this->is_ticket_event_from_another_account( $event->get( 'post_id' ) ) ) {
73
- //prevent changes on Ticket Events that were imported
74
- $error = sprintf(
75
- __( 'This Event was created using a different account %s. Changes are not allowed.', AI1EC_PLUGIN_NAME ),
76
- $this->get_api_event_account( $event->get( 'post_id' ) )
77
- );
78
- if ( ! $ajax_action ) {
79
- $notification = $this->_registry->get( 'notification.admin' );
80
- $notification->store(
81
- $error,
82
- 'error',
83
- 0,
84
- array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
85
- false
86
- );
87
- }
88
- return $error;
89
- }
90
- return null;
91
- }
92
-
93
- /**
94
- * Run some validations inside the _POST request to check if the Event
95
- * submmited is a valid event for Tickets
96
- * @return NULL in case of success or a Message in case of error
97
- */
98
- private function _is_valid_post( Ai1ec_Event $event, $updating ) {
99
- $message = null;
100
- if ( ( isset( $_POST['ai1ec_rdate'] ) && ! empty( $_POST['ai1ec_rdate'] ) ) ||
101
- ( isset( $_POST['ai1ec_repeat'] ) && ! empty( $_POST['ai1ec_repeat'] ) )
102
- ) {
103
- $message = __( 'The Repeat option was selected but recurrence is not supported by Event with Tickets.', AI1EC_PLUGIN_NAME );
104
- } else if ( isset( $_POST['ai1ec_tickets_loading_error'] ) ) {
105
- //do not update tickets because is unsafe. There was a problem to load the tickets,
106
- //the customer received the same message when the event was loaded.
107
- $message = $_POST['ai1ec_tickets_loading_error'];
108
- } else if ( false === ai1ec_is_blank( $event->get( 'ical_feed_url' ) ) ) {
109
- //prevent ticket creating inside Regular Events Imported events
110
- $message = __( 'This Event was replicated from another site. Any changes on Tickets were discarded.', AI1EC_PLUGIN_NAME );
111
- } else {
112
- $error = $this->_prevent_update_ticket_event( $event );
113
- if ( null !== $error ) {
114
- $message = $error;
115
- } else if ( ! isset( $_POST['ai1ec_tickets'] ) || 0 === $this->_count_valid_tickets( $_POST['ai1ec_tickets'] ) ) {
116
- $message = __( 'The Event has the cost option Ticket selected but no ticket was included.', AI1EC_PLUGIN_NAME );
117
- } else if ( false === $this->has_payment_settings() ) {
118
- $message = __( 'You need to save the payments settings to create ticket events.', AI1EC_PLUGIN_NAME );
119
- } else if ( ! isset( $_POST['tax_options'] ) && ! $updating ) {
120
- $message = __( 'Tax and Invoice options are required.', AI1EC_PLUGIN_NAME );
121
- }
122
- }
123
- if ( null !== $message ) {
124
- $notification = $this->_registry->get( 'notification.admin' );
125
- $notification->store( $message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
126
- return $message;
127
- }
128
- return null;
129
- }
130
-
131
- /**
132
- * Create or update a Ticket Event on API server
133
- * @return object Response body in JSON.
134
- */
135
- public function store_event( Ai1ec_Event $event, WP_Post $post, $updating ) {
136
-
137
- $error = $this->_is_valid_post( $event, $updating );
138
- if ( null !== $error ) {
139
- return $error;
140
- }
141
- $api_event_id = $this->get_api_event_id( $event->get( 'post_id' ) );
142
- $is_new = ! $api_event_id;
143
- $fields = array( 'visibility' => $_POST['visibility'] );
144
- if ( isset( $_POST['tax_options'] ) ) {
145
- $fields['tax_options'] = $_POST['tax_options'];
146
- }
147
- $body_data = $this->parse_event_fields_to_api_structure(
148
- $event,
149
- $post,
150
- $_POST['ai1ec_tickets'],
151
- $fields
152
- );
153
- $url = AI1EC_API_URL . 'events';
154
- if ( $api_event_id ) {
155
- $url = $url . '/' . $api_event_id;
156
- }
157
-
158
- //get the thumbnail id saved previously
159
- $api_data = $this->get_api_event_data( $event->get( 'post_id' ) );
160
- if ( isset( $api_data[self::ATTR_THUMBNAIL_ID] ) ) {
161
- $event_thumbnail_id = $api_data[self::ATTR_THUMBNAIL_ID];
162
- } else {
163
- $event_thumbnail_id = 0;
164
- }
165
- //get the current thumbnail id
166
- $post_thumbnail_id = get_post_thumbnail_id( $event->get( 'post_id' ) );
167
- if ( false === isset( $post_thumbnail_id ) ) {
168
- $post_thumbnail_id = 0;
169
- }
170
- $update_image = ( $event_thumbnail_id !== $post_thumbnail_id );
171
- $payload = '';
172
- $custom_headers = null;
173
-
174
- if ( true === $update_image && 0 < $post_thumbnail_id ) {
175
- $boundary = wp_generate_password( 24 );
176
- $custom_headers['content-type'] = 'multipart/form-data; boundary=' . $boundary;
177
- $body_data['update_image'] = '1';
178
- foreach ($body_data as $key => $value) {
179
- if ( is_array( $value ) ) {
180
- $index = 0;
181
- foreach ( $value as $arr_key => $arr_value ) {
182
- if ( is_array( $arr_value ) ) {
183
- foreach ( $arr_value as $child_key => $child_value ) {
184
- $payload .= '--' . $boundary;
185
- $payload .= "\r\n";
186
- $payload .= 'Content-Disposition: form-data; name="' . $key . '[' . $index . '][' . $child_key . ']"' . "\r\n";
187
- $payload .= "\r\n";
188
- $payload .= $child_value;
189
- $payload .= "\r\n";
190
- }
191
- } else {
192
- $payload .= '--' . $boundary;
193
- $payload .= "\r\n";
194
- $payload .= 'Content-Disposition: form-data; name="tax_options[' . $arr_key . ']"' . "\r\n";
195
- $payload .= "\r\n";
196
- $payload .= $arr_value;
197
- $payload .= "\r\n";
198
- }
199
- $index++;
200
- }
201
- } else {
202
- $payload .= '--' . $boundary;
203
- $payload .= "\r\n";
204
- $payload .= 'Content-Disposition: form-data; name="' . $key . '"' . "\r\n";
205
- $payload .= "\r\n";
206
- $payload .= $value;
207
- $payload .= "\r\n";
208
- }
209
- }
210
- $file_path = get_attached_file ( $post_thumbnail_id );
211
- $file_type = wp_check_filetype ( $file_path );
212
- $payload .= '--' . $boundary;
213
- $payload .= "\r\n";
214
- $payload .= 'Content-Disposition: form-data; name="image_id"; filename="' . basename( $file_path ) . '"' . "\r\n";
215
- $payload .= 'Content-Type: ' . $file_type['type'] . "\r\n";
216
- $payload .= "\r\n";
217
- $payload .= file_get_contents( $file_path );
218
- $payload .= "\r\n";
219
- $payload .= '--' . $boundary . '--';
220
- } else {
221
- $body_data['update_image'] = (true === $update_image) ? '1' : '0';
222
- $payload = json_encode( $body_data );
223
- }
224
- $response = $this->request_api( 'POST', $url, $payload,
225
- true, //true to decode response body
226
- $custom_headers
227
- );
228
- if ( $this->is_response_success( $response ) ) {
229
- $api_event_id = $response->body->id;
230
- if ( isset( $response->body->currency ) ) {
231
- $currency = $response->body->currency;
232
- } else {
233
- $currency = 'USD';
234
- }
235
- $currency = $response->body->currency;
236
- if ( $post_thumbnail_id <= 0 ) {
237
- $post_thumbnail_id = null;
238
- }
239
- $this->save_api_event_data( $event->get( 'post_id') , $api_event_id, null, null, $currency, $post_thumbnail_id );
240
- return true;
241
- } else {
242
- $error_message = '';
243
- if ( $is_new ) {
244
- $error_message = __( 'We were unable to create the Event on Time.ly Ticketing', AI1EC_PLUGIN_NAME );
245
- } else {
246
- $error_message = __( 'We were unable to update the Event on Time.ly Ticketing', AI1EC_PLUGIN_NAME );
247
- }
248
- return $this->save_error_notification( $response, $error_message );
249
- }
250
- }
251
-
252
- /**
253
- * Parse the fields of an Event to the structure used by API
254
- */
255
- public function parse_event_fields_to_api_structure( Ai1ec_Event $event , WP_Post $post, $post_ticket_types, $api_fields_values ) {
256
- $calendar_id = $this->_get_ticket_calendar();
257
- if ( $calendar_id <= 0 ) {
258
- return null;
259
- }
260
-
261
- //fields of ai1ec events table used by API
262
- $body['latitude'] = $event->get( 'latitude' );
263
- $body['longitude'] = $event->get( 'longitude' );
264
- $body['post_id'] = $event->get( 'post_id' );
265
- $body['calendar_id'] = $calendar_id;
266
- $body['dtstart'] = $event->get( 'start' )->format_to_javascript();
267
- $body['dtend'] = $event->getenddate()->format_to_javascript();
268
- $body['timezone'] = $event->get( 'timezone_name' );
269
- $body['venue_name'] = $event->get( 'venue' );
270
- $body['address'] = $event->get( 'address' );
271
- $body['city'] = $event->get( 'city' );
272
- $body['province'] = $event->get( 'province' );
273
- $body['postal_code'] = $event->get( 'postal_code' );
274
- $body['country'] = $event->get( 'country' );
275
- $body['contact_name'] = $event->get( 'contact_name' );
276
- $body['contact_phone'] = $event->get( 'contact_phone' );
277
- $body['contact_email'] = $event->get( 'contact_email' );
278
- $body['contact_website'] = $event->get( 'contact_url' );
279
- $body['uid'] = $event->get_uid();
280
- $body['title'] = $post->post_title;
281
- $body['description'] = $post->post_content;
282
- $body['url'] = get_permalink( $post->ID );
283
- $body['status'] = $post->post_status;
284
-
285
- $utc_current_time = $this->_registry->get( 'date.time')->format_to_javascript();
286
- $body['created_at'] = $utc_current_time;
287
- $body['updated_at'] = $utc_current_time;
288
-
289
- //removing blank values
290
- foreach ($body as $key => $value) {
291
- if ( ai1ec_is_blank( $value ) ) {
292
- unset( $body[ $key ] );
293
- }
294
- }
295
-
296
- if ( is_null( $api_fields_values ) || 0 == count( $api_fields_values ) ) {
297
- $api_fields_values = array( 'status' => 'closed', 'ai1ec_version' => AI1EC_VERSION );
298
- } else {
299
- if ( ! isset( $api_fields_values['ai1ec_version'] ) ) {
300
- $api_fields_values['ai1ec_version'] = AI1EC_VERSION;
301
- }
302
- foreach ( $api_fields_values as $key => $value ) {
303
- $body[$key] = $api_fields_values[$key];
304
- if ( 'visibility' === $key ) {
305
- if ( 0 === strcasecmp( 'private', $value ) ) {
306
- $body['status'] = 'private';
307
- } else if ( 0 === strcasecmp( 'password', $value ) ) {
308
- $body['status'] = 'password';
309
- }
310
- }
311
- }
312
- }
313
-
314
- $tickets_types = array();
315
- if ( ! is_null( $post_ticket_types ) ) {
316
- $index = 0;
317
- foreach ( $post_ticket_types as $ticket_type_ite ) {
318
- if ( false === isset( $ticket_type_ite['id'] ) &&
319
- isset( $ticket_type_ite['remove'] ) ) {
320
- //ignoring new tickets that didn't go to api yet
321
- continue;
322
- }
323
- $tickets_types[$index++] = $this->_parse_tickets_type_post_to_api_structure(
324
- $ticket_type_ite,
325
- $event
326
- );
327
- }
328
- }
329
- $body['ticket_types'] = $tickets_types;
330
-
331
- return $body;
332
- }
333
-
334
- /**
335
- * Parse the fields of a Ticket Type to the structure used by API
336
- */
337
- protected function _parse_tickets_type_post_to_api_structure( $ticket_type_ite, $event ) {
338
- $utc_current_time = $this->_registry->get( 'date.time' )->format_to_javascript();
339
- if ( isset( $ticket_type_ite['id'] ) ) {
340
- $ticket_type['id'] = $ticket_type_ite['id'];
341
- $ticket_type['created_at'] = $ticket_type_ite['created_at'];
342
- } else {
343
- $ticket_type['created_at'] = $utc_current_time;
344
- }
345
- if ( isset( $ticket_type_ite['remove'] ) ) {
346
- $ticket_type['deleted_at'] = $utc_current_time;
347
- }
348
- $ticket_type['name'] = $ticket_type_ite['ticket_name'];
349
- $ticket_type['description'] = $ticket_type_ite['description'];
350
- $ticket_type['price'] = $ticket_type_ite['ticket_price'];
351
- if ( 0 === strcasecmp( 'on', $ticket_type_ite['unlimited'] ) ) {
352
- $ticket_type['quantity'] = null;
353
- } else {
354
- $ticket_type['quantity'] = $ticket_type_ite['quantity'];
355
- }
356
- $ticket_type['buy_min_qty'] = $ticket_type_ite['buy_min_limit'];
357
- if ( ai1ec_is_blank( $ticket_type_ite['buy_max_limit'] ) ) {
358
- $ticket_type['buy_max_qty'] = null;
359
- } else {
360
- $ticket_type['buy_max_qty'] = $ticket_type_ite['buy_max_limit'];
361
- }
362
- if ( 0 === strcasecmp( 'on', $ticket_type_ite['availibility'] ) ) {
363
- //immediate availability
364
- $timezone_start_time = $this->_registry->get( 'date.time' );
365
- $timezone_start_time->set_timezone( $event->get('timezone_name') );
366
- $ticket_type['immediately'] = true;
367
- $ticket_type['sale_start_date'] = $timezone_start_time->format_to_javascript( $event->get('timezone_name') );
368
- $ticket_type['sale_end_date'] = $event->get( 'end' )->format_to_javascript();
369
- } else {
370
- $ticket_type['immediately'] = false;
371
- $ticket_type['sale_start_date'] = $ticket_type_ite['ticket_sale_start_date'];
372
- $ticket_type['sale_end_date'] = $ticket_type_ite['ticket_sale_end_date'];
373
- }
374
- $ticket_type['updated_at'] = $utc_current_time;
375
- $ticket_type['status'] = $ticket_type_ite['ticket_status'];
376
- return $ticket_type;
377
- }
378
-
379
- /**
380
- * Unparse the fields of API structure to the Ticket Type
381
- */
382
- protected function _unparse_tickets_type_from_api_structure( $ticket_type_api ) {
383
- $ticket_type = $ticket_type_api;
384
- $ticket_type->ticket_name = $ticket_type_api->name;
385
- $ticket_type->ticket_price = $ticket_type_api->price;
386
- $ticket_type->buy_min_limit = $ticket_type_api->buy_min_qty;
387
- if ( null === $ticket_type_api->buy_max_qty ) {
388
- $ticket_type->buy_max_limit = self::MAX_TICKET_TO_BUY_DEFAULT;
389
- } else {
390
- $ticket_type->buy_max_limit = $ticket_type_api->buy_max_qty;
391
- }
392
- if ( true === ( ( bool ) $ticket_type_api->immediately ) ) {
393
- $ticket_type->availibility = 'on';
394
- } else {
395
- $ticket_type->availibility = 'off';
396
- }
397
- $ticket_type->ticket_sale_start_date = $ticket_type_api->sale_start_date; //YYYY-MM-YY HH:NN:SS
398
- $ticket_type->ticket_sale_end_date = $ticket_type_api->sale_end_date; //YYYY-MM-YY HH:NN:SS
399
- $ticket_type->ticket_status = $ticket_type_api->status;
400
- if ( 'open' === $ticket_type_api->status ) {
401
- $ticket_type->ticket_status_label = __( 'Open for sale', AI1EC_PLUGIN_NAME );
402
- } else if ( 'closed' === $ticket_type_api->status ) {
403
- $ticket_type->ticket_status_label = __( 'Sale ended', AI1EC_PLUGIN_NAME );
404
- } else if ( 'canceled' === $ticket_type_api->status ) {
405
- $ticket_type->ticket_status_label = __( 'Canceled', AI1EC_PLUGIN_NAME );
406
- } else {
407
- $ticket_type->ticket_status_label = $ticket_type_api->status;
408
- }
409
- if ( false === isset( $ticket_type_api->quantity ) ||
410
- null === $ticket_type_api->quantity ) {
411
- $ticket_type->unlimited = 'on';
412
- } else {
413
- $ticket_type->unlimited = 'off';
414
- }
415
- $ticket_type->ticket_type_id = $ticket_type_api->id;
416
- $ticket_type->available = $ticket_type_api->available;
417
- $ticket_type->availability = $this->_parse_availability_message( $ticket_type_api->availability );
418
-
419
- //derived property to set the max quantity of dropdown
420
- if ( $ticket_type->available !== null ) {
421
- if ( $ticket_type->available > $ticket_type->buy_max_limit ) {
422
- $ticket_type->buy_max_available = $ticket_type->buy_max_limit;
423
- } else {
424
- $ticket_type->buy_max_available = $ticket_type->available;
425
- }
426
- } else {
427
- $ticket_type->buy_max_available = $ticket_type->buy_max_limit;
428
- }
429
- return $ticket_type;
430
- }
431
-
432
- public function _parse_availability_message( $availability ){
433
- if ( ai1ec_is_blank ( $availability ) ) {
434
- return null;
435
- } else {
436
- switch ($availability) {
437
- case 'past_event':
438
- return __( 'Past Event' );
439
- case 'event_closed':
440
- return __( 'Event closed' );
441
- case 'not_available_yet':
442
- return __( 'Not available yet' );
443
- case 'sale_closed':
444
- return __( 'Sale closed' );
445
- case 'sold_out':
446
- return __( 'Sold out' );
447
- default:
448
- return __( 'Not available' );
449
- }
450
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  }
452
 
453
- public function get_event( $post_id ) {
454
- $api_event_id = $this->get_api_event_id( $post_id );
455
- if ( ! $api_event_id ) {
456
- return (object) array( 'data' => array() );
457
- }
458
- $response = $this->request_api( 'GET', $this->get_api_event_url( $post_id ) . 'events/' . $api_event_id . '/edit' );
459
- if ( $this->is_response_success( $response ) ) {
460
- if ( isset( $response->body->ticket_types ) ) {
461
- foreach ( $response->body->ticket_types as $ticket_api ) {
462
- $this->_unparse_tickets_type_from_api_structure( $ticket_api );
463
- }
464
- }
465
- return (object) array( 'data' => $response->body );
466
- } else {
467
- $error_message = $this->_transform_error_message(
468
- __( 'We were unable to get the Event Details from Time.ly Ticketing', AI1EC_PLUGIN_NAME ),
469
- $response->raw, $response->url,
470
- true
471
- );
472
- return (object) array( 'data' => array(), 'error' => $error_message );
473
- }
474
- }
475
-
476
- /**
477
- * @return string JSON.
478
- */
479
- public function get_ticket_types( $post_id, $get_canceled = true ) {
480
- $api_event_id = $this->get_api_event_id( $post_id );
481
- if ( ! $api_event_id ) {
482
- return json_encode( array( 'data' => array() ) );
483
- }
484
- $response = $this->request_api( 'GET', $this->get_api_event_url( $post_id ) . 'events/' . $api_event_id . '/ticket_types',
485
- array( 'get_canceled' => ( true === $get_canceled ? 1 : 0 ) )
486
- );
487
- if ( $this->is_response_success( $response ) ) {
488
- if ( isset( $response->body->ticket_types ) ) {
489
- foreach ( $response->body->ticket_types as $ticket_api ) {
490
- $this->_unparse_tickets_type_from_api_structure( $ticket_api );
491
- }
492
- return json_encode( array( 'data' => $response->body->ticket_types ) );
493
- } else {
494
- return json_encode( array( 'data' => array() ) );
495
- }
496
- } else {
497
- $error_message = $this->_transform_error_message(
498
- __( 'We were unable to get the Tickets Details from Time.ly Ticketing', AI1EC_PLUGIN_NAME ),
499
- $response->raw, $response->url,
500
- true
501
- );
502
- return json_encode( array( 'data' => array(), 'error' => $error_message ) );
503
- }
504
- }
505
-
506
- /**
507
- * @return object Response body in JSON.
508
- */
509
- public function get_tickets( $post_id ) {
510
- $api_event_id = $this->get_api_event_id( $post_id );
511
- if ( ! $api_event_id ) {
512
- return json_encode( array( 'data' => array() ) );
513
- }
514
- $request = array(
515
- 'headers' => $this->_get_headers(),
516
- 'timeout' => parent::DEFAULT_TIMEOUT
517
- );
518
- $url = $this->get_api_event_url( $post_id ) . 'events/' . $api_event_id . '/tickets';
519
- $response = wp_remote_get( $url, $request );
520
- $response_code = wp_remote_retrieve_response_code( $response );
521
- if ( 200 === $response_code ) {
522
- return $response['body'];
523
- } else {
524
- $error_message = $this->_transform_error_message(
525
- __( 'We were unable to get the Tickets Attendees from Time.ly Ticketing', AI1EC_PLUGIN_NAME ),
526
- $response, $url,
527
- true
528
- );
529
- return json_encode( array( 'data' => array(), 'error' => $error_message ) );
530
- }
531
- }
532
-
533
- /**
534
- * Check if a Ticket Event was imported from an ICS Feed
535
- */
536
- public function is_ticket_event_imported( $post_id ) {
537
- $data = $this->get_api_event_data( $post_id );
538
- if ( isset( $data[self::ATTR_EVENT_ID] ) && isset( $data[self::ATTR_ICS_API_URL] ) ) {
539
- return ( ! ai1ec_is_blank ( $data[self::ATTR_ICS_API_URL] ) );
540
- } else {
541
- return false;
542
- }
543
-
544
- }
545
-
546
- /**
547
- * Check if the Ticket Event was created using a different account
548
- * The user probably created the event from one account, signed out and
549
- * is currently signed in with a new account
550
- */
551
- public function is_ticket_event_from_another_account( $post_id ) {
552
- $data = $this->get_api_event_data( $post_id );
553
- if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
554
- if ( isset( $data[self::ATTR_ACCOUNT] ) ) {
555
- return ( $this->get_current_account() != $data[self::ATTR_ACCOUNT] );
556
- } else {
557
- return false;
558
- }
559
- } else {
560
- return false;
561
- }
562
- }
563
-
564
- /**
565
- * Get the API account where the event was created
566
- * @param int $post_id Post ID
567
- * @param bool $default_null True to return NULL if the value does not exist, false to return the configured API URL
568
- */
569
- public function get_api_event_account( $post_id ) {
570
- $data = $this->get_api_event_data( $post_id );
571
- if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
572
- if ( isset( $data[self::ATTR_ACCOUNT] ) ) {
573
- return $data[self::ATTR_ACCOUNT];
574
- } else {
575
- return null;
576
- }
577
- } else {
578
- return null;
579
- }
580
- }
581
-
582
- /**
583
  * Check if the response that came from the API is the event not found
584
  */
585
- private function _is_event_notfound_error( $response ) {
586
- if ( isset( $response->response_code ) && 404 === $response->response_code ) {
587
- if ( isset( $response->body ) ) {
588
- if ( is_array( $response->body ) &&
589
- isset( $response->body['message'] ) ) {
590
- if ( false !== stripos( $response->body['message'], 'event not found') ) {
591
- return true;
592
- }
593
- }
594
- }
595
- }
596
- return false;
597
  }
598
 
599
- /**
600
- * @return NULL in case of success or an error string in case of error
601
- */
602
  public function update_api_event_fields( WP_Post $post, $api_fields_values, $post_action = 'trash', $ajax_action = false ) {
603
- $post_id = $post->ID;
604
- $api_event_id = $this->get_api_event_id( $post_id );
605
- if ( ! $api_event_id ) {
606
- return null;
607
- }
608
- try {
609
- $event = $this->_registry->get( 'model.event', $post_id );
610
- } catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
611
- $message = __( 'Event not found inside the database.', AI1EC_PLUGIN_NAME );
612
- $notification = $this->_registry->get( 'notification.admin' );
613
- $notification->store( $message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
614
- return $message;
615
- }
616
- if ( 'update' === $post_action ) {
617
- $error = $this->_prevent_update_ticket_event( $event, $ajax_action );
618
- if ( null !== $error ) {
619
- return $error;
620
- }
621
- } else {
622
- if ( $this->is_ticket_event_imported( $post_id ) ) {
623
- return null;
624
- }
625
- if ( $this->is_ticket_event_from_another_account( $post_id ) ) {
626
- return null;
627
- }
628
- }
629
- $headers = $this->_get_headers();
630
- $body_data = $this->parse_event_fields_to_api_structure(
631
- $event,
632
- $post,
633
- null, //does not update ticket types, just chaging the api fields specified
634
- $api_fields_values
635
- );
636
- $response = $this->request_api( 'POST',
637
- AI1EC_API_URL . "events/$api_event_id",
638
- json_encode( $body_data ),
639
- true //true to decode response body
640
- );
641
- if ( ! $this->is_response_success( $response ) ) {
642
- if ( $this->_is_event_notfound_error( $response ) ) {
643
- if ( isset( $api_fields_values['status'] ) &&
644
- 'trash' === $api_fields_values['status'] ) {
645
- //this is an exception, the event was deleted on API server, but for some reason
646
- //the metada was not unset, in this case leave the event be
647
- //move to trash
648
- return null;
649
- }
650
- }
651
- $message = $this->save_error_notification( $response, __( 'We were unable to Update the Event on Time.ly Network', AI1EC_PLUGIN_NAME ) );
652
- return $message;
653
- } else {
654
- return null;
655
  }
656
  }
657
 
@@ -660,242 +660,242 @@ class Ai1ec_Api_Ticketing extends Ai1ec_Api_Abstract {
660
  * @return NULL in case of success or an error string in case of error
661
  */
662
  public function delete_api_event( $post_id, $post_action = 'delete', $ajax_action = false ) {
663
- $api_event_id = $this->get_api_event_id( $post_id );
664
- if ( ! $api_event_id ) {
665
- return null;
666
- }
667
- if ( 'update' === $post_action ) {
668
- try {
669
- $event = $this->_registry->get( 'model.event', $post_id );
670
- } catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
671
- $message = __( 'Event not found inside the database.', AI1EC_PLUGIN_NAME );
672
- $notification = $this->_registry->get( 'notification.admin' );
673
- $notification->store( $message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
674
- return $message;
675
- }
676
- $error = $this->_prevent_update_ticket_event( $event, $ajax_action );
677
- if ( null !== $error ) {
678
- return $error;
679
- }
680
- } else {
681
- if ( $this->is_ticket_event_imported( $post_id ) ) {
682
- $this->clear_event_metadata( $post_id );
683
- return null;
684
- }
685
- if ( $this->is_ticket_event_from_another_account( $post_id ) ) {
686
- $this->clear_event_metadata( $post_id );
687
- return null;
688
- }
689
- }
690
- $response = $this->request_api( 'DELETE',
691
- AI1EC_API_URL . "events/$api_event_id",
692
- true //true to decode response body
693
- );
694
- if ( $this->is_response_success( $response ) ) {
695
- $this->clear_event_metadata( $post_id );
696
- return null;
697
- } else {
698
- if ( $this->_is_event_notfound_error( $response ) ) {
699
- $this->clear_event_metadata( $post_id );
700
- return null;
701
- }
702
- $message = $this->save_error_notification( $response, __( 'We were unable to remove the Event on Time.ly Network', AI1EC_PLUGIN_NAME ) );
703
- return $message;
704
  }
705
  }
706
 
707
- /**
708
  * Clear the event metadata used by Event from the post id
709
  * @param int $post_id Post ID
710
  */
711
  public function clear_event_metadata( $post_id ) {
712
- delete_post_meta( $post_id, self::API_EVENT_DATA );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713
  }
714
 
715
- public function get_api_event_data( $post_id ) {
716
- $data = get_post_meta(
717
- $post_id,
718
- self::API_EVENT_DATA,
719
- true
720
- );
721
- if ( ai1ec_is_blank ( $data ) ) {
722
- return null;
723
- } else if ( is_numeric( $data ) ) {
724
- //migrate the old metadata into one
725
- $new_data[self::ATTR_EVENT_ID] = $data;
726
- $value = get_post_meta( $post_id, '_ai1ec_thumbnail_id', true );
727
- if ( false === ai1ec_is_blank( $value ) ) {
728
- $new_data[self::ATTR_THUMBNAIL_ID] = $value;
729
- }
730
- $value = get_post_meta( $post_id, '_ai1ec_ics_checkout_url', true );
731
- if ( false === ai1ec_is_blank( $value ) ) {
732
- $new_data[self::ATTR_ICS_CHECKOUT_URL] = $value;
733
- }
734
- $value = get_post_meta( $post_id, '_ai1ec_ics_api_url' , true );
735
- if ( ai1ec_is_blank( $value ) ) {
736
- //not imported ticket event
737
- $new_data[self::ATTR_ACCOUNT] = $this->get_current_account();
738
- $new_data[self::ATTR_CALENDAR_ID] = $this->get_current_calendar();
739
- } else {
740
- $new_data[self::ATTR_ICS_API_URL] = $value;
741
- }
742
- $new_data[self::ATTR_CURRENCY] = 'USD';
743
- update_post_meta( $post_id, self::API_EVENT_DATA, $new_data );
744
- return $new_data;
745
- } else if ( is_array( $data ) ) {
746
- return $data;
747
- } else {
748
- wp_die( 'Error geting the api data' );
749
- }
750
- }
751
-
752
- /**
753
- * Get the id of the event on the API
754
- * @param int $post_id Post ID
755
- */
756
- public function get_api_event_id( $post_id ) {
757
- $data = $this->get_api_event_data( $post_id );
758
- if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
759
- return $data[self::ATTR_EVENT_ID];
760
- } else {
761
- return null;
762
- }
763
- }
764
-
765
- /**
766
- * Get the API URL of the event
767
- * @param int $post_id Post ID
768
- * @param bool $default_null True to return NULL if the value does not exist, false to return the configured API URL
769
- */
770
- public function get_api_event_url ( $post_id ) {
771
- $data = $this->get_api_event_data( $post_id );
772
- if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
773
- if ( isset( $data[self::ATTR_ICS_API_URL] ) ) {
774
- return $data[self::ATTR_ICS_API_URL];
775
- } else {
776
- return AI1EC_API_URL;
777
- }
778
- } else {
779
- return null;
780
- }
781
- }
782
-
783
- /**
784
- * Get the Currency of the event
785
- * @param int $post_id Post ID
786
- */
787
- public function get_api_event_currency ( $post_id ) {
788
- $data = $this->get_api_event_data( $post_id );
789
- if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
790
- if ( isset( $data[self::ATTR_CURRENCY] ) ) {
791
- return $data[self::ATTR_CURRENCY];
792
- } else {
793
- return 'USD';
794
- }
795
- } else {
796
- return null;
797
- }
798
- }
799
-
800
- /**
801
- * Get the Checkout url of the event
802
- * @param int $post_id Post ID
803
- */
804
- public function get_api_event_checkout_url ( $post_id ) {
805
- $data = $this->get_api_event_data( $post_id );
806
- if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
807
- if ( isset( $data[self::ATTR_ICS_CHECKOUT_URL] ) ) {
808
- return $data[self::ATTR_ICS_CHECKOUT_URL];
809
- } else {
810
- return AI1EC_TICKETS_CHECKOUT_URL;
811
- }
812
- } else {
813
- return null;
814
- }
815
- }
816
-
817
- /**
818
- * Get the Buy Ticket URL of the event
819
- * @param int $post_id Post ID
820
- */
821
- public function get_api_event_buy_ticket_url ( $post_id ) {
822
- $data = $this->get_api_event_data( $post_id );
823
- if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
824
- $api_event_id = $data[self::ATTR_EVENT_ID];
825
- if ( isset( $data[self::ATTR_ICS_CHECKOUT_URL] ) ) {
826
- $checkout_url = $data[self::ATTR_ICS_CHECKOUT_URL];
827
- } else {
828
- $checkout_url = AI1EC_TICKETS_CHECKOUT_URL;
829
- }
830
- return str_replace( '{event_id}', $api_event_id, $checkout_url );
831
- } else {
832
- return null;
833
- }
834
- }
835
-
836
- /**
837
- * Get tax options modal
838
- * @param int $event_id Event ID (optional)
839
- */
840
- public function get_tax_options_modal( $post_id = null ) {
841
- $calendar_id = $this->_get_ticket_calendar();
842
- $event_id = $this->get_api_event_id( $post_id );
843
- $response = $this->request_api( 'GET',
844
- AI1EC_API_URL . "calendars/$calendar_id/tax_options" .
845
- ( is_null( $event_id ) ? '' : "?event_id=$event_id" )
846
- );
847
- return (object) array( 'data' => $response->raw, 'error' => false );
848
- }
849
-
850
- /**
851
- * Get tax options modal
852
- * @param int $event_id Event ID (optional)
853
- */
854
- public function get_tax_options_modal_ep() {
855
- $calendar_id = $this->_get_ticket_calendar();
856
- $response = $this->request_api( 'GET',
857
- AI1EC_API_URL . "eventpromote/$calendar_id/tax_options"
858
- );
859
- return (object) array( 'data' => $response->raw, 'error' => false );
860
- }
861
-
862
- /**
863
- * Save the API event data
864
- * @param int $post_id Post ID
865
- * @param int $api_event_id (optional) Id of the event on the API
866
- * @param string $ics_api_url (optional) API URL of the event on the API (used when importing an ICS feed)
867
- * @param string $ics_checkout_url (optional) API CHECKOUT URL of the event on the API (used when importing an ICS feed)
868
- * @param string $currency (optional) Currency code of the event
869
- * @param string $thumbnail_id (optional) Id of the Thumbnail (Featured Image id)
870
- */
871
- public function save_api_event_data( $post_id, $api_event_id, $ics_api_url = null, $ics_checkout_url = null, $currency = null, $thumbnail_id = null ) {
872
- if ( ai1ec_is_blank( $api_event_id ) ) {
873
- throw new Error( 'Api event id should never be null' );
874
- }
875
- $api_data[self::ATTR_EVENT_ID] = $api_event_id;
876
- $api_data[self::ATTR_ICS_API_URL] = $ics_api_url;
877
- $api_data[self::ATTR_ICS_CHECKOUT_URL] = $ics_checkout_url;
878
- $api_data[self::ATTR_CURRENCY] = $currency;
879
- $api_data[self::ATTR_THUMBNAIL_ID] = $thumbnail_id;
880
- if ( ai1ec_is_blank( $ics_api_url ) ) {
881
- $api_data[self::ATTR_ACCOUNT] = $this->get_current_account();
882
- $api_data[self::ATTR_CALENDAR_ID] = $this->get_current_calendar();
883
- }
884
- $previous_data = $this->get_api_event_data( $post_id );
885
- $new_data = array();
886
- if ( is_array( $previous_data ) ) {
887
- foreach ( $previous_data as $key => $value) {
888
- $new_data[$key] = $value;
889
- }
890
- }
891
- foreach ( $api_data as $key => $value ) {
892
- if ( ai1ec_is_blank( $value ) ) {
893
- unset( $new_data[$key] );
894
- } else {
895
- $new_data[$key] = $api_data[$key];
896
- }
897
- }
898
- return update_post_meta( $post_id, self::API_EVENT_DATA, $new_data, $previous_data );
899
- }
900
 
901
  }
10
  */
11
  class Ai1ec_Api_Ticketing extends Ai1ec_Api_Abstract {
12
 
13
+ const API_EVENT_DATA = '_ai1ec_api_event_id';
14
+
15
+ const ATTR_EVENT_ID = 'api_event_id';
16
+ const ATTR_THUMBNAIL_ID = 'thumbnail_id';
17
+ const ATTR_ICS_CHECKOUT_URL = 'ics_checkout_url';
18
+ const ATTR_ICS_API_URL = 'ics_api_url';
19
+ const ATTR_ACCOUNT = 'account';
20
+ const ATTR_CALENDAR_ID = 'calendar_id';
21
+ const ATTR_CURRENCY = 'currency';
22
+
23
+ const MAX_TICKET_TO_BUY_DEFAULT = 25;
24
+
25
+ /**
26
+ * Post construction routine.
27
+ *
28
+ * Override this method to perform post-construction tasks.
29
+ *
30
+ * @return void Return from this method is ignored.
31
+ */
32
+ protected function _initialize() {
33
+ parent::_initialize();
34
+ }
35
+
36
+ /**
37
+ * Count the valid Tickets Types (not removed) included inside the Ticket Event
38
+ */
39
+ private function _count_valid_tickets( $post_ticket_types ) {
40
+ if (false === isset( $post_ticket_types ) || 0 === count( $post_ticket_types ) ) {
41
+ return 0;
42
+ } else {
43
+ $count = 0;
44
+ foreach ( $post_ticket_types as $ticket_type_ite ) {
45
+ if ( !isset( $ticket_type_ite['remove'] ) ) {
46
+ $count++;
47
+ }
48
+ }
49
+ return $count;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Return an error if the Ticket Event is not owned by the Current account
55
+ */
56
+ private function _prevent_update_ticket_event( Ai1ec_Event $event, $ajax_action = false ) {
57
+ if ( $this->is_ticket_event_imported( $event->get( 'post_id' ) ) ) {
58
+ //prevent changes on Ticket Events that were imported
59
+ $error = __( 'This Event was replicated from another site. Changes are not allowed.', AI1EC_PLUGIN_NAME );
60
+ if ( ! $ajax_action ) {
61
+ $notification = $this->_registry->get( 'notification.admin' );
62
+ $notification->store(
63
+ $error,
64
+ 'error',
65
+ 0,
66
+ array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
67
+ false
68
+ );
69
+ }
70
+ return $error;
71
+ }
72
+ if ( $this->is_ticket_event_from_another_account( $event->get( 'post_id' ) ) ) {
73
+ //prevent changes on Ticket Events that were imported
74
+ $error = sprintf(
75
+ __( 'This Event was created using a different account %s. Changes are not allowed.', AI1EC_PLUGIN_NAME ),
76
+ $this->get_api_event_account( $event->get( 'post_id' ) )
77
+ );
78
+ if ( ! $ajax_action ) {
79
+ $notification = $this->_registry->get( 'notification.admin' );
80
+ $notification->store(
81
+ $error,
82
+ 'error',
83
+ 0,
84
+ array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
85
+ false
86
+ );
87
+ }
88
+ return $error;
89
+ }
90
+ return null;
91
+ }
92
+
93
+ /**
94
+ * Run some validations inside the _POST request to check if the Event
95
+ * submmited is a valid event for Tickets
96
+ * @return NULL in case of success or a Message in case of error
97
+ */
98
+ private function _is_valid_post( Ai1ec_Event $event, $updating ) {
99
+ $message = null;
100
+ if ( ( isset( $_POST['ai1ec_rdate'] ) && ! empty( $_POST['ai1ec_rdate'] ) ) ||
101
+ ( isset( $_POST['ai1ec_repeat'] ) && ! empty( $_POST['ai1ec_repeat'] ) )
102
+ ) {
103
+ $message = __( 'The Repeat option was selected but recurrence is not supported by Event with Tickets.', AI1EC_PLUGIN_NAME );
104
+ } else if ( isset( $_POST['ai1ec_tickets_loading_error'] ) ) {
105
+ //do not update tickets because is unsafe. There was a problem to load the tickets,
106
+ //the customer received the same message when the event was loaded.
107
+ $message = $_POST['ai1ec_tickets_loading_error'];
108
+ } else if ( false === ai1ec_is_blank( $event->get( 'ical_feed_url' ) ) ) {
109
+ //prevent ticket creating inside Regular Events Imported events
110
+ $message = __( 'This Event was replicated from another site. Any changes on Tickets were discarded.', AI1EC_PLUGIN_NAME );
111
+ } else {
112
+ $error = $this->_prevent_update_ticket_event( $event );
113
+ if ( null !== $error ) {
114
+ $message = $error;
115
+ } else if ( ! isset( $_POST['ai1ec_tickets'] ) || 0 === $this->_count_valid_tickets( $_POST['ai1ec_tickets'] ) ) {
116
+ $message = __( 'The Event has the cost option Ticket selected but no ticket was included.', AI1EC_PLUGIN_NAME );
117
+ } else if ( false === $this->has_payment_settings() ) {
118
+ $message = __( 'You need to save the payments settings to create ticket events.', AI1EC_PLUGIN_NAME );
119
+ } else if ( ! isset( $_POST['tax_options'] ) && ! $updating ) {
120
+ $message = __( 'Tax and Invoice options are required.', AI1EC_PLUGIN_NAME );
121
+ }
122
+ }
123
+ if ( null !== $message ) {
124
+ $notification = $this->_registry->get( 'notification.admin' );
125
+ $notification->store( $message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
126
+ return $message;
127
+ }
128
+ return null;
129
+ }
130
+
131
+ /**
132
+ * Create or update a Ticket Event on API server
133
+ * @return object Response body in JSON.
134
+ */
135
+ public function store_event( Ai1ec_Event $event, WP_Post $post, $updating ) {
136
+
137
+ $error = $this->_is_valid_post( $event, $updating );
138
+ if ( null !== $error ) {
139
+ return $error;
140
+ }
141
+ $api_event_id = $this->get_api_event_id( $event->get( 'post_id' ) );
142
+ $is_new = ! $api_event_id;
143
+ $fields = array( 'visibility' => $_POST['visibility'] );
144
+ if ( isset( $_POST['tax_options'] ) ) {
145
+ $fields['tax_options'] = $_POST['tax_options'];
146
+ }
147
+ $body_data = $this->parse_event_fields_to_api_structure(
148
+ $event,
149
+ $post,
150
+ $_POST['ai1ec_tickets'],
151
+ $fields
152
+ );
153
+ $url = AI1EC_API_URL . 'events';
154
+ if ( $api_event_id ) {
155
+ $url = $url . '/' . $api_event_id;
156
+ }
157
+
158
+ //get the thumbnail id saved previously
159
+ $api_data = $this->get_api_event_data( $event->get( 'post_id' ) );
160
+ if ( isset( $api_data[self::ATTR_THUMBNAIL_ID] ) ) {
161
+ $event_thumbnail_id = $api_data[self::ATTR_THUMBNAIL_ID];
162
+ } else {
163
+ $event_thumbnail_id = 0;
164
+ }
165
+ //get the current thumbnail id
166
+ $post_thumbnail_id = get_post_thumbnail_id( $event->get( 'post_id' ) );
167
+ if ( false === isset( $post_thumbnail_id ) ) {
168
+ $post_thumbnail_id = 0;
169
+ }
170
+ $update_image = ( $event_thumbnail_id !== $post_thumbnail_id );
171
+ $payload = '';
172
+ $custom_headers = null;
173
+
174
+ if ( true === $update_image && 0 < $post_thumbnail_id ) {
175
+ $boundary = wp_generate_password( 24 );
176
+ $custom_headers['content-type'] = 'multipart/form-data; boundary=' . $boundary;
177
+ $body_data['update_image'] = '1';
178
+ foreach ($body_data as $key => $value) {
179
+ if ( is_array( $value ) ) {
180
+ $index = 0;
181
+ foreach ( $value as $arr_key => $arr_value ) {
182
+ if ( is_array( $arr_value ) ) {
183
+ foreach ( $arr_value as $child_key => $child_value ) {
184
+ $payload .= '--' . $boundary;
185
+ $payload .= "\r\n";
186
+ $payload .= 'Content-Disposition: form-data; name="' . $key . '[' . $index . '][' . $child_key . ']"' . "\r\n";
187
+ $payload .= "\r\n";
188
+ $payload .= $child_value;
189
+ $payload .= "\r\n";
190
+ }
191
+ } else {
192
+ $payload .= '--' . $boundary;
193
+ $payload .= "\r\n";
194
+ $payload .= 'Content-Disposition: form-data; name="tax_options[' . $arr_key . ']"' . "\r\n";
195
+ $payload .= "\r\n";
196
+ $payload .= $arr_value;
197
+ $payload .= "\r\n";
198
+ }
199
+ $index++;
200
+ }
201
+ } else {
202
+ $payload .= '--' . $boundary;
203
+ $payload .= "\r\n";
204
+ $payload .= 'Content-Disposition: form-data; name="' . $key . '"' . "\r\n";
205
+ $payload .= "\r\n";
206
+ $payload .= $value;
207
+ $payload .= "\r\n";
208
+ }
209
+ }
210
+ $file_path = get_attached_file ( $post_thumbnail_id );
211
+ $file_type = wp_check_filetype ( $file_path );
212
+ $payload .= '--' . $boundary;
213
+ $payload .= "\r\n";
214
+ $payload .= 'Content-Disposition: form-data; name="image_id"; filename="' . basename( $file_path ) . '"' . "\r\n";
215
+ $payload .= 'Content-Type: ' . $file_type['type'] . "\r\n";
216
+ $payload .= "\r\n";
217
+ $payload .= file_get_contents( $file_path );
218
+ $payload .= "\r\n";
219
+ $payload .= '--' . $boundary . '--';
220
+ } else {
221
+ $body_data['update_image'] = (true === $update_image) ? '1' : '0';
222
+ $payload = json_encode( $body_data );
223
+ }
224
+ $response = $this->request_api( 'POST', $url, $payload,
225
+ true, //true to decode response body
226
+ $custom_headers
227
+ );
228
+ if ( $this->is_response_success( $response ) ) {
229
+ $api_event_id = $response->body->id;
230
+ if ( isset( $response->body->currency ) ) {
231
+ $currency = $response->body->currency;
232
+ } else {
233
+ $currency = 'USD';
234
+ }
235
+ $currency = $response->body->currency;
236
+ if ( $post_thumbnail_id <= 0 ) {
237
+ $post_thumbnail_id = null;
238
+ }
239
+ $this->save_api_event_data( $event->get( 'post_id') , $api_event_id, null, null, $currency, $post_thumbnail_id );
240
+ return true;
241
+ } else {
242
+ $error_message = '';
243
+ if ( $is_new ) {
244
+ $error_message = __( 'We were unable to create the Event on Time.ly Ticketing', AI1EC_PLUGIN_NAME );
245
+ } else {
246
+ $error_message = __( 'We were unable to update the Event on Time.ly Ticketing', AI1EC_PLUGIN_NAME );
247
+ }
248
+ return $this->save_error_notification( $response, $error_message );
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Parse the fields of an Event to the structure used by API
254
+ */
255
+ public function parse_event_fields_to_api_structure( Ai1ec_Event $event , WP_Post $post, $post_ticket_types, $api_fields_values ) {
256
+ $calendar_id = $this->_get_ticket_calendar();
257
+ if ( $calendar_id <= 0 ) {
258
+ return null;
259
+ }
260
+
261
+ //fields of ai1ec events table used by API
262
+ $body['latitude'] = $event->get( 'latitude' );
263
+ $body['longitude'] = $event->get( 'longitude' );
264
+ $body['post_id'] = $event->get( 'post_id' );
265
+ $body['calendar_id'] = $calendar_id;
266
+ $body['dtstart'] = $event->get( 'start' )->format_to_javascript();
267
+ $body['dtend'] = $event->getenddate()->format_to_javascript();
268
+ $body['timezone'] = $event->get( 'timezone_name' );
269
+ $body['venue_name'] = $event->get( 'venue' );
270
+ $body['address'] = $event->get( 'address' );
271
+ $body['city'] = $event->get( 'city' );
272
+ $body['province'] = $event->get( 'province' );
273
+ $body['postal_code'] = $event->get( 'postal_code' );
274
+ $body['country'] = $event->get( 'country' );
275
+ $body['contact_name'] = $event->get( 'contact_name' );
276
+ $body['contact_phone'] = $event->get( 'contact_phone' );
277
+ $body['contact_email'] = $event->get( 'contact_email' );
278
+ $body['contact_website'] = $event->get( 'contact_url' );
279
+ $body['uid'] = $event->get_uid();
280
+ $body['title'] = $post->post_title;
281
+ $body['description'] = $post->post_content;
282
+ $body['url'] = get_permalink( $post->ID );
283
+ $body['status'] = $post->post_status;
284
+
285
+ $utc_current_time = $this->_registry->get( 'date.time')->format_to_javascript();
286
+ $body['created_at'] = $utc_current_time;
287
+ $body['updated_at'] = $utc_current_time;
288
+
289
+ //removing blank values
290
+ foreach ($body as $key => $value) {
291
+ if ( ai1ec_is_blank( $value ) ) {
292
+ unset( $body[ $key ] );
293
+ }
294
+ }
295
+
296
+ if ( is_null( $api_fields_values ) || 0 == count( $api_fields_values ) ) {
297
+ $api_fields_values = array( 'status' => 'closed', 'ai1ec_version' => AI1EC_VERSION );
298
+ } else {
299
+ if ( ! isset( $api_fields_values['ai1ec_version'] ) ) {
300
+ $api_fields_values['ai1ec_version'] = AI1EC_VERSION;
301
+ }
302
+ foreach ( $api_fields_values as $key => $value ) {
303
+ $body[$key] = $api_fields_values[$key];
304
+ if ( 'visibility' === $key ) {
305
+ if ( 0 === strcasecmp( 'private', $value ) ) {
306
+ $body['status'] = 'private';
307
+ } else if ( 0 === strcasecmp( 'password', $value ) ) {
308
+ $body['status'] = 'password';
309
+ }
310
+ }
311
+ }
312
+ }
313
+
314
+ $tickets_types = array();
315
+ if ( ! is_null( $post_ticket_types ) ) {
316
+ $index = 0;
317
+ foreach ( $post_ticket_types as $ticket_type_ite ) {
318
+ if ( false === isset( $ticket_type_ite['id'] ) &&
319
+ isset( $ticket_type_ite['remove'] ) ) {
320
+ //ignoring new tickets that didn't go to api yet
321
+ continue;
322
+ }
323
+ $tickets_types[$index++] = $this->_parse_tickets_type_post_to_api_structure(
324
+ $ticket_type_ite,
325
+ $event
326
+ );
327
+ }
328
+ }
329
+ $body['ticket_types'] = $tickets_types;
330
+
331
+ return $body;
332
+ }
333
+
334
+ /**
335
+ * Parse the fields of a Ticket Type to the structure used by API
336
+ */
337
+ protected function _parse_tickets_type_post_to_api_structure( $ticket_type_ite, $event ) {
338
+ $utc_current_time = $this->_registry->get( 'date.time' )->format_to_javascript();
339
+ if ( isset( $ticket_type_ite['id'] ) ) {
340
+ $ticket_type['id'] = $ticket_type_ite['id'];
341
+ $ticket_type['created_at'] = $ticket_type_ite['created_at'];
342
+ } else {
343
+ $ticket_type['created_at'] = $utc_current_time;
344
+ }
345
+ if ( isset( $ticket_type_ite['remove'] ) ) {
346
+ $ticket_type['deleted_at'] = $utc_current_time;
347
+ }
348
+ $ticket_type['name'] = $ticket_type_ite['ticket_name'];
349
+ $ticket_type['description'] = $ticket_type_ite['description'];
350
+ $ticket_type['price'] = $ticket_type_ite['ticket_price'];
351
+ if ( 0 === strcasecmp( 'on', $ticket_type_ite['unlimited'] ) ) {
352
+ $ticket_type['quantity'] = null;
353
+ } else {
354
+ $ticket_type['quantity'] = $ticket_type_ite['quantity'];
355
+ }
356
+ $ticket_type['buy_min_qty'] = $ticket_type_ite['buy_min_limit'];
357
+ if ( ai1ec_is_blank( $ticket_type_ite['buy_max_limit'] ) ) {
358
+ $ticket_type['buy_max_qty'] = null;
359
+ } else {
360
+ $ticket_type['buy_max_qty'] = $ticket_type_ite['buy_max_limit'];
361
+ }
362
+ if ( 0 === strcasecmp( 'on', $ticket_type_ite['availibility'] ) ) {
363
+ //immediate availability
364
+ $timezone_start_time = $this->_registry->get( 'date.time' );
365
+ $timezone_start_time->set_timezone( $event->get('timezone_name') );
366
+ $ticket_type['immediately'] = true;
367
+ $ticket_type['sale_start_date'] = $timezone_start_time->format_to_javascript( $event->get('timezone_name') );
368
+ $ticket_type['sale_end_date'] = $event->get( 'end' )->format_to_javascript();
369
+ } else {
370
+ $ticket_type['immediately'] = false;
371
+ $ticket_type['sale_start_date'] = $ticket_type_ite['ticket_sale_start_date'];
372
+ $ticket_type['sale_end_date'] = $ticket_type_ite['ticket_sale_end_date'];
373
+ }
374
+ $ticket_type['updated_at'] = $utc_current_time;
375
+ $ticket_type['status'] = $ticket_type_ite['ticket_status'];
376
+ return $ticket_type;
377
+ }
378
+
379
+ /**
380
+ * Unparse the fields of API structure to the Ticket Type
381
+ */
382
+ protected function _unparse_tickets_type_from_api_structure( $ticket_type_api ) {
383
+ $ticket_type = $ticket_type_api;
384
+ $ticket_type->ticket_name = $ticket_type_api->name;
385
+ $ticket_type->ticket_price = $ticket_type_api->price;
386
+ $ticket_type->buy_min_limit = $ticket_type_api->buy_min_qty;
387
+ if ( null === $ticket_type_api->buy_max_qty ) {
388
+ $ticket_type->buy_max_limit = self::MAX_TICKET_TO_BUY_DEFAULT;
389
+ } else {
390
+ $ticket_type->buy_max_limit = $ticket_type_api->buy_max_qty;
391
+ }
392
+ if ( true === ( ( bool ) $ticket_type_api->immediately ) ) {
393
+ $ticket_type->availibility = 'on';
394
+ } else {
395
+ $ticket_type->availibility = 'off';
396
+ }
397
+ $ticket_type->ticket_sale_start_date = $ticket_type_api->sale_start_date; //YYYY-MM-YY HH:NN:SS
398
+ $ticket_type->ticket_sale_end_date = $ticket_type_api->sale_end_date; //YYYY-MM-YY HH:NN:SS
399
+ $ticket_type->ticket_status = $ticket_type_api->status;
400
+ if ( 'open' === $ticket_type_api->status ) {
401
+ $ticket_type->ticket_status_label = __( 'Open for sale', AI1EC_PLUGIN_NAME );
402
+ } else if ( 'closed' === $ticket_type_api->status ) {
403
+ $ticket_type->ticket_status_label = __( 'Sale ended', AI1EC_PLUGIN_NAME );
404
+ } else if ( 'canceled' === $ticket_type_api->status ) {
405
+ $ticket_type->ticket_status_label = __( 'Canceled', AI1EC_PLUGIN_NAME );
406
+ } else {
407
+ $ticket_type->ticket_status_label = $ticket_type_api->status;
408
+ }
409
+ if ( false === isset( $ticket_type_api->quantity ) ||
410
+ null === $ticket_type_api->quantity ) {
411
+ $ticket_type->unlimited = 'on';
412
+ } else {
413
+ $ticket_type->unlimited = 'off';
414
+ }
415
+ $ticket_type->ticket_type_id = $ticket_type_api->id;
416
+ $ticket_type->available = $ticket_type_api->available;
417
+ $ticket_type->availability = $this->_parse_availability_message( $ticket_type_api->availability );
418
+
419
+ //derived property to set the max quantity of dropdown
420
+ if ( $ticket_type->available !== null ) {
421
+ if ( $ticket_type->available > $ticket_type->buy_max_limit ) {
422
+ $ticket_type->buy_max_available = $ticket_type->buy_max_limit;
423
+ } else {
424
+ $ticket_type->buy_max_available = $ticket_type->available;
425
+ }
426
+ } else {
427
+ $ticket_type->buy_max_available = $ticket_type->buy_max_limit;
428
+ }
429
+ return $ticket_type;
430
+ }
431
+
432
+ public function _parse_availability_message( $availability ){
433
+ if ( ai1ec_is_blank ( $availability ) ) {
434
+ return null;
435
+ } else {
436
+ switch ($availability) {
437
+ case 'past_event':
438
+ return __( 'Past Event' );
439
+ case 'event_closed':
440
+ return __( 'Event closed' );
441
+ case 'not_available_yet':
442
+ return __( 'Not available yet' );
443
+ case 'sale_closed':
444
+ return __( 'Sale closed' );
445
+ case 'sold_out':
446
+ return __( 'Sold out' );
447
+ default:
448
+ return __( 'Not available' );
449
+ }
450
+ }
451
+ }
452
+
453
+ public function get_event( $post_id ) {
454
+ $api_event_id = $this->get_api_event_id( $post_id );
455
+ if ( ! $api_event_id ) {
456
+ return (object) array( 'data' => array() );
457
+ }
458
+ $response = $this->request_api( 'GET', $this->get_api_event_url( $post_id ) . 'events/' . $api_event_id . '/edit' );
459
+ if ( $this->is_response_success( $response ) ) {
460
+ if ( isset( $response->body->ticket_types ) ) {
461
+ foreach ( $response->body->ticket_types as $ticket_api ) {
462
+ $this->_unparse_tickets_type_from_api_structure( $ticket_api );
463
+ }
464
+ }
465
+ return (object) array( 'data' => $response->body );
466
+ } else {
467
+ $error_message = $this->_transform_error_message(
468
+ __( 'We were unable to get the Event Details from Time.ly Ticketing', AI1EC_PLUGIN_NAME ),
469
+ $response->raw, $response->url,
470
+ true
471
+ );
472
+ return (object) array( 'data' => array(), 'error' => $error_message );
473
+ }
474
+ }
475
+
476
+ /**
477
+ * @return string JSON.
478
+ */
479
+ public function get_ticket_types( $post_id, $get_canceled = true ) {
480
+ $api_event_id = $this->get_api_event_id( $post_id );
481
+ if ( ! $api_event_id ) {
482
+ return json_encode( array( 'data' => array() ) );
483
+ }
484
+ $response = $this->request_api( 'GET', $this->get_api_event_url( $post_id ) . 'events/' . $api_event_id . '/ticket_types',
485
+ array( 'get_canceled' => ( true === $get_canceled ? 1 : 0 ) )
486
+ );
487
+ if ( $this->is_response_success( $response ) ) {
488
+ if ( isset( $response->body->ticket_types ) ) {
489
+ foreach ( $response->body->ticket_types as $ticket_api ) {
490
+ $this->_unparse_tickets_type_from_api_structure( $ticket_api );
491
+ }
492
+ return json_encode( array( 'data' => $response->body->ticket_types ) );
493
+ } else {
494
+ return json_encode( array( 'data' => array() ) );
495
+ }
496
+ } else {
497
+ $error_message = $this->_transform_error_message(
498
+ __( 'We were unable to get the Tickets Details from Time.ly Ticketing', AI1EC_PLUGIN_NAME ),
499
+ $response->raw, $response->url,
500
+ true
501
+ );
502
+ return json_encode( array( 'data' => array(), 'error' => $error_message ) );
503
+ }
504
+ }
505
+
506
+ /**
507
+ * @return object Response body in JSON.
508
+ */
509
+ public function get_tickets( $post_id ) {
510
+ $api_event_id = $this->get_api_event_id( $post_id );
511
+ if ( ! $api_event_id ) {
512
+ return json_encode( array( 'data' => array() ) );
513
+ }
514
+ $request = array(
515
+ 'headers' => $this->_get_headers(),
516
+ 'timeout' => parent::DEFAULT_TIMEOUT
517
+ );
518
+ $url = $this->get_api_event_url( $post_id ) . 'events/' . $api_event_id . '/tickets';
519
+ $response = wp_remote_get( $url, $request );
520
+ $response_code = wp_remote_retrieve_response_code( $response );
521
+ if ( 200 === $response_code ) {
522
+ return $response['body'];
523
+ } else {
524
+ $error_message = $this->_transform_error_message(
525
+ __( 'We were unable to get the Tickets Attendees from Time.ly Ticketing', AI1EC_PLUGIN_NAME ),
526
+ $response, $url,
527
+ true
528
+ );
529
+ return json_encode( array( 'data' => array(), 'error' => $error_message ) );
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Check if a Ticket Event was imported from an ICS Feed
535
+ */
536
+ public function is_ticket_event_imported( $post_id ) {
537
+ $data = $this->get_api_event_data( $post_id );
538
+ if ( isset( $data[self::ATTR_EVENT_ID] ) && isset( $data[self::ATTR_ICS_API_URL] ) ) {
539
+ return ( ! ai1ec_is_blank ( $data[self::ATTR_ICS_API_URL] ) );
540
+ } else {
541
+ return false;
542
+ }
543
+
544
+ }
545
+
546
+ /**
547
+ * Check if the Ticket Event was created using a different account
548
+ * The user probably created the event from one account, signed out and
549
+ * is currently signed in with a new account
550
+ */
551
+ public function is_ticket_event_from_another_account( $post_id ) {
552
+ $data = $this->get_api_event_data( $post_id );
553
+ if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
554
+ if ( isset( $data[self::ATTR_ACCOUNT] ) ) {
555
+ return ( $this->get_current_account() != $data[self::ATTR_ACCOUNT] );
556
+ } else {
557
+ return false;
558
+ }
559
+ } else {
560
+ return false;
561
+ }
562
  }
563
 
564
+ /**
565
+ * Get the API account where the event was created
566
+ * @param int $post_id Post ID
567
+ * @param bool $default_null True to return NULL if the value does not exist, false to return the configured API URL
568
+ */
569
+ public function get_api_event_account( $post_id ) {
570
+ $data = $this->get_api_event_data( $post_id );
571
+ if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
572
+ if ( isset( $data[self::ATTR_ACCOUNT] ) ) {
573
+ return $data[self::ATTR_ACCOUNT];
574
+ } else {
575
+ return null;
576
+ }
577
+ } else {
578
+ return null;
579
+ }
580
+ }
581
+
582
+ /**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
583
  * Check if the response that came from the API is the event not found
584
  */
585
+ private function _is_event_notfound_error( $response ) {
586
+ if ( isset( $response->response_code ) && 404 === $response->response_code ) {
587
+ if ( isset( $response->body ) ) {
588
+ if ( is_array( $response->body ) &&
589
+ isset( $response->body['message'] ) ) {
590
+ if ( false !== stripos( $response->body['message'], 'event not found') ) {
591
+ return true;
592
+ }
593
+ }
594
+ }
595
+ }
596
+ return false;
597
  }
598
 
599
+ /**
600
+ * @return NULL in case of success or an error string in case of error
601
+ */
602
  public function update_api_event_fields( WP_Post $post, $api_fields_values, $post_action = 'trash', $ajax_action = false ) {
603
+ $post_id = $post->ID;
604
+ $api_event_id = $this->get_api_event_id( $post_id );
605
+ if ( ! $api_event_id ) {
606
+ return null;
607
+ }
608
+ try {
609
+ $event = $this->_registry->get( 'model.event', $post_id );
610
+ } catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
611
+ $message = __( 'Event not found inside the database.', AI1EC_PLUGIN_NAME );
612
+ $notification = $this->_registry->get( 'notification.admin' );
613
+ $notification->store( $message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
614
+ return $message;
615
+ }
616
+ if ( 'update' === $post_action ) {
617
+ $error = $this->_prevent_update_ticket_event( $event, $ajax_action );
618
+ if ( null !== $error ) {
619
+ return $error;
620
+ }
621
+ } else {
622
+ if ( $this->is_ticket_event_imported( $post_id ) ) {
623
+ return null;
624
+ }
625
+ if ( $this->is_ticket_event_from_another_account( $post_id ) ) {
626
+ return null;
627
+ }
628
+ }
629
+ $headers = $this->_get_headers();
630
+ $body_data = $this->parse_event_fields_to_api_structure(
631
+ $event,
632
+ $post,
633
+ null, //does not update ticket types, just chaging the api fields specified
634
+ $api_fields_values
635
+ );
636
+ $response = $this->request_api( 'POST',
637
+ AI1EC_API_URL . "events/$api_event_id",
638
+ json_encode( $body_data ),
639
+ true //true to decode response body
640
+ );
641
+ if ( ! $this->is_response_success( $response ) ) {
642
+ if ( $this->_is_event_notfound_error( $response ) ) {
643
+ if ( isset( $api_fields_values['status'] ) &&
644
+ 'trash' === $api_fields_values['status'] ) {
645
+ //this is an exception, the event was deleted on API server, but for some reason
646
+ //the metada was not unset, in this case leave the event be
647
+ //move to trash
648
+ return null;
649
+ }
650
+ }
651
+ $message = $this->save_error_notification( $response, __( 'We were unable to Update the Event on Time.ly Network', AI1EC_PLUGIN_NAME ) );
652
+ return $message;
653
+ } else {
654
+ return null;
655
  }
656
  }
657
 
660
  * @return NULL in case of success or an error string in case of error
661
  */
662
  public function delete_api_event( $post_id, $post_action = 'delete', $ajax_action = false ) {
663
+ $api_event_id = $this->get_api_event_id( $post_id );
664
+ if ( ! $api_event_id ) {
665
+ return null;
666
+ }
667
+ if ( 'update' === $post_action ) {
668
+ try {
669
+ $event = $this->_registry->get( 'model.event', $post_id );
670
+ } catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
671
+ $message = __( 'Event not found inside the database.', AI1EC_PLUGIN_NAME );
672
+ $notification = $this->_registry->get( 'notification.admin' );
673
+ $notification->store( $message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
674
+ return $message;
675
+ }
676
+ $error = $this->_prevent_update_ticket_event( $event, $ajax_action );
677
+ if ( null !== $error ) {
678
+ return $error;
679
+ }
680
+ } else {
681
+ if ( $this->is_ticket_event_imported( $post_id ) ) {
682
+ $this->clear_event_metadata( $post_id );
683
+ return null;
684
+ }
685
+ if ( $this->is_ticket_event_from_another_account( $post_id ) ) {
686
+ $this->clear_event_metadata( $post_id );
687
+ return null;
688
+ }
689
+ }
690
+ $response = $this->request_api( 'DELETE',
691
+ AI1EC_API_URL . "events/$api_event_id",
692
+ true //true to decode response body
693
+ );
694
+ if ( $this->is_response_success( $response ) ) {
695
+ $this->clear_event_metadata( $post_id );
696
+ return null;
697
+ } else {
698
+ if ( $this->_is_event_notfound_error( $response ) ) {
699
+ $this->clear_event_metadata( $post_id );
700
+ return null;
701
+ }
702
+ $message = $this->save_error_notification( $response, __( 'We were unable to remove the Event on Time.ly Network', AI1EC_PLUGIN_NAME ) );
703
+ return $message;
704
  }
705
  }
706
 
707
+ /**
708
  * Clear the event metadata used by Event from the post id
709
  * @param int $post_id Post ID
710
  */
711
  public function clear_event_metadata( $post_id ) {
712
+ delete_post_meta( $post_id, self::API_EVENT_DATA );
713
+ }
714
+
715
+ public function get_api_event_data( $post_id ) {
716
+ $data = get_post_meta(
717
+ $post_id,
718
+ self::API_EVENT_DATA,
719
+ true
720
+ );
721
+ if ( ai1ec_is_blank ( $data ) ) {
722
+ return null;
723
+ } else if ( is_numeric( $data ) ) {
724
+ //migrate the old metadata into one
725
+ $new_data[self::ATTR_EVENT_ID] = $data;
726
+ $value = get_post_meta( $post_id, '_ai1ec_thumbnail_id', true );
727
+ if ( false === ai1ec_is_blank( $value ) ) {
728
+ $new_data[self::ATTR_THUMBNAIL_ID] = $value;
729
+ }
730
+ $value = get_post_meta( $post_id, '_ai1ec_ics_checkout_url', true );
731
+ if ( false === ai1ec_is_blank( $value ) ) {
732
+ $new_data[self::ATTR_ICS_CHECKOUT_URL] = $value;
733
+ }
734
+ $value = get_post_meta( $post_id, '_ai1ec_ics_api_url' , true );
735
+ if ( ai1ec_is_blank( $value ) ) {
736
+ //not imported ticket event
737
+ $new_data[self::ATTR_ACCOUNT] = $this->get_current_account();
738
+ $new_data[self::ATTR_CALENDAR_ID] = $this->get_current_calendar();
739
+ } else {
740
+ $new_data[self::ATTR_ICS_API_URL] = $value;
741
+ }
742
+ $new_data[self::ATTR_CURRENCY] = 'USD';
743
+ update_post_meta( $post_id, self::API_EVENT_DATA, $new_data );
744
+ return $new_data;
745
+ } else if ( is_array( $data ) ) {
746
+ return $data;
747
+ } else {
748
+ wp_die( 'Error geting the api data' );
749
+ }
750
+ }
751
+
752
+ /**
753
+ * Get the id of the event on the API
754
+ * @param int $post_id Post ID
755
+ */
756
+ public function get_api_event_id( $post_id ) {
757
+ $data = $this->get_api_event_data( $post_id );
758
+ if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
759
+ return $data[self::ATTR_EVENT_ID];
760
+ } else {
761
+ return null;
762
+ }
763
+ }
764
+
765
+ /**
766
+ * Get the API URL of the event
767
+ * @param int $post_id Post ID
768
+ * @param bool $default_null True to return NULL if the value does not exist, false to return the configured API URL
769
+ */
770
+ public function get_api_event_url ( $post_id ) {
771
+ $data = $this->get_api_event_data( $post_id );
772
+ if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
773
+ if ( isset( $data[self::ATTR_ICS_API_URL] ) ) {
774
+ return $data[self::ATTR_ICS_API_URL];
775
+ } else {
776
+ return AI1EC_API_URL;
777
+ }
778
+ } else {
779
+ return null;
780
+ }
781
+ }
782
+
783
+ /**
784
+ * Get the Currency of the event
785
+ * @param int $post_id Post ID
786
+ */
787
+ public function get_api_event_currency ( $post_id ) {
788
+ $data = $this->get_api_event_data( $post_id );
789
+ if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
790
+ if ( isset( $data[self::ATTR_CURRENCY] ) ) {
791
+ return $data[self::ATTR_CURRENCY];
792
+ } else {
793
+ return 'USD';
794
+ }
795
+ } else {
796
+ return null;
797
+ }
798
  }
799
 
800
+ /**
801
+ * Get the Checkout url of the event
802
+ * @param int $post_id Post ID
803
+ */
804
+ public function get_api_event_checkout_url ( $post_id ) {
805
+ $data = $this->get_api_event_data( $post_id );
806
+ if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
807
+ if ( isset( $data[self::ATTR_ICS_CHECKOUT_URL] ) ) {
808
+ return $data[self::ATTR_ICS_CHECKOUT_URL];
809
+ } else {
810
+ return AI1EC_TICKETS_CHECKOUT_URL;
811
+ }
812
+ } else {
813
+ return null;
814
+ }
815
+ }
816
+
817
+ /**
818
+ * Get the Buy Ticket URL of the event
819
+ * @param int $post_id Post ID
820
+ */
821
+ public function get_api_event_buy_ticket_url ( $post_id ) {
822
+ $data = $this->get_api_event_data( $post_id );
823
+ if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
824
+ $api_event_id = $data[self::ATTR_EVENT_ID];
825
+ if ( isset( $data[self::ATTR_ICS_CHECKOUT_URL] ) ) {
826
+ $checkout_url = $data[self::ATTR_ICS_CHECKOUT_URL];
827
+ } else {
828
+ $checkout_url = AI1EC_TICKETS_CHECKOUT_URL;
829
+ }
830
+ return str_replace( '{event_id}', $api_event_id, $checkout_url );
831
+ } else {
832
+ return null;
833
+ }
834
+ }
835
+
836
+ /**
837
+ * Get tax options modal
838
+ * @param int $event_id Event ID (optional)
839
+ */
840
+ public function get_tax_options_modal( $post_id = null ) {
841
+ $calendar_id = $this->_get_ticket_calendar();
842
+ $event_id = $this->get_api_event_id( $post_id );
843
+ $response = $this->request_api( 'GET',
844
+ AI1EC_API_URL . "calendars/$calendar_id/tax_options" .
845
+ ( is_null( $event_id ) ? '' : "?event_id=$event_id" )
846
+ );
847
+ return (object) array( 'data' => $response->raw, 'error' => false );
848
+ }
849
+
850
+ /**
851
+ * Get tax options modal
852
+ * @param int $event_id Event ID (optional)
853
+ */
854
+ public function get_tax_options_modal_ep() {
855
+ $calendar_id = $this->_get_ticket_calendar();
856
+ $response = $this->request_api( 'GET',
857
+ AI1EC_API_URL . "eventpromote/$calendar_id/tax_options"
858
+ );
859
+ return (object) array( 'data' => $response->raw, 'error' => false );
860
+ }
861
+
862
+ /**
863
+ * Save the API event data
864
+ * @param int $post_id Post ID
865
+ * @param int $api_event_id (optional) Id of the event on the API
866
+ * @param string $ics_api_url (optional) API URL of the event on the API (used when importing an ICS feed)
867
+ * @param string $ics_checkout_url (optional) API CHECKOUT URL of the event on the API (used when importing an ICS feed)
868
+ * @param string $currency (optional) Currency code of the event
869
+ * @param string $thumbnail_id (optional) Id of the Thumbnail (Featured Image id)
870
+ */
871
+ public function save_api_event_data( $post_id, $api_event_id, $ics_api_url = null, $ics_checkout_url = null, $currency = null, $thumbnail_id = null ) {
872
+ if ( ai1ec_is_blank( $api_event_id ) ) {
873
+ throw new Error( 'Api event id should never be null' );
874
+ }
875
+ $api_data[self::ATTR_EVENT_ID] = $api_event_id;
876
+ $api_data[self::ATTR_ICS_API_URL] = $ics_api_url;
877
+ $api_data[self::ATTR_ICS_CHECKOUT_URL] = $ics_checkout_url;
878
+ $api_data[self::ATTR_CURRENCY] = $currency;
879
+ $api_data[self::ATTR_THUMBNAIL_ID] = $thumbnail_id;
880
+ if ( ai1ec_is_blank( $ics_api_url ) ) {
881
+ $api_data[self::ATTR_ACCOUNT] = $this->get_current_account();
882
+ $api_data[self::ATTR_CALENDAR_ID] = $this->get_current_calendar();
883
+ }
884
+ $previous_data = $this->get_api_event_data( $post_id );
885
+ $new_data = array();
886
+ if ( is_array( $previous_data ) ) {
887
+ foreach ( $previous_data as $key => $value) {
888
+ $new_data[$key] = $value;
889
+ }
890
+ }
891
+ foreach ( $api_data as $key => $value ) {
892
+ if ( ai1ec_is_blank( $value ) ) {
893
+ unset( $new_data[$key] );
894
+ } else {
895
+ $new_data[$key] = $api_data[$key];
896
+ }
897
+ }
898
+ return update_post_meta( $post_id, self::API_EVENT_DATA, $new_data, $previous_data );
899
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
900
 
901
  }
app/model/app.php CHANGED
@@ -10,25 +10,25 @@
10
  */
11
  class Ai1ec_App extends Ai1ec_Base {
12
 
13
- /**
14
- * Initiate base objects.
15
- *
16
- * @param Ai1ec_Registry_Object $registry
17
- * @internal param \Ai1ec_Registry_Object $system Injectable system object.
18
- */
19
- public function __construct( Ai1ec_Registry_Object $registry ) {
20
- parent::__construct( $registry );
21
- $this->_initialize();
22
- }
23
 
24
- /**
25
- * Post construction routine.
26
- *
27
- * Override this method to perform post-construction tasks.
28
- *
29
- * @return void Return from this method is ignored.
30
- */
31
- protected function _initialize() {}
32
 
33
 
34
  }
10
  */
11
  class Ai1ec_App extends Ai1ec_Base {
12
 
13
+ /**
14
+ * Initiate base objects.
15
+ *
16
+ * @param Ai1ec_Registry_Object $registry
17
+ * @internal param \Ai1ec_Registry_Object $system Injectable system object.
18
+ */
19
+ public function __construct( Ai1ec_Registry_Object $registry ) {
20
+ parent::__construct( $registry );
21
+ $this->_initialize();
22
+ }
23
 
24
+ /**
25
+ * Post construction routine.
26
+ *
27
+ * Override this method to perform post-construction tasks.
28
+ *
29
+ * @return void Return from this method is ignored.
30
+ */
31
+ protected function _initialize() {}
32
 
33
 
34
  }
app/model/event-compatibility.php CHANGED
@@ -11,101 +11,101 @@
11
  */
12
  class Ai1ec_Event_Compatibility extends Ai1ec_Event {
13
 
14
- /**
15
- * Getter.
16
- *
17
- * @param string $name Property name.
18
- *
19
- * @return mixed Property value.
20
- */
21
- public function __get( $name ) {
22
- $value = $this->get( $name );
23
- if ( null !== $value ) {
24
- return $value;
25
- }
26
- return $this->get_runtime( $name );
27
- }
28
 
29
- /**
30
- * Isset magic function.
31
- *
32
- * @param string $name Property name.
33
- *
34
- * @return bool True of false.
35
- */
36
- public function __isset( $name ) {
37
- $method_name = 'get' . $name;
38
- if ( method_exists( $this, $method_name ) ) {
39
- return false;
40
- }
41
- return ( null !== $this->$name );
42
- }
43
 
44
- /**
45
- * Twig timespan short method.
46
- *
47
- * @return string Value.
48
- */
49
- public function gettimespan_short() {
50
- return $this->_registry->get( 'view.event.time' )
51
- ->get_timespan_html( $this, 'short' );
52
- }
53
 
54
- /**
55
- * Twig is_allday method.
56
- *
57
- * @return bool Value.
58
- */
59
- public function getis_allday() {
60
- return $this->is_allday();
61
- }
62
 
63
- /**
64
- * Twig is_multiday method.
65
- *
66
- * @return bool Value.
67
- */
68
- public function getis_multiday() {
69
- return $this->is_multiday();
70
- }
71
 
72
- /**
73
- * Returns Event instance permalink for FER compatibility.
74
- *
75
- * @return string Event instance permalink.
76
- */
77
- public function getpermalink() {
78
- return $this->get_runtime( 'instance_permalink' );
79
- }
80
 
81
- /**
82
- * Returns Event timespan for popup.
83
- *
84
- * @return string
85
- */
86
- public function getpopup_timespan() {
87
- return $this->_registry->get( 'twig.ai1ec-extension' )
88
- ->timespan( $this, 'short' );
89
- }
90
 
91
- /**
92
- * Returns Avatar not wrapped in <a> tag.
93
- *
94
- * @return string
95
- */
96
- public function getavatar_not_wrapped() {
97
- return $this->getavatar( false );
98
- }
99
 
100
- /**
101
- * Returns Event avatar URL.
102
- *
103
- * @return string Event avatar URL.
104
- * @throws Ai1ec_Bootstrap_Exception
105
- */
106
- public function getavatar_url() {
107
- return $this->_registry->get(
108
- 'view.event.avatar'
109
- )->get_event_avatar_url( $this );
110
- }
111
  }
11
  */
12
  class Ai1ec_Event_Compatibility extends Ai1ec_Event {
13
 
14
+ /**
15
+ * Getter.
16
+ *
17
+ * @param string $name Property name.
18
+ *
19
+ * @return mixed Property value.
20
+ */
21
+ public function __get( $name ) {
22
+ $value = $this->get( $name );
23
+ if ( null !== $value ) {
24
+ return $value;
25
+ }
26
+ return $this->get_runtime( $name );
27
+ }
28
 
29
+ /**
30
+ * Isset magic function.
31
+ *
32
+ * @param string $name Property name.
33
+ *
34
+ * @return bool True of false.
35
+ */
36
+ public function __isset( $name ) {
37
+ $method_name = 'get' . $name;
38
+ if ( method_exists( $this, $method_name ) ) {
39
+ return false;
40
+ }
41
+ return ( null !== $this->$name );
42
+ }
43
 
44
+ /**
45
+ * Twig timespan short method.
46
+ *
47
+ * @return string Value.
48
+ */
49
+ public function gettimespan_short() {
50
+ return $this->_registry->get( 'view.event.time' )
51
+ ->get_timespan_html( $this, 'short' );
52
+ }
53
 
54
+ /**
55
+ * Twig is_allday method.
56
+ *
57
+ * @return bool Value.
58
+ */
59
+ public function getis_allday() {
60
+ return $this->is_allday();
61
+ }
62
 
63
+ /**
64
+ * Twig is_multiday method.
65
+ *
66
+ * @return bool Value.
67
+ */
68
+ public function getis_multiday() {
69
+ return $this->is_multiday();
70
+ }
71
 
72
+ /**
73
+ * Returns Event instance permalink for FER compatibility.
74
+ *
75
+ * @return string Event instance permalink.
76
+ */
77
+ public function getpermalink() {
78
+ return $this->get_runtime( 'instance_permalink' );
79
+ }
80
 
81
+ /**
82
+ * Returns Event timespan for popup.
83
+ *
84
+ * @return string
85
+ */
86
+ public function getpopup_timespan() {
87
+ return $this->_registry->get( 'twig.ai1ec-extension' )
88
+ ->timespan( $this, 'short' );
89
+ }
90
 
91
+ /**
92
+ * Returns Avatar not wrapped in <a> tag.
93
+ *
94
+ * @return string
95
+ */
96
+ public function getavatar_not_wrapped() {
97
+ return $this->getavatar( false );
98
+ }
99
 
100
+ /**
101
+ * Returns Event avatar URL.
102
+ *
103
+ * @return string Event avatar URL.
104
+ * @throws Ai1ec_Bootstrap_Exception
105
+ */
106
+ public function getavatar_url() {
107
+ return $this->_registry->get(
108
+ 'view.event.avatar'
109
+ )->get_event_avatar_url( $this );
110
+ }
111
  }
app/model/event.php CHANGED
@@ -11,885 +11,885 @@
11
  */
12
  class Ai1ec_Event extends Ai1ec_Base {
13
 
14
- /**
15
- * @var Ai1ec_Event_Entity Data store object reference.
16
- */
17
- protected $_entity = null;
18
-
19
- /**
20
- * @var array Map of fields that require special care during set/get
21
- * operations. Values have following meanings:
22
- * [0] - both way care required;
23
- * [1] - only `set` operations require care;
24
- * [-1] - only `get` (for storage) operations require care.
25
- */
26
- protected $_swizzable = array(
27
- 'cost' => 0,
28
- 'start' => -1,
29
- 'end' => -1,
30
- 'timezone_name' => -1,
31
- 'recurrence_dates' => 1,
32
- 'exception_dates' => 1,
33
- );
34
-
35
- /**
36
- * @var array Runtime properties
37
- */
38
- protected $_runtime_props = array();
39
-
40
- /**
41
- * @var bool|null Boolean cache-definition indicating if event is multiday.
42
- */
43
- protected $_is_multiday = null;
44
-
45
- /**
46
- * Wrapper to get property value.
47
- *
48
- * @param string $property Name of property to get.
49
- * @param mixed $default Default value to return.
50
- *
51
- * @return mixed Actual property.
52
- */
53
- public function get( $property, $default = null ) {
54
- return $this->_entity->get( $property, $default );
55
- }
56
-
57
- /**
58
- * Get properties generated at runtime
59
- *
60
- * @param string $property
61
- *
62
- * @return string
63
- */
64
- public function get_runtime( $property, $default = '' ) {
65
- return isset( $this->_runtime_props[$property] ) ?
66
- $this->_runtime_props[$property] :
67
- $default;
68
- }
69
-
70
- /**
71
- * Set properties generated at runtime
72
- *
73
- * @param string $property
74
- * @param string $value
75
- */
76
- public function set_runtime( $property, $value ) {
77
- $this->_runtime_props[$property] = $value;
78
- }
79
-
80
- /**
81
- * Handle property initiation.
82
- *
83
- * Decides, how to extract value stored in permanent storage.
84
- *
85
- * @param string $property Name of property to handle
86
- * @param mixed $value Value, read from permanent storage
87
- *
88
- * @return bool Success
89
- */
90
- public function set( $property, $value ) {
91
- if (
92
- isset( $this->_swizzable[$property] ) &&
93
- $this->_swizzable[$property] >= 0
94
- ) {
95
- $method = '_handle_property_construct_' . $property;
96
- $value = $this->{$method}( $value );
97
- }
98
- $this->_entity->set( $property, $value );
99
- return $this;
100
- }
101
-
102
- /**
103
- * Set the event is all day, during the specified number of days
104
- *
105
- * @param number $length
106
- */
107
- public function set_all_day( $length = 1 ) {
108
- // set allday as true
109
- $this->set( 'allday', true );
110
- $start = $this->get( 'start' );
111
- // reset time component
112
- $start->set_time( 0, 0, 0 );
113
- $end = $this->_registry->get( 'date.time', $start );
114
- // set the correct length
115
- $end->adjust_day( $length );
116
- $this->set( 'end', $end );
117
- }
118
-
119
- /**
120
- * Set the event as if it has no end time
121
- */
122
- public function set_no_end_time() {
123
- $this->set( 'instant_event', true );
124
- $start = $this->get( 'start' );
125
- $end = $this->_registry->get( 'date.time', $start );
126
- $end->set_time(
127
- $start->format( 'H' ),
128
- $start->format( 'i' ) + 15,
129
- $start->format( 's' )
130
- );
131
- $this->set( 'end', $end );
132
- }
133
-
134
- /**
135
- * Set object fields from arbitrary array.
136
- *
137
- * @param array $data Supposedly map of fields to initiate.
138
- *
139
- * @return Ai1ec_Event Instance of self for chaining.
140
- */
141
- public function initialize_from_array( array $data ) {
142
-
143
- // =======================================================
144
- // = Assign each event field the value from the database =
145
- // =======================================================
146
- foreach ( $this->_entity->list_properties() as $property ) {
147
- if ( 'post' !== $property && isset( $data[$property] ) ) {
148
- $this->set( $property, $data[$property] );
149
- unset( $data[$property] );
150
- }
151
- }
152
- if ( isset( $data['post'] ) ) {
153
- $this->set( 'post', (object)$data['post'] );
154
- } else {
155
- // ========================================
156
- // = Remaining fields are the post fields =
157
- // ========================================
158
- $this->set( 'post', (object)$data );
159
- }
160
- return $this;
161
- }
162
-
163
- /**
164
- * Delete the events from all tables
165
- */
166
- public function delete() {
167
- // delete post (this will trigger deletion of cached events, and
168
- // remove the event from events table)
169
- wp_delete_post( $this->get( 'post_id' ), true );
170
- }
171
-
172
- /**
173
- * Initialize object from ID.
174
- *
175
- * Attempts to retrieve entity from database and if succeeds - uses
176
- * {@see self::initialize_from_array} to initiate actual values.
177
- *
178
- * @param int $post_id ID of post (event) to initiate.
179
- * @param int|bool $instance ID of event instance, false for base event.
180
- *
181
- * @return Ai1ec_Event Instance of self for chaining.
182
- *
183
- * @throws Ai1ec_Event_Not_Found_Exception If entity is not locatable.
184
- */
185
- public function initialize_from_id( $post_id, $instance = false ) {
186
- $post = get_post( $post_id );
187
- if ( ! $post || $post->post_status == 'auto-draft' ) {
188
- throw new Ai1ec_Event_Not_Found_Exception(
189
- 'Post with ID \'' . $post_id .
190
- '\' could not be retrieved from the database.'
191
- );
192
- }
193
- $post_id = (int)$post_id;
194
- $dbi = $this->_registry->get( 'dbi.dbi' );
195
-
196
- $left_join = '';
197
- $select_sql = '
198
- e.post_id,
199
- e.timezone_name,
200
- e.recurrence_rules,
201
- e.exception_rules,
202
- e.allday,
203
- e.instant_event,
204
- e.recurrence_dates,
205
- e.exception_dates,
206
- e.venue,
207
- e.country,
208
- e.address,
209
- e.city,
210
- e.province,
211
- e.postal_code,
212
- e.show_map,
213
- e.contact_name,
214
- e.contact_phone,
215
- e.contact_email,
216
- e.contact_url,
217
- e.cost,
218
- e.ticket_url,
219
- e.ical_feed_url,
220
- e.ical_source_url,
221
- e.ical_organizer,
222
- e.ical_contact,
223
- e.ical_uid,
224
- e.longitude,
225
- e.latitude,
226
- e.show_coordinates,
227
- GROUP_CONCAT( ttc.term_id ) AS categories,
228
- GROUP_CONCAT( ttt.term_id ) AS tags
229
- ';
230
-
231
- if (
232
- false !== $instance &&
233
- is_numeric( $instance ) &&
234
- $instance > 0
235
- ) {
236
- $select_sql .= ', IF( aei.start IS NOT NULL, aei.start, e.start ) as start,' .
237
- ' IF( aei.start IS NOT NULL, aei.end, e.end ) as end ';
238
-
239
- $instance = (int)$instance;
240
- $this->set( 'instance_id', $instance );
241
- $left_join = 'LEFT JOIN ' . $dbi->get_table_name( 'ai1ec_event_instances' ) .
242
- ' aei ON aei.id = ' . $instance . ' AND e.post_id = aei.post_id ';
243
- } else {
244
- $select_sql .= ', e.start as start, e.end as end, e.allday ';
245
- if ( -1 === (int)$instance ) {
246
- $select_sql .= ', aei.id as instance_id ';
247
- $left_join = 'LEFT JOIN ' .
248
- $dbi->get_table_name( 'ai1ec_event_instances' ) .
249
- ' aei ON e.post_id = aei.post_id ' .
250
- 'AND e.start = aei.start AND e.end = aei.end ';
251
- }
252
- }
253
-
254
- // =============================
255
- // = Fetch event from database =
256
- // =============================
257
- $query = 'SELECT ' . $select_sql . '
258
- FROM ' . $dbi->get_table_name( 'ai1ec_events' ) . ' e
259
- LEFT JOIN ' .
260
- $dbi->get_table_name( 'term_relationships' ) . ' tr
261
- ON ( e.post_id = tr.object_id )
262
- LEFT JOIN ' . $dbi->get_table_name( 'term_taxonomy' ) . ' ttc
263
- ON (
264
- tr.term_taxonomy_id = ttc.term_taxonomy_id AND
265
- ttc.taxonomy = \'events_categories\'
266
- )
267
- LEFT JOIN ' . $dbi->get_table_name( 'term_taxonomy' ) . ' ttt
268
- ON (
269
- tr.term_taxonomy_id = ttt.term_taxonomy_id AND
270
- ttt.taxonomy = \'events_tags\'
271
- )
272
- ' . $left_join . '
273
- WHERE e.post_id = ' . $post_id . '
274
- GROUP BY e.post_id';
275
-
276
- $event = $dbi->get_row( $query, ARRAY_A );
277
- if ( null === $event || null === $event['post_id'] ) {
278
- throw new Ai1ec_Event_Not_Found_Exception(
279
- 'Event with ID \'' . $post_id .
280
- '\' could not be retrieved from the database.'
281
- );
282
- }
283
-
284
- $event['post'] = $post;
285
- return $this->initialize_from_array( $event );
286
- }
287
-
288
- public function getenddate() {
289
- $end = $this->get( 'end' );
290
- if ( $this->is_allday() ) {
291
- $end->set_time(
292
- $end->format( 'H' ),
293
- $end->format( 'i' ),
294
- $end->format( 's' ) - 1
295
- );
296
- }
297
- return $end;
298
- }
299
- /**
300
- * Returns enddate specific info.
301
- *
302
- * @return array Date info structure.
303
- */
304
- public function getenddate_info() {
305
- $end = $this->getenddate();
306
- return array(
307
- 'month' => $this->get( 'end' )->format_i18n( 'M' ),
308
- 'day' => $this->get( 'end' )->format_i18n( 'j' ),
309
- 'weekday' => $this->get( 'end' )->format_i18n( 'D' ),
310
- 'year' => $this->get( 'end' )->format_i18n( 'Y' ),
311
- );
312
- }
313
-
314
- /**
315
- * Create new event object, using provided data for initialization.
316
- *
317
- * @param Ai1ec_Registry_Object $registry Injected object registry.
318
- * @param int|array|null $data Look up post with id $data, or
319
- * initialize fields with associative
320
- * array $data containing both post
321
- * and event fields.
322
- * @param int|bool $instance Optionally instance ID. When ID
323
- * value is -1 then it is
324
- * retrieved from db.
325
- *
326
- * @throws Ai1ec_Invalid_Argument_Exception When $data is not one
327
- * of int|array|null.
328
- * @throws Ai1ec_Event_Not_Found_Exception When $data relates to
329
- * non-existent ID.
330
- *
331
- */
332
- function __construct(
333
- Ai1ec_Registry_Object $registry,
334
- $data = null,
335
- $instance = false
336
- ) {
337
- parent::__construct( $registry );
338
- $this->_entity = $this->_registry->get( 'model.event.entity' );
339
- if ( null === $data ) {
340
- return; // empty object
341
- } else if ( is_numeric( $data ) ) {
342
- $this->initialize_from_id( $data, $instance );
343
- } else if ( is_array( $data ) ) {
344
- $this->initialize_from_array( $data );
345
- } else {
346
- throw new Ai1ec_Invalid_Argument_Exception(
347
- 'Argument to constructor must be integer, array or null' .
348
- ', not ' . var_export( $data, true )
349
- );
350
- }
351
-
352
- if ( $this->is_allday() ) {
353
- try {
354
- $timezone = $this->_registry->get( 'date.timezone' )
355
- ->get( $this->get( 'timezone_name' ) );
356
- $this->_entity->set_preferred_timezone( $timezone );
357
- } catch ( Exception $excpt ) {
358
- // ignore
359
- }
360
- }
361
- }
362
-
363
- /**
364
- * Twig method for retrieving avatar.
365
- *
366
- * @param bool $wrap_permalink Whether to wrap avatar in <a> element or not
367
- *
368
- * @return string Avatar markup
369
- */
370
- public function getavatar( $wrap_permalink = true ) {
371
- return $this->_registry->
372
- get( 'view.event.avatar' )->get_event_avatar(
373
- $this,
374
- $this->_registry->get( 'view.calendar.fallbacks' )->get_all(),
375
- '',
376
- $wrap_permalink
377
- );
378
- }
379
-
380
- /**
381
- * Returns whether Event has geo information.
382
- *
383
- * @return bool True or false.
384
- */
385
- public function has_geoinformation() {
386
- $latitude = floatval( $this->get( 'latitude') );
387
- $longitude = floatval( $this->get( 'longitude' ) );
388
- return (
389
- (
390
- $latitude >= 0.000000000000001 ||
391
- $latitude <= -0.000000000000001
392
- ) &&
393
- (
394
- $longitude >= 0.000000000000001 ||
395
- $longitude <= -0.000000000000001
396
- )
397
- );
398
- }
399
-
400
- protected function _handle_property_construct_recurrence_dates( $value ) {
401
- if ( $value ) {
402
- $this->_entity->set( 'recurrence_rules', 'RDATE=' . $value );
403
- }
404
- return $value;
405
- }
406
-
407
- protected function _handle_property_construct_exception_dates( $value ) {
408
- if ( $value ) {
409
- $this->_entity->set( 'exception_rules', 'EXDATE=' . $value );
410
- }
411
- return $value;
412
- }
413
-
414
- /**
415
- * Handle `cost` value reading from permanent storage.
416
- *
417
- * @param string $value Value stored in permanent storage
418
- *
419
- * @return bool Success: true, always
420
- */
421
- protected function _handle_property_construct_cost( $value ) {
422
- $test_value = false;
423
- if (
424
- isset( $value{1} ) && (
425
- ':' === $value{1} || ';' === $value{1}
426
- )
427
- ) {
428
- $test_value = unserialize( $value );
429
- }
430
- $cost = $is_free = NULL;
431
- if ( false === $test_value ) {
432
- $cost = trim( $value );
433
- $is_free = false;
434
- } else {
435
- extract( $test_value, EXTR_IF_EXISTS );
436
- }
437
- $this->_entity->set( 'is_free', (bool)$is_free );
438
- return (string)$cost;
439
- }
440
-
441
- public function get_uid_pattern() {
442
- static $format = null;
443
- if ( null === $format ) {
444
- $site_url = parse_url( ai1ec_get_site_url() );
445
- $format = 'ai1ec-%d@' . $site_url['host'];
446
- if ( isset( $site_url['path'] ) ) {
447
- $format .= $site_url['path'];
448
- }
449
- }
450
- return $format;
451
- }
452
-
453
- /**
454
- * Get UID to be used for current event.
455
- *
456
- * The generated format is cached in static variable within this function
457
- * to re-use when generating UIDs for different entries.
458
- *
459
- * @return string Generated UID.
460
- *
461
- * @staticvar string $format Cached format.
462
- */
463
- public function get_uid() {
464
- $ical_uid = $this->get( 'ical_uid' );
465
- if ( ! empty( $ical_uid ) ) {
466
- return $ical_uid;
467
- }
468
- return sprintf( $this->get_uid_pattern(), $this->get( 'post_id' ) );
469
- }
470
-
471
- /**
472
- * Check if event is free.
473
- *
474
- * @return bool Free status.
475
- */
476
- public function is_free() {
477
- return (bool)$this->get( 'is_free' );
478
- }
479
-
480
- /**
481
- * Check if event is taking all day.
482
- *
483
- * @return bool True for all-day long events.
484
- */
485
- public function is_allday() {
486
- return (bool)$this->get( 'allday' );
487
- }
488
-
489
- /**
490
- * Check if event has virtually no time.
491
- *
492
- * @return bool True for instant events.
493
- */
494
- public function is_instant() {
495
- return (bool)$this->get( 'instant_event' );
496
- }
497
-
498
- /**
499
- * Check if event is taking multiple days.
500
- *
501
- * Uses object-wide variable {@see self::$_is_multiday} to store
502
- * calculated value after first call.
503
- *
504
- * @return bool True for multiday events.
505
- */
506
- public function is_multiday() {
507
- if ( null === $this->_is_multiday ) {
508
- $start = $this->get( 'start' );
509
- $end = $this->get( 'end' );
510
- $diff = $end->diff_sec( $start );
511
- $this->_is_multiday = $diff > 86400 &&
512
- $start->format( 'Y-m-d' ) !== $end->format( 'Y-m-d' );
513
- }
514
- return $this->_is_multiday;
515
- }
516
-
517
- /**
518
- * Get the duration of the event
519
- *
520
- * @return number
521
- */
522
- public function get_duration() {
523
- $duration = $this->get_runtime( 'duration', null );
524
- if ( null === $duration ) {
525
- $duration = $this->get( 'end' )->format() -
526
- $this->get( 'start' )->format();
527
- $this->set_runtime( 'duration', $duration );
528
- }
529
- return $duration;
530
- }
531
-
532
- /**
533
- * Create/update entity representation.
534
- *
535
- * Saves the current event data to the database. If $this->post_id exists,
536
- * but $update is false, creates a new record in the ai1ec_events table of
537
- * this event data, but does not try to create a new post. Else if $update
538
- * is true, updates existing event record. If $this->post_id is empty,
539
- * creates a new post AND record in the ai1ec_events table for this event.
540
- *
541
- * @param bool $update Whether to update an existing event or create a
542
- * new one
543
- * @param bool $backward_compatibility The (wpdb) ofr the new wordpress 4.4
544
- * now inserts NULL as null values. The previous version, if you insert a NULL
545
- * value in an int value, the values saved would be 0 instead of null.
546
- * @return int The post_id of the new or existing event.
547
- */
548
- function save( $update = false, $backward_compatibility = true ) {
549
- do_action( 'ai1ec_pre_save_event', $this, $update );
550
- if ( ! $update ) {
551
- $response = apply_filters( 'ai1ec_event_save_new', $this );
552
- if ( is_wp_error( $response ) ) {
553
- throw new Ai1ec_Event_Create_Exception(
554
- 'Failed to create event: ' . $response->get_error_message()
555
- );
556
- }
557
- }
558
-
559
- $dbi = $this->_registry->get( 'dbi.dbi' );
560
- $columns = $this->prepare_store_entity();
561
- $format = $this->prepare_store_format( $columns, $backward_compatibility );
562
- $table_name = $dbi->get_table_name( 'ai1ec_events' );
563
- $post_id = $columns['post_id'];
564
-
565
- if ( $this->get( 'end' )->is_empty() ) {
566
- $this->set_no_end_time();
567
- }
568
- if ( $post_id ) {
569
- $success = false;
570
- if ( ! $update ) {
571
- $success = $dbi->insert(
572
- $table_name,
573
- $columns,
574
- $format
575
- );
576
- } else {
577
- $success = $dbi->update(
578
- $table_name,
579
- $columns,
580
- array( 'post_id' => $columns['post_id'] ),
581
- $format,
582
- array( '%d' )
583
- );
584
- }
585
- if ( false === $success ) {
586
- return false;
587
- }
588
-
589
- } else {
590
- // ===================
591
- // = Insert new post =
592
- // ===================
593
- $post_id = wp_insert_post( $this->get( 'post' ), false );
594
- if ( 0 === $post_id ) {
595
- return false;
596
- }
597
- $this->set( 'post_id', $post_id );
598
- $columns['post_id'] = $post_id;
599
-
600
- // =========================
601
- // = Insert new event data =
602
- // =========================
603
- if ( false === $dbi->insert( $table_name, $columns, $format ) ) {
604
- return false;
605
- }
606
- }
607
-
608
- $taxonomy = $this->_registry->get(
609
- 'model.event.taxonomy',
610
- $post_id
611
- );
612
- $cats = $this->get( 'categories' );
613
- if (
614
- is_array( $cats ) &&
615
- ! empty( $cats )
616
- ) {
617
- $taxonomy->set_categories( $cats );
618
- }
619
- $tags = $this->get( 'tags' );
620
- if (
621
- is_array( $tags ) &&
622
- ! empty( $tags )
623
- ) {
624
- $taxonomy->set_tags( $tags );
625
- }
626
-
627
- if (
628
- $feed = $this->get( 'feed' ) &&
629
- isset( $feed->feed_id )
630
- ) {
631
- $taxonomy->set_feed( $feed );
632
- }
633
-
634
- // give other plugins / extensions the ability to do things
635
- // when saving, like fetching authors which i removed as it's not core.
636
- do_action( 'ai1ec_save_event' );
637
-
638
- $instance_model = $this->_registry->get( 'model.event.instance' );
639
- $instance_model->recreate( $this );
640
-
641
- do_action( 'ai1ec_event_saved', $post_id, $this, $update );
642
- return $post_id;
643
- }
644
-
645
- /**
646
- * Prepare fields format flags to use in database operations.
647
- *
648
- * @param array $columns Array of columns with data to insert.
649
- *
650
- * @return array List of format flags to use in integrations with DBI.
651
- */
652
- public function prepare_store_format( array &$columns, $backward_compatibility = true ) {
653
- $format = array(
654
- '%d', // post_id
655
- '%d', // start
656
- '%d', // end
657
- '%s', // timezone_name
658
- '%d', // allday
659
- '%d', // instant_event
660
- '%s', // recurrence_rules
661
- '%s', // exception_rules
662
- '%s', // recurrence_dates
663
- '%s', // exception_dates
664
- '%s', // venue
665
- '%s', // country
666
- '%s', // address
667
- '%s', // city
668
- '%s', // province
669
- '%s', // postal_code
670
- '%d', // show_map
671
- '%s', // contact_name
672
- '%s', // contact_phone
673
- '%s', // contact_email
674
- '%s', // contact_url
675
- '%s', // cost
676
- '%s', // ticket_url
677
- '%s', // ical_feed_url
678
- '%s', // ical_source_url
679
- '%s', // ical_uid
680
- '%d', // show_coordinates
681
- '%f', // latitude
682
- '%f', // longitude
683
- );
684
-
685
- if ( $backward_compatibility ) {
686
- $columns_count = count( $columns );
687
- if ( count( $format ) !== $columns_count ) {
688
- throw new Ai1ec_Event_Not_Found_Exception(
689
- 'Data columns count differs from format columns count'
690
- );
691
- }
692
- $index = 0;
693
- foreach ( $columns as $key => $value ) {
694
- if ( '%d' === $format[ $index ] ) {
695
- if ( is_null( $value ) ) {
696
- $columns[ $key ] = 0;
697
- }
698
- }
699
- $index++;
700
- }
701
- }
702
-
703
- return $format;
704
- }
705
-
706
- /**
707
- * Prepare event entity {@see self::$_entity} for persistent storage.
708
- *
709
- * Creates an array of database fields and corresponding values.
710
- *
711
- * @return array Map of fields to store.
712
- */
713
- public function prepare_store_entity() {
714
- $entity = array(
715
- 'post_id' => $this->storage_format( 'post_id' ),
716
- 'start' => $this->storage_format( 'start' ),
717
- 'end' => $this->storage_format( 'end' ),
718
- 'timezone_name' => $this->storage_format( 'timezone_name' ),
719
- 'allday' => $this->storage_format( 'allday' ),
720
- 'instant_event' => $this->storage_format( 'instant_event' ),
721
- 'recurrence_rules' => $this->storage_format( 'recurrence_rules' ),
722
- 'exception_rules' => $this->storage_format( 'exception_rules' ),
723
- 'recurrence_dates' => $this->storage_format( 'recurrence_dates' ),
724
- 'exception_dates' => $this->storage_format( 'exception_dates' ),
725
- 'venue' => $this->storage_format( 'venue' ),
726
- 'country' => $this->storage_format( 'country' ),
727
- 'address' => $this->storage_format( 'address' ),
728
- 'city' => $this->storage_format( 'city' ),
729
- 'province' => $this->storage_format( 'province' ),
730
- 'postal_code' => $this->storage_format( 'postal_code' ),
731
- 'show_map' => $this->storage_format( 'show_map' ),
732
- 'contact_name' => $this->storage_format( 'contact_name' ),
733
- 'contact_phone' => $this->storage_format( 'contact_phone' ),
734
- 'contact_email' => $this->storage_format( 'contact_email' ),
735
- 'contact_url' => $this->storage_format( 'contact_url' ),
736
- 'cost' => $this->storage_format( 'cost' ),
737
- 'ticket_url' => $this->storage_format( 'ticket_url' ),
738
- 'ical_feed_url' => $this->storage_format( 'ical_feed_url' ),
739
- 'ical_source_url' => $this->storage_format( 'ical_source_url' ),
740
- 'ical_uid' => $this->storage_format( 'ical_uid' ),
741
- 'show_coordinates' => $this->storage_format( 'show_coordinates' ),
742
- 'latitude' => $this->storage_format( 'latitude', '' ),
743
- 'longitude' => $this->storage_format( 'longitude', '' ),
744
- );
745
- return $entity;
746
- }
747
-
748
- /**
749
- * Compact field for writing to persistent storage.
750
- *
751
- * @param string $field Name of field to compact.
752
- * @param mixed $default Default value to use for undescribed fields.
753
- *
754
- * @return mixed Value or $default.
755
- */
756
- public function storage_format( $field, $default = null ) {
757
- $value = $this->_entity->get( $field, $default );
758
- if (
759
- isset( $this->_swizzable[$field] ) &&
760
- $this->_swizzable[$field] <= 0
761
- ) {
762
- $value = $this->{ '_handle_property_destruct_' . $field }( $value );
763
- }
764
- return $value;
765
- }
766
-
767
- /**
768
- * Allow properties to be modified after cloning.
769
- *
770
- * @return void
771
- */
772
- public function __clone() {
773
- $this->_entity = clone $this->_entity;
774
- }
775
-
776
- /**
777
- * Decode timezone to use for event.
778
- *
779
- * Following algorythm is used to detect a value:
780
- * - take value provided in input;
781
- * - if empty - take value associated with start time;
782
- * - if empty - take current environment timezone.
783
- *
784
- * @param string $timezone_name Timezone provided in input.
785
- *
786
- * @return string Timezone name to use for event in future.
787
- */
788
- protected function _handle_property_destruct_timezone_name(
789
- $timezone_name
790
- ) {
791
- if ( empty( $timezone_name ) ) {
792
- $timezone_name = $this->get( 'start' )->get_timezone();
793
- if ( empty( $timezone_name ) ) {
794
- $timezone_name = $this->_registry->get( 'date.timezone' )
795
- ->get_default_timezone();
796
- }
797
- }
798
- return $timezone_name;
799
- }
800
-
801
- /**
802
- * Format datetime to UNIX timestamp for storage.
803
- *
804
- * @param Ai1ec_Date_Time $start Datetime object to compact.
805
- *
806
- * @return int UNIX timestamp.
807
- */
808
- protected function _handle_property_destruct_start( Ai1ec_Date_Time $start ) {
809
- return $start->format_to_gmt();
810
- }
811
-
812
- /**
813
- * Format datetime to UNIX timestamp for storage.
814
- *
815
- * @param Ai1ec_Date_Time $end Datetime object to compact.
816
- *
817
- * @return int UNIX timestamp.
818
- */
819
- protected function _handle_property_destruct_end( Ai1ec_Date_Time $end ) {
820
- return $end->format_to_gmt();
821
- }
822
-
823
- /**
824
- * Handle `cost` writing to permanent storage.
825
- *
826
- * @param string $cost Value of cost.
827
- *
828
- * @return string Serialized value to store.
829
- */
830
- protected function _handle_property_destruct_cost( $cost ) {
831
- $cost = array(
832
- 'cost' => $cost,
833
- 'is_free' => false,
834
- );
835
- if ( $this->get( 'is_free' ) ) {
836
- $cost['is_free'] = true;
837
- }
838
- return serialize( $cost );
839
- }
840
-
841
- /**
842
- * Get the submitter information array
843
- * @return array (
844
- * is_organizer => 1 if the organizer is the submitter,
845
- * email => if is_organizer is 0, them this property has the email of the submitter,
846
- * name => if is_organizer is 0, them this property has the name of the submitter
847
- * )
848
- */
849
- public function get_submitter_info() {
850
- $post_id = $this->get( 'post_id' );
851
- if ( empty( $post_id ) ) {
852
- return null;
853
- }
854
- $submitter_info = get_post_meta(
855
- $post_id,
856
- '_submitter_info',
857
- true
858
- );
859
- if ( false == ai1ec_is_blank( $submitter_info ) ) {
860
- $submitter_info = json_decode( $submitter_info, true );
861
- if ( is_array( $submitter_info ) ) {
862
- return $submitter_info;
863
- }
864
- }
865
- return null;
866
- }
867
-
868
- /**
869
- * Save the submitter information into post metadata
870
- */
871
- public function save_submitter_info( $is_submitter, $submitter_email, $submitter_name ) {
872
- $post_id = $this->get( 'post_id' );
873
- if ( empty( $post_id ) ) {
874
- throw new Exception( 'Post id empty' );
875
- }
876
- $save = false;
877
- if ( 1 === intval( $is_submitter ) ) {
878
- $submitter_info['is_organizer'] = 1;
879
- if ( false === ai1ec_is_blank( $this->get( 'contact_email' ) ) ) {
880
- $save = true;
881
- }
882
- } else {
883
- $submitter_info['is_organizer'] = 0;
884
- if ( false === ai1ec_is_blank( $submitter_email ) ) {
885
- $submitter_info['email'] = trim( $submitter_email );
886
- $submitter_info['name'] = trim( $submitter_name );
887
- $save = true;
888
- }
889
- }
890
- if ( $save ) {
891
- update_post_meta( $post_id, '_submitter_info', json_encode( $submitter_info ) );
892
- }
893
- }
894
 
895
  }
11
  */
12
  class Ai1ec_Event extends Ai1ec_Base {
13
 
14
+ /**
15
+ * @var Ai1ec_Event_Entity Data store object reference.
16
+ */
17
+ protected $_entity = null;
18
+
19
+ /**
20
+ * @var array Map of fields that require special care during set/get
21
+ * operations. Values have following meanings:
22
+ * [0] - both way care required;
23
+ * [1] - only `set` operations require care;
24
+ * [-1] - only `get` (for storage) operations require care.
25
+ */
26
+ protected $_swizzable = array(
27
+ 'cost' => 0,
28
+ 'start' => -1,
29
+ 'end' => -1,
30
+ 'timezone_name' => -1,
31
+ 'recurrence_dates' => 1,
32
+ 'exception_dates' => 1,
33
+ );
34
+
35
+ /**
36
+ * @var array Runtime properties
37
+ */
38
+ protected $_runtime_props = array();
39
+
40
+ /**
41
+ * @var bool|null Boolean cache-definition indicating if event is multiday.
42
+ */
43
+ protected $_is_multiday = null;
44
+
45
+ /**
46
+ * Wrapper to get property value.
47
+ *
48
+ * @param string $property Name of property to get.
49
+ * @param mixed $default Default value to return.
50
+ *
51
+ * @return mixed Actual property.
52
+ */
53
+ public function get( $property, $default = null ) {
54
+ return $this->_entity->get( $property, $default );
55
+ }
56
+
57
+ /**
58
+ * Get properties generated at runtime
59
+ *
60
+ * @param string $property
61
+ *
62
+ * @return string
63
+ */
64
+ public function get_runtime( $property, $default = '' ) {
65
+ return isset( $this->_runtime_props[$property] ) ?
66
+ $this->_runtime_props[$property] :
67
+ $default;
68
+ }
69
+
70
+ /**
71
+ * Set properties generated at runtime
72
+ *
73
+ * @param string $property
74
+ * @param string $value
75
+ */
76
+ public function set_runtime( $property, $value ) {
77
+ $this->_runtime_props[$property] = $value;
78
+ }
79
+
80
+ /**
81
+ * Handle property initiation.
82
+ *
83
+ * Decides, how to extract value stored in permanent storage.
84
+ *
85
+ * @param string $property Name of property to handle
86
+ * @param mixed $value Value, read from permanent storage
87
+ *
88
+ * @return bool Success
89
+ */
90
+ public function set( $property, $value ) {
91
+ if (
92
+ isset( $this->_swizzable[$property] ) &&
93
+ $this->_swizzable[$property] >= 0
94
+ ) {
95
+ $method = '_handle_property_construct_' . $property;
96
+ $value = $this->{$method}( $value );
97
+ }
98
+ $this->_entity->set( $property, $value );
99
+ return $this;
100
+ }
101
+
102
+ /**
103
+ * Set the event is all day, during the specified number of days
104
+ *
105
+ * @param number $length
106
+ */
107
+ public function set_all_day( $length = 1 ) {
108
+ // set allday as true
109
+ $this->set( 'allday', true );
110
+ $start = $this->get( 'start' );
111
+ // reset time component
112
+ $start->set_time( 0, 0, 0 );
113
+ $end = $this->_registry->get( 'date.time', $start );
114
+ // set the correct length
115
+ $end->adjust_day( $length );
116
+ $this->set( 'end', $end );
117
+ }
118
+
119
+ /**
120
+ * Set the event as if it has no end time
121
+ */
122
+ public function set_no_end_time() {
123
+ $this->set( 'instant_event', true );
124
+ $start = $this->get( 'start' );
125
+ $end = $this->_registry->get( 'date.time', $start );
126
+ $end->set_time(
127
+ $start->format( 'H' ),
128
+ $start->format( 'i' ) + 15,
129
+ $start->format( 's' )
130
+ );
131
+ $this->set( 'end', $end );
132
+ }
133
+
134
+ /**
135
+ * Set object fields from arbitrary array.
136
+ *
137
+ * @param array $data Supposedly map of fields to initiate.
138
+ *
139
+ * @return Ai1ec_Event Instance of self for chaining.
140
+ */
141
+ public function initialize_from_array( array $data ) {
142
+
143
+ // =======================================================
144
+ // = Assign each event field the value from the database =
145
+ // =======================================================
146
+ foreach ( $this->_entity->list_properties() as $property ) {
147
+ if ( 'post' !== $property && isset( $data[$property] ) ) {
148
+ $this->set( $property, $data[$property] );
149
+ unset( $data[$property] );
150
+ }
151
+ }
152
+ if ( isset( $data['post'] ) ) {
153
+ $this->set( 'post', (object)$data['post'] );
154
+ } else {
155
+ // ========================================
156
+ // = Remaining fields are the post fields =
157
+ // ========================================
158
+ $this->set( 'post', (object)$data );
159
+ }
160
+ return $this;
161
+ }
162
+
163
+ /**
164
+ * Delete the events from all tables
165
+ */
166
+ public function delete() {
167
+ // delete post (this will trigger deletion of cached events, and
168
+ // remove the event from events table)
169
+ wp_delete_post( $this->get( 'post_id' ), true );
170
+ }
171
+
172
+ /**
173
+ * Initialize object from ID.
174
+ *
175
+ * Attempts to retrieve entity from database and if succeeds - uses
176
+ * {@see self::initialize_from_array} to initiate actual values.
177
+ *
178
+ * @param int $post_id ID of post (event) to initiate.
179
+ * @param int|bool $instance ID of event instance, false for base event.
180
+ *
181
+ * @return Ai1ec_Event Instance of self for chaining.
182
+ *
183
+ * @throws Ai1ec_Event_Not_Found_Exception If entity is not locatable.
184
+ */
185
+ public function initialize_from_id( $post_id, $instance = false ) {
186
+ $post = get_post( $post_id );
187
+ if ( ! $post || $post->post_status == 'auto-draft' ) {
188
+ throw new Ai1ec_Event_Not_Found_Exception(
189
+ 'Post with ID \'' . $post_id .
190
+ '\' could not be retrieved from the database.'
191
+ );
192
+ }
193
+ $post_id = (int)$post_id;
194
+ $dbi = $this->_registry->get( 'dbi.dbi' );
195
+
196
+ $left_join = '';
197
+ $select_sql = '
198
+ e.post_id,
199
+ e.timezone_name,
200
+ e.recurrence_rules,
201
+ e.exception_rules,
202
+ e.allday,
203
+ e.instant_event,
204
+ e.recurrence_dates,
205
+ e.exception_dates,
206
+ e.venue,
207
+ e.country,
208
+ e.address,
209
+ e.city,
210
+ e.province,
211
+ e.postal_code,
212
+ e.show_map,
213
+ e.contact_name,
214
+ e.contact_phone,
215
+ e.contact_email,
216
+ e.contact_url,
217
+ e.cost,
218
+ e.ticket_url,
219
+ e.ical_feed_url,
220
+ e.ical_source_url,
221
+ e.ical_organizer,
222
+ e.ical_contact,
223
+ e.ical_uid,
224
+ e.longitude,
225
+ e.latitude,
226
+ e.show_coordinates,
227
+ GROUP_CONCAT( ttc.term_id ) AS categories,
228
+ GROUP_CONCAT( ttt.term_id ) AS tags
229
+ ';
230
+
231
+ if (
232
+ false !== $instance &&
233
+ is_numeric( $instance ) &&
234
+ $instance > 0
235
+ ) {
236
+ $select_sql .= ', IF( aei.start IS NOT NULL, aei.start, e.start ) as start,' .
237
+ ' IF( aei.start IS NOT NULL, aei.end, e.end ) as end ';
238
+
239
+ $instance = (int)$instance;
240
+ $this->set( 'instance_id', $instance );
241
+ $left_join = 'LEFT JOIN ' . $dbi->get_table_name( 'ai1ec_event_instances' ) .
242
+ ' aei ON aei.id = ' . $instance . ' AND e.post_id = aei.post_id ';
243
+ } else {
244
+ $select_sql .= ', e.start as start, e.end as end, e.allday ';
245
+ if ( -1 === (int)$instance ) {
246
+ $select_sql .= ', aei.id as instance_id ';
247
+ $left_join = 'LEFT JOIN ' .
248
+ $dbi->get_table_name( 'ai1ec_event_instances' ) .
249
+ ' aei ON e.post_id = aei.post_id ' .
250
+ 'AND e.start = aei.start AND e.end = aei.end ';
251
+ }
252
+ }
253
+
254
+ // =============================
255
+ // = Fetch event from database =
256
+ // =============================
257
+ $query = 'SELECT ' . $select_sql . '
258
+ FROM ' . $dbi->get_table_name( 'ai1ec_events' ) . ' e
259
+ LEFT JOIN ' .
260
+ $dbi->get_table_name( 'term_relationships' ) . ' tr
261
+ ON ( e.post_id = tr.object_id )
262
+ LEFT JOIN ' . $dbi->get_table_name( 'term_taxonomy' ) . ' ttc
263
+ ON (
264
+ tr.term_taxonomy_id = ttc.term_taxonomy_id AND
265
+ ttc.taxonomy = \'events_categories\'
266
+ )
267
+ LEFT JOIN ' . $dbi->get_table_name( 'term_taxonomy' ) . ' ttt
268
+ ON (
269
+ tr.term_taxonomy_id = ttt.term_taxonomy_id AND
270
+ ttt.taxonomy = \'events_tags\'
271
+ )
272
+ ' . $left_join . '
273
+ WHERE e.post_id = ' . $post_id . '
274
+ GROUP BY e.post_id';
275
+
276
+ $event = $dbi->get_row( $query, ARRAY_A );
277
+ if ( null === $event || null === $event['post_id'] ) {
278
+ throw new Ai1ec_Event_Not_Found_Exception(
279
+ 'Event with ID \'' . $post_id .
280
+ '\' could not be retrieved from the database.'
281
+ );
282
+ }
283
+
284
+ $event['post'] = $post;
285
+ return $this->initialize_from_array( $event );
286
+ }
287
+
288
+ public function getenddate() {
289
+ $end = $this->get( 'end' );
290
+ if ( $this->is_allday() ) {
291
+ $end->set_time(
292
+ $end->format( 'H' ),
293
+ $end->format( 'i' ),
294
+ $end->format( 's' ) - 1
295
+ );
296
+ }
297
+ return $end;
298
+ }
299
+ /**
300
+ * Returns enddate specific info.
301
+ *
302
+ * @return array Date info structure.
303
+ */
304
+ public function getenddate_info() {
305
+ $end = $this->getenddate();
306
+ return array(
307
+ 'month' => $this->get( 'end' )->format_i18n( 'M' ),
308
+ 'day' => $this->get( 'end' )->format_i18n( 'j' ),
309
+ 'weekday' => $this->get( 'end' )->format_i18n( 'D' ),
310
+ 'year' => $this->get( 'end' )->format_i18n( 'Y' ),
311
+ );
312
+ }
313
+
314
+ /**
315
+ * Create new event object, using provided data for initialization.
316
+ *
317
+ * @param Ai1ec_Registry_Object $registry Injected object registry.
318
+ * @param int|array|null $data Look up post with id $data, or
319
+ * initialize fields with associative
320
+ * array $data containing both post
321
+ * and event fields.
322
+ * @param int|bool $instance Optionally instance ID. When ID
323
+ * value is -1 then it is
324
+ * retrieved from db.
325
+ *
326
+ * @throws Ai1ec_Invalid_Argument_Exception When $data is not one
327
+ * of int|array|null.
328
+ * @throws Ai1ec_Event_Not_Found_Exception When $data relates to
329
+ * non-existent ID.
330
+ *
331
+ */
332
+ function __construct(
333
+ Ai1ec_Registry_Object $registry,
334
+ $data = null,
335
+ $instance = false
336
+ ) {
337
+ parent::__construct( $registry );
338
+ $this->_entity = $this->_registry->get( 'model.event.entity' );
339
+ if ( null === $data ) {
340
+ return; // empty object
341
+ } else if ( is_numeric( $data ) ) {
342
+ $this->initialize_from_id( $data, $instance );
343
+ } else if ( is_array( $data ) ) {
344
+ $this->initialize_from_array( $data );
345
+ } else {
346
+ throw new Ai1ec_Invalid_Argument_Exception(
347
+ 'Argument to constructor must be integer, array or null' .
348
+ ', not ' . var_export( $data, true )
349
+ );
350
+ }
351
+
352
+ if ( $this->is_allday() ) {
353
+ try {
354
+ $timezone = $this->_registry->get( 'date.timezone' )
355
+ ->get( $this->get( 'timezone_name' ) );
356
+ $this->_entity->set_preferred_timezone( $timezone );
357
+ } catch ( Exception $excpt ) {
358
+ // ignore
359
+ }
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Twig method for retrieving avatar.
365
+ *
366
+ * @param bool $wrap_permalink Whether to wrap avatar in <a> element or not
367
+ *
368
+ * @return string Avatar markup
369
+ */
370
+ public function getavatar( $wrap_permalink = true ) {
371
+ return $this->_registry->
372
+ get( 'view.event.avatar' )->get_event_avatar(
373
+ $this,
374
+ $this->_registry->get( 'view.calendar.fallbacks' )->get_all(),
375
+ '',
376
+ $wrap_permalink
377
+ );
378
+ }
379
+
380
+ /**
381
+ * Returns whether Event has geo information.
382
+ *
383
+ * @return bool True or false.
384
+ */
385
+ public function has_geoinformation() {
386
+ $latitude = floatval( $this->get( 'latitude') );
387
+ $longitude = floatval( $this->get( 'longitude' ) );
388
+ return (
389
+ (
390
+ $latitude >= 0.000000000000001 ||
391
+ $latitude <= -0.000000000000001
392
+ ) &&
393
+ (
394
+ $longitude >= 0.000000000000001 ||
395
+ $longitude <= -0.000000000000001
396
+ )
397
+ );
398
+ }
399
+
400
+ protected function _handle_property_construct_recurrence_dates( $value ) {
401
+ if ( $value ) {
402
+ $this->_entity->set( 'recurrence_rules', 'RDATE=' . $value );
403
+ }
404
+ return $value;
405
+ }
406
+
407
+ protected function _handle_property_construct_exception_dates( $value ) {
408
+ if ( $value ) {
409
+ $this->_entity->set( 'exception_rules', 'EXDATE=' . $value );
410
+ }
411
+ return $value;
412
+ }
413
+
414
+ /**
415
+ * Handle `cost` value reading from permanent storage.
416
+ *
417
+ * @param string $value Value stored in permanent storage
418
+ *
419
+ * @return bool Success: true, always
420
+ */
421
+ protected function _handle_property_construct_cost( $value ) {
422
+ $test_value = false;
423
+ if (
424
+ isset( $value{1} ) && (
425
+ ':' === $value{1} || ';' === $value{1}
426
+ )
427
+ ) {
428
+ $test_value = unserialize( $value );
429
+ }
430
+ $cost = $is_free = NULL;
431
+ if ( false === $test_value ) {
432
+ $cost = trim( $value );
433
+ $is_free = false;
434
+ } else {
435
+ extract( $test_value, EXTR_IF_EXISTS );
436
+ }
437
+ $this->_entity->set( 'is_free', (bool)$is_free );
438
+ return (string)$cost;
439
+ }
440
+
441
+ public function get_uid_pattern() {
442
+ static $format = null;
443
+ if ( null === $format ) {
444
+ $site_url = parse_url( ai1ec_get_site_url() );
445
+ $format = 'ai1ec-%d@' . $site_url['host'];
446
+ if ( isset( $site_url['path'] ) ) {
447
+ $format .= $site_url['path'];
448
+ }
449
+ }
450
+ return $format;
451
+ }
452
+
453
+ /**
454
+ * Get UID to be used for current event.
455
+ *
456
+ * The generated format is cached in static variable within this function
457
+ * to re-use when generating UIDs for different entries.
458
+ *
459
+ * @return string Generated UID.
460
+ *
461
+ * @staticvar string $format Cached format.
462
+ */
463
+ public function get_uid() {
464
+ $ical_uid = $this->get( 'ical_uid' );
465
+ if ( ! empty( $ical_uid ) ) {
466
+ return $ical_uid;
467
+ }
468
+ return sprintf( $this->get_uid_pattern(), $this->get( 'post_id' ) );
469
+ }
470
+
471
+ /**
472
+ * Check if event is free.
473
+ *
474
+ * @return bool Free status.
475
+ */
476
+ public function is_free() {
477
+ return (bool)$this->get( 'is_free' );
478
+ }
479
+
480
+ /**
481
+ * Check if event is taking all day.
482
+ *
483
+ * @return bool True for all-day long events.
484
+ */
485
+ public function is_allday() {
486
+ return (bool)$this->get( 'allday' );
487
+ }
488
+
489
+ /**
490
+ * Check if event has virtually no time.
491
+ *
492
+ * @return bool True for instant events.
493
+ */
494
+ public function is_instant() {
495
+ return (bool)$this->get( 'instant_event' );
496
+ }
497
+
498
+ /**
499
+ * Check if event is taking multiple days.
500
+ *
501
+ * Uses object-wide variable {@see self::$_is_multiday} to store
502
+ * calculated value after first call.
503
+ *
504
+ * @return bool True for multiday events.
505
+ */
506
+ public function is_multiday() {
507
+ if ( null === $this->_is_multiday ) {
508
+ $start = $this->get( 'start' );
509
+ $end = $this->get( 'end' );
510
+ $diff = $end->diff_sec( $start );
511
+ $this->_is_multiday = $diff > 86400 &&
512
+ $start->format( 'Y-m-d' ) !== $end->format( 'Y-m-d' );
513
+ }
514
+ return $this->_is_multiday;
515
+ }
516
+
517
+ /**
518
+ * Get the duration of the event
519
+ *
520
+ * @return number
521
+ */
522
+ public function get_duration() {
523
+ $duration = $this->get_runtime( 'duration', null );
524
+ if ( null === $duration ) {
525
+ $duration = $this->get( 'end' )->format() -
526
+ $this->get( 'start' )->format();
527
+ $this->set_runtime( 'duration', $duration );
528
+ }
529
+ return $duration;
530
+ }
531
+
532
+ /**
533
+ * Create/update entity representation.
534
+ *
535
+ * Saves the current event data to the database. If $this->post_id exists,
536
+ * but $update is false, creates a new record in the ai1ec_events table of
537
+ * this event data, but does not try to create a new post. Else if $update
538
+ * is true, updates existing event record. If $this->post_id is empty,
539
+ * creates a new post AND record in the ai1ec_events table for this event.
540
+ *
541
+ * @param bool $update Whether to update an existing event or create a
542
+ * new one
543
+ * @param bool $backward_compatibility The (wpdb) ofr the new wordpress 4.4
544
+ * now inserts NULL as null values. The previous version, if you insert a NULL
545
+ * value in an int value, the values saved would be 0 instead of null.
546
+ * @return int The post_id of the new or existing event.
547
+ */
548
+ function save( $update = false, $backward_compatibility = true ) {
549
+ do_action( 'ai1ec_pre_save_event', $this, $update );
550
+ if ( ! $update ) {
551
+ $response = apply_filters( 'ai1ec_event_save_new', $this );
552
+ if ( is_wp_error( $response ) ) {
553
+ throw new Ai1ec_Event_Create_Exception(
554
+ 'Failed to create event: ' . $response->get_error_message()
555
+ );
556
+ }
557
+ }
558
+
559
+ $dbi = $this->_registry->get( 'dbi.dbi' );
560
+ $columns = $this->prepare_store_entity();
561
+ $format = $this->prepare_store_format( $columns, $backward_compatibility );
562
+ $table_name = $dbi->get_table_name( 'ai1ec_events' );
563
+ $post_id = $columns['post_id'];
564
+
565
+ if ( $this->get( 'end' )->is_empty() ) {
566
+ $this->set_no_end_time();
567
+ }
568
+ if ( $post_id ) {
569
+ $success = false;
570
+ if ( ! $update ) {
571
+ $success = $dbi->insert(
572
+ $table_name,
573
+ $columns,
574
+ $format
575
+ );
576
+ } else {
577
+ $success = $dbi->update(
578
+ $table_name,
579
+ $columns,
580
+ array( 'post_id' => $columns['post_id'] ),
581
+ $format,
582
+ array( '%d' )
583
+ );
584
+ }
585
+ if ( false === $success ) {
586
+ return false;
587
+ }
588
+
589
+ } else {
590
+ // ===================
591
+ // = Insert new post =
592
+ // ===================
593
+ $post_id = wp_insert_post( $this->get( 'post' ), false );
594
+ if ( 0 === $post_id ) {
595
+ return false;
596
+ }
597
+ $this->set( 'post_id', $post_id );
598
+ $columns['post_id'] = $post_id;
599
+
600
+ // =========================
601
+ // = Insert new event data =
602
+ // =========================
603
+ if ( false === $dbi->insert( $table_name, $columns, $format ) ) {
604
+ return false;
605
+ }
606
+ }
607
+
608
+ $taxonomy = $this->_registry->get(
609
+ 'model.event.taxonomy',
610
+ $post_id
611
+ );
612
+ $cats = $this->get( 'categories' );
613
+ if (
614
+ is_array( $cats ) &&
615
+ ! empty( $cats )
616
+ ) {
617
+ $taxonomy->set_categories( $cats );
618
+ }
619
+ $tags = $this->get( 'tags' );
620
+ if (
621
+ is_array( $tags ) &&
622
+ ! empty( $tags )
623
+ ) {
624
+ $taxonomy->set_tags( $tags );
625
+ }
626
+
627
+ if (
628
+ $feed = $this->get( 'feed' ) &&
629
+ isset( $feed->feed_id )
630
+ ) {
631
+ $taxonomy->set_feed( $feed );
632
+ }
633
+
634
+ // give other plugins / extensions the ability to do things
635
+ // when saving, like fetching authors which i removed as it's not core.
636
+ do_action( 'ai1ec_save_event' );
637
+
638
+ $instance_model = $this->_registry->get( 'model.event.instance' );
639
+ $instance_model->recreate( $this );
640
+
641
+ do_action( 'ai1ec_event_saved', $post_id, $this, $update );
642
+ return $post_id;
643
+ }
644
+
645
+ /**
646
+ * Prepare fields format flags to use in database operations.
647
+ *
648
+ * @param array $columns Array of columns with data to insert.
649
+ *
650
+ * @return array List of format flags to use in integrations with DBI.
651
+ */
652
+ public function prepare_store_format( array &$columns, $backward_compatibility = true ) {
653
+ $format = array(
654
+ '%d', // post_id
655
+ '%d', // start
656
+ '%d', // end
657
+ '%s', // timezone_name
658
+ '%d', // allday
659
+ '%d', // instant_event
660
+ '%s', // recurrence_rules
661
+ '%s', // exception_rules
662
+ '%s', // recurrence_dates
663
+ '%s', // exception_dates
664
+ '%s', // venue
665
+ '%s', // country
666
+ '%s', // address
667
+ '%s', // city
668
+ '%s', // province
669
+ '%s', // postal_code
670
+ '%d', // show_map
671
+ '%s', // contact_name
672
+ '%s', // contact_phone
673
+ '%s', // contact_email
674
+ '%s', // contact_url
675
+ '%s', // cost
676
+ '%s', // ticket_url
677
+ '%s', // ical_feed_url
678
+ '%s', // ical_source_url
679
+ '%s', // ical_uid
680
+ '%d', // show_coordinates
681
+ '%f', // latitude
682
+ '%f', // longitude
683
+ );
684
+
685
+ if ( $backward_compatibility ) {
686
+ $columns_count = count( $columns );
687
+ if ( count( $format ) !== $columns_count ) {
688
+ throw new Ai1ec_Event_Not_Found_Exception(
689
+ 'Data columns count differs from format columns count'
690
+ );
691
+ }
692
+ $index = 0;
693
+ foreach ( $columns as $key => $value ) {
694
+ if ( '%d' === $format[ $index ] ) {
695
+ if ( is_null( $value ) ) {
696
+ $columns[ $key ] = 0;
697
+ }
698
+ }
699
+ $index++;
700
+ }
701
+ }
702
+
703
+ return $format;
704
+ }
705
+
706
+ /**
707
+ * Prepare event entity {@see self::$_entity} for persistent storage.
708
+ *
709
+ * Creates an array of database fields and corresponding values.
710
+ *
711
+ * @return array Map of fields to store.
712
+ */
713
+ public function prepare_store_entity() {
714
+ $entity = array(
715
+ 'post_id' => $this->storage_format( 'post_id' ),
716
+ 'start' => $this->storage_format( 'start' ),
717
+ 'end' => $this->storage_format( 'end' ),
718
+ 'timezone_name' => $this->storage_format( 'timezone_name' ),
719
+ 'allday' => $this->storage_format( 'allday' ),
720
+ 'instant_event' => $this->storage_format( 'instant_event' ),
721
+ 'recurrence_rules' => $this->storage_format( 'recurrence_rules' ),
722
+ 'exception_rules' => $this->storage_format( 'exception_rules' ),
723
+ 'recurrence_dates' => $this->storage_format( 'recurrence_dates' ),
724
+ 'exception_dates' => $this->storage_format( 'exception_dates' ),
725
+ 'venue' => $this->storage_format( 'venue' ),
726
+ 'country' => $this->storage_format( 'country' ),
727
+ 'address' => $this->storage_format( 'address' ),
728
+ 'city' => $this->storage_format( 'city' ),
729
+ 'province' => $this->storage_format( 'province' ),
730
+ 'postal_code' => $this->storage_format( 'postal_code' ),
731
+ 'show_map' => $this->storage_format( 'show_map' ),
732
+ 'contact_name' => $this->storage_format( 'contact_name' ),
733
+ 'contact_phone' => $this->storage_format( 'contact_phone' ),
734
+ 'contact_email' => $this->storage_format( 'contact_email' ),
735
+ 'contact_url' => $this->storage_format( 'contact_url' ),
736
+ 'cost' => $this->storage_format( 'cost' ),
737
+ 'ticket_url' => $this->storage_format( 'ticket_url' ),
738
+ 'ical_feed_url' => $this->storage_format( 'ical_feed_url' ),
739
+ 'ical_source_url' => $this->storage_format( 'ical_source_url' ),
740
+ 'ical_uid' => $this->storage_format( 'ical_uid' ),
741
+ 'show_coordinates' => $this->storage_format( 'show_coordinates' ),
742
+ 'latitude' => $this->storage_format( 'latitude', '' ),
743
+ 'longitude' => $this->storage_format( 'longitude', '' ),
744
+ );
745
+ return $entity;
746
+ }
747
+
748
+ /**
749
+ * Compact field for writing to persistent storage.
750
+ *
751
+ * @param string $field Name of field to compact.
752
+ * @param mixed $default Default value to use for undescribed fields.
753
+ *
754
+ * @return mixed Value or $default.
755
+ */
756
+ public function storage_format( $field, $default = null ) {
757
+ $value = $this->_entity->get( $field, $default );
758
+ if (
759
+ isset( $this->_swizzable[$field] ) &&
760
+ $this->_swizzable[$field] <= 0
761
+ ) {
762
+ $value = $this->{ '_handle_property_destruct_' . $field }( $value );
763
+ }
764
+ return $value;
765
+ }
766
+
767
+ /**
768
+ * Allow properties to be modified after cloning.
769
+ *
770
+ * @return void
771
+ */
772
+ public function __clone() {
773
+ $this->_entity = clone $this->_entity;
774
+ }
775
+
776
+ /**
777
+ * Decode timezone to use for event.
778
+ *
779
+ * Following algorythm is used to detect a value:
780
+ * - take value provided in input;
781
+ * - if empty - take value associated with start time;
782
+ * - if empty - take current environment timezone.
783
+ *
784
+ * @param string $timezone_name Timezone provided in input.
785
+ *
786
+ * @return string Timezone name to use for event in future.
787
+ */
788
+ protected function _handle_property_destruct_timezone_name(
789
+ $timezone_name
790
+ ) {
791
+ if ( empty( $timezone_name ) ) {
792
+ $timezone_name = $this->get( 'start' )->get_timezone();
793
+ if ( empty( $timezone_name ) ) {
794
+ $timezone_name = $this->_registry->get( 'date.timezone' )
795
+ ->get_default_timezone();
796
+ }
797
+ }
798
+ return $timezone_name;
799
+ }
800
+
801
+ /**
802
+ * Format datetime to UNIX timestamp for storage.
803
+ *
804
+ * @param Ai1ec_Date_Time $start Datetime object to compact.
805
+ *
806
+ * @return int UNIX timestamp.
807
+ */
808
+ protected function _handle_property_destruct_start( Ai1ec_Date_Time $start ) {
809
+ return $start->format_to_gmt();
810
+ }
811
+
812
+ /**
813
+ * Format datetime to UNIX timestamp for storage.
814
+ *
815
+ * @param Ai1ec_Date_Time $end Datetime object to compact.
816
+ *
817
+ * @return int UNIX timestamp.
818
+ */
819
+ protected function _handle_property_destruct_end( Ai1ec_Date_Time $end ) {
820
+ return $end->format_to_gmt();
821
+ }
822
+
823
+ /**
824
+ * Handle `cost` writing to permanent storage.
825
+ *
826
+ * @param string $cost Value of cost.
827
+ *
828
+ * @return string Serialized value to store.
829
+ */
830
+ protected function _handle_property_destruct_cost( $cost ) {
831
+ $cost = array(
832
+ 'cost' => $cost,
833
+ 'is_free' => false,
834
+ );
835
+ if ( $this->get( 'is_free' ) ) {
836
+ $cost['is_free'] = true;
837
+ }
838
+ return serialize( $cost );
839
+ }
840
+
841
+ /**
842
+ * Get the submitter information array
843
+ * @return array (
844
+ * is_organizer => 1 if the organizer is the submitter,
845
+ * email => if is_organizer is 0, them this property has the email of the submitter,
846
+ * name => if is_organizer is 0, them this property has the name of the submitter
847
+ * )
848
+ */
849
+ public function get_submitter_info() {
850
+ $post_id = $this->get( 'post_id' );
851
+ if ( empty( $post_id ) ) {
852
+ return null;
853
+ }
854
+ $submitter_info = get_post_meta(
855
+ $post_id,
856
+ '_submitter_info',
857
+ true
858
+ );
859
+ if ( false == ai1ec_is_blank( $submitter_info ) ) {
860
+ $submitter_info = json_decode( $submitter_info, true );
861
+ if ( is_array( $submitter_info ) ) {
862
+ return $submitter_info;
863
+ }
864
+ }
865
+ return null;
866
+ }
867
+
868
+ /**
869
+ * Save the submitter information into post metadata
870
+ */
871
+ public function save_submitter_info( $is_submitter, $submitter_email, $submitter_name ) {
872
+ $post_id = $this->get( 'post_id' );
873
+ if ( empty( $post_id ) ) {
874
+ throw new Exception( 'Post id empty' );
875
+ }
876
+ $save = false;
877
+ if ( 1 === intval( $is_submitter ) ) {
878
+ $submitter_info['is_organizer'] = 1;
879
+ if ( false === ai1ec_is_blank( $this->get( 'contact_email' ) ) ) {
880
+ $save = true;
881
+ }
882
+ } else {
883
+ $submitter_info['is_organizer'] = 0;
884
+ if ( false === ai1ec_is_blank( $submitter_email ) ) {
885
+ $submitter_info['email'] = trim( $submitter_email );
886
+ $submitter_info['name'] = trim( $submitter_name );
887
+ $save = true;
888
+ }
889
+ }
890
+ if ( $save ) {
891
+ update_post_meta( $post_id, '_submitter_info', json_encode( $submitter_info ) );
892
+ }
893
+ }
894
 
895
  }
app/model/event/creating.php CHANGED
@@ -11,496 +11,499 @@
11
  */
12
  class Ai1ec_Event_Creating extends Ai1ec_Base {
13
 
14
- protected function is_valid_event( $post ) {
15
- // verify this came from the our screen and with proper authorization,
16
- // because save_post can be triggered at other times
17
- if (
18
- ! isset( $_POST[AI1EC_POST_TYPE] ) ||
19
- ! wp_verify_nonce( $_POST[AI1EC_POST_TYPE], 'ai1ec' )
20
- ) {
21
- return false;
22
- }
23
-
24
- if (
25
- isset( $post->post_status ) &&
26
- 'auto-draft' === $post->post_status
27
- ) {
28
- return false;
29
- }
30
-
31
- // verify if this is not inline-editing
32
- if (
33
- isset( $_REQUEST['action'] ) &&
34
- 'inline-save' === $_REQUEST['action']
35
- ) {
36
- return false;
37
- }
38
-
39
- // verify that the post_type is that of an event
40
- if ( $post->post_type !== AI1EC_POST_TYPE ) {
41
- return false;
42
- }
43
-
44
- return true;
45
- }
46
-
47
- private function _parse_post_to_event( $post_id ) {
48
-
49
- /**
50
- * =====================================================================
51
- *
52
- * CHANGE CODE BELLOW TO HAVE FOLLOWING PROPERTIES:
53
- * - be initializiable from model;
54
- * - have sane defaults;
55
- * - avoid that cluster of isset and ternary operator.
56
- *
57
- * =====================================================================
58
- */
59
-
60
- $all_day = isset( $_POST['ai1ec_all_day_event'] ) ? 1 : 0;
61
- $instant_event = isset( $_POST['ai1ec_instant_event'] ) ? 1 : 0;
62
- $timezone_name = isset( $_POST['ai1ec_timezone_name'] ) ? $_POST['ai1ec_timezone_name'] : 'sys.default';
63
- $start_time = isset( $_POST['ai1ec_start_time'] ) ? $_POST['ai1ec_start_time'] : '';
64
- $end_time = isset( $_POST['ai1ec_end_time'] ) ? $_POST['ai1ec_end_time'] : '';
65
- $venue = isset( $_POST['ai1ec_venue'] ) ? $_POST['ai1ec_venue'] : '';
66
- $address = isset( $_POST['ai1ec_address'] ) ? $_POST['ai1ec_address'] : '';
67
- $city = isset( $_POST['ai1ec_city'] ) ? $_POST['ai1ec_city'] : '';
68
- $province = isset( $_POST['ai1ec_province'] ) ? $_POST['ai1ec_province'] : '';
69
- $postal_code = isset( $_POST['ai1ec_postal_code'] ) ? $_POST['ai1ec_postal_code'] : '';
70
- $country = isset( $_POST['ai1ec_country'] ) ? $_POST['ai1ec_country'] : '';
71
- $google_map = isset( $_POST['ai1ec_google_map'] ) ? 1 : 0;
72
- $cost = isset( $_POST['ai1ec_cost'] ) ? $_POST['ai1ec_cost'] : '';
73
- $is_free = isset( $_POST['ai1ec_is_free'] ) ? (bool)$_POST['ai1ec_is_free'] : false;
74
- $ticket_url = isset( $_POST['ai1ec_ticket_url'] ) ? $_POST['ai1ec_ticket_url'] : '';
75
- $contact_name = isset( $_POST['ai1ec_contact_name'] ) ? $_POST['ai1ec_contact_name'] : '';
76
- $contact_phone = isset( $_POST['ai1ec_contact_phone'] ) ? $_POST['ai1ec_contact_phone'] : '';
77
- $contact_email = isset( $_POST['ai1ec_contact_email'] ) ? $_POST['ai1ec_contact_email'] : '';
78
- $contact_url = isset( $_POST['ai1ec_contact_url'] ) ? $_POST['ai1ec_contact_url'] : '';
79
- $show_coordinates = isset( $_POST['ai1ec_input_coordinates'] )? 1 : 0;
80
- $longitude = isset( $_POST['ai1ec_longitude'] ) ? $_POST['ai1ec_longitude'] : '';
81
- $latitude = isset( $_POST['ai1ec_latitude'] ) ? $_POST['ai1ec_latitude'] : '';
82
- $cost_type = isset( $_POST['ai1ec_cost_type'] ) ? $_POST['ai1ec_cost_type'] : '';
83
- $rrule = null;
84
- $exrule = null;
85
- $exdate = null;
86
- $rdate = null;
87
-
88
- if ( 'external' !== $cost_type ) {
89
- $ticket_url = '';
90
- }
91
-
92
- $this->_remap_recurrence_dates();
93
- // if rrule is set, convert it from local to UTC time
94
- if (
95
- isset( $_POST['ai1ec_repeat'] ) &&
96
- ! empty( $_POST['ai1ec_repeat'] )
97
- ) {
98
- $rrule = $_POST['ai1ec_rrule'];
99
- }
100
-
101
- // add manual dates
102
- if (
103
- isset( $_POST['ai1ec_exdate'] ) &&
104
- ! empty( $_POST['ai1ec_exdate'] )
105
- ) {
106
- $exdate = $_POST['ai1ec_exdate'];
107
- }
108
- if (
109
- isset( $_POST['ai1ec_rdate'] ) &&
110
- ! empty( $_POST['ai1ec_rdate'] )
111
- ) {
112
- $rdate = $_POST['ai1ec_rdate'];
113
- }
114
-
115
- // if exrule is set, convert it from local to UTC time
116
- if (
117
- isset( $_POST['ai1ec_exclude'] ) &&
118
- ! empty( $_POST['ai1ec_exclude'] ) &&
119
- ( null !== $rrule || null !== $rdate ) // no point for exclusion, if repetition is not set
120
- ) {
121
- $exrule = $this->_registry->get( 'recurrence.rule' )->merge_exrule(
122
- $_POST['ai1ec_exrule'],
123
- $rrule
124
- );
125
- }
126
-
127
- $is_new = false;
128
- try {
129
- $event = $this->_registry->get(
130
- 'model.event',
131
- $post_id ? $post_id : null
132
- );
133
- } catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
134
- // Post exists, but event data hasn't been saved yet. Create new event
135
- // object.
136
- $is_new = true;
137
- $event = $this->_registry->get( 'model.event' );
138
- }
139
- $formatted_timezone = $this->_registry->get( 'date.timezone' )
140
- ->get_name( $timezone_name );
141
- if ( empty( $timezone_name ) || ! $formatted_timezone ) {
142
- $timezone_name = 'sys.default';
143
- }
144
-
145
- unset( $formatted_timezone );
146
- $start_time_entry = $this->_registry
147
- ->get( 'date.time', $start_time, $timezone_name );
148
- $end_time_entry = $this->_registry
149
- ->get( 'date.time', $end_time, $timezone_name );
150
-
151
- $timezone_name = $start_time_entry->get_timezone();
152
- if ( null === $timezone_name ) {
153
- $timezone_name = $start_time_entry->get_default_format_timezone();
154
- }
155
-
156
- $event->set( 'post_id', $post_id );
157
- $event->set( 'start', $start_time_entry );
158
- if ( $instant_event ) {
159
- $event->set_no_end_time();
160
- } else {
161
- $event->set( 'end', $end_time_entry );
162
- $event->set( 'instant_event', false );
163
- }
164
- $event->set( 'timezone_name', $timezone_name );
165
- $event->set( 'allday', $all_day );
166
- $event->set( 'venue', $venue );
167
- $event->set( 'address', $address );
168
- $event->set( 'city', $city );
169
- $event->set( 'province', $province );
170
- $event->set( 'postal_code', $postal_code );
171
- $event->set( 'country', $country );
172
- $event->set( 'show_map', $google_map );
173
- $event->set( 'cost', $cost );
174
- $event->set( 'is_free', $is_free );
175
- $event->set( 'ticket_url', $ticket_url );
176
- $event->set( 'contact_name', $contact_name );
177
- $event->set( 'contact_phone', $contact_phone );
178
- $event->set( 'contact_email', $contact_email );
179
- $event->set( 'contact_url', $contact_url );
180
- $event->set( 'recurrence_rules', $rrule );
181
- $event->set( 'exception_rules', $exrule );
182
- $event->set( 'exception_dates', $exdate );
183
- $event->set( 'recurrence_dates', $rdate );
184
- $event->set( 'show_coordinates', $show_coordinates );
185
- $event->set( 'longitude', trim( $longitude ) );
186
- $event->set( 'latitude', trim( $latitude ) );
187
- $event->set( 'ical_uid', $event->get_uid() );
188
-
189
- return array(
190
- 'event' => $event,
191
- 'is_new' => $is_new
192
- );
193
- }
194
-
195
- /**
196
- * Saves meta post data.
197
- *
198
- * @wp_hook save_post
199
- *
200
- * @param int $post_id Post ID.
201
- * @param object $post Post object.
202
- * @param update
203
- *
204
- * @return object|null Saved Ai1ec_Event object if successful or null.
205
- */
206
- public function save_post( $post_id, $post, $update ) {
207
-
208
- if ( false === $this->is_valid_event( $post ) ) {
209
- return null;
210
- }
211
-
212
- // LABEL:magicquotes
213
- // remove WordPress `magical` slashes - we work around it ourselves
214
- $_POST = stripslashes_deep( $_POST );
215
-
216
- $data = $this->_parse_post_to_event( $post_id );
217
- if ( ! $data ) {
218
- return null;
219
- }
220
- $event = $data['event'];
221
- $is_new = $data[ 'is_new'];
222
-
223
- $banner_image = isset( $_POST['ai1ec_banner_image'] ) ? $_POST['ai1ec_banner_image'] : '';
224
- $cost_type = isset( $_POST['ai1ec_cost_type'] ) ? $_POST['ai1ec_cost_type'] : '';
225
-
226
- update_post_meta( $post_id, 'ai1ec_banner_image', $banner_image );
227
- if ( $cost_type ) {
228
- update_post_meta( $post_id, '_ai1ec_cost_type', $cost_type );
229
- }
230
- $api = $this->_registry->get( 'model.api.api-ticketing' );
231
- if ( $update === false ) {
232
- //this method just creates the API event, the update action
233
- //is treated by another hook (pre_update_event inside api )
234
- if ( 'tickets' === $cost_type ) {
235
- $result = $api->store_event( $event, $post, false );
236
- if ( true !== $result ) {
237
- $_POST['_ticket_store_event_error'] = $result;
238
- } else {
239
- update_post_meta(
240
- $post_id,
241
- '_ai1ec_timely_tickets_url',
242
- $api->get_api_event_buy_ticket_url( $event->get( 'post_id' ) )
243
- );
244
- }
245
- }
246
- }
247
- if ( 'tickets' === $cost_type ) {
248
- update_post_meta(
249
- $post_id,
250
- '_ai1ec_timely_tickets_url',
251
- $api->get_api_event_buy_ticket_url( $event->get( 'post_id' ) )
252
- );
253
- } else {
254
- delete_post_meta(
255
- $post_id,
256
- '_ai1ec_timely_tickets_url'
257
- );
258
- }
259
-
260
- // let other extensions save their fields.
261
- do_action( 'ai1ec_save_post', $event );
262
-
263
- $event->save( ! $is_new );
264
-
265
- // LABEL:magicquotes
266
- // restore `magic` WordPress quotes to maintain compatibility
267
- $_POST = add_magic_quotes( $_POST );
268
-
269
- return $event;
270
- }
271
-
272
- private function get_sendback_page( $post_id ) {
273
- $sendback = wp_get_referer();
274
- $page_base = Ai1ec_Wp_Uri_Helper::get_pagebase( $sendback ); //$_SERVER['REQUEST_URI'] );
275
- if ( 'post.php' === $page_base ) {
276
- return get_edit_post_link( $post_id, 'url' );
277
- } else {
278
- return admin_url( 'edit.php?post_type=ai1ec_event' );
279
- }
280
- }
281
-
282
- /**
283
- * Handle PRE (ticket event) update.
284
- * Just handle the Ticket Events, other kind of post are ignored
285
- * @wp_hook pre_post_update
286
- *
287
- */
288
- public function pre_post_update ( $post_id, $new_post_data ) {
289
-
290
- // LABEL:magicquotes
291
- // remove WordPress `magical` slashes - we work around it ourselves
292
- $_POST = stripslashes_deep( $_POST );
293
-
294
- $api = $this->_registry->get( 'model.api.api-ticketing' );
295
- $action = $this->current_action();
296
- switch( $action ) {
297
- case 'inline-save': //quick edit from edit page
298
- $fields = array();
299
- if ( false === ai1ec_is_blank( $_REQUEST['post_title'] ) ) {
300
- $fields['title'] = $_REQUEST['post_title'];
301
- }
302
- if ( false === ai1ec_is_blank( $_REQUEST['_status'] ) ) {
303
- $fields['status'] = $_REQUEST['_status'];
304
- }
305
- if ( isset( $_REQUEST['keep_private'] ) && 'private' === $_REQUEST['keep_private'] ) {
306
- $fields['visibility'] = 'private';
307
- } else if ( isset( $_REQUEST['post_password'] ) && false === ai1ec_is_blank( $_REQUEST['post_password'] ) ) {
308
- $fields['visibility'] = 'password';
309
- }
310
- if ( 0 < count( $fields ) ) {
311
- $post = get_post( $post_id );
312
- $ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
313
- $message = $api->update_api_event_fields( $post, $fields, 'update', $ajax );
314
- if ( null !== $message ) {
315
- if ( $ajax ) {
316
- wp_die( $message );
317
- } else {
318
- wp_redirect( $this->get_sendback_page( $post_id ) );
319
- exit();
320
- }
321
- }
322
- }
323
- return;
324
- case 'edit': //bulk edition from edit page
325
- $fields = array();
326
- if ( false === ai1ec_is_blank( $_REQUEST['_status'] ) ) {
327
- $fields['status'] = $_REQUEST['_status'];
328
- }
329
- if ( 0 < count( $fields ) ) {
330
- $post = get_post( $post_id );
331
- $ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
332
- $message = $api->update_api_event_fields( $post, $fields, 'update', $ajax );
333
- if ( null !== $message ) {
334
- if ( $ajax ) {
335
- wp_die( $message );
336
- } else {
337
- wp_redirect( $this->get_sendback_page( $post_id ) );
338
- exit();
339
- }
340
- }
341
- }
342
- return;
343
- case 'editpost': //edition from post page
344
- $new_post_data['ID'] = $post_id;
345
- $post = new WP_Post( (object) $new_post_data );
346
- if ( false === $this->is_valid_event( $post ) ) {
347
- break;
348
- }
349
- $data = $this->_parse_post_to_event( $post_id );
350
- if ( ! $data ) {
351
- break;
352
- }
353
- $event = $data['event'];
354
- $cost_type = isset( $_REQUEST['ai1ec_cost_type'] ) ? $_REQUEST['ai1ec_cost_type'] : '';
355
- if ( 'tickets' === $cost_type ) {
356
- $result = $api->store_event( $event, $post, true );
357
- if ( true !== $result ) {
358
- wp_redirect( $this->get_sendback_page( $post_id ) );
359
- exit();
360
- }
361
- } else {
362
- $message = $api->delete_api_event( $post_id, 'update', false );
363
- if ( null !== $message ) {
364
- wp_redirect( $this->get_sendback_page( $post_id ) );
365
- exit();
366
- }
367
- }
368
- break;
369
- default:
370
- break;
371
- }
372
-
373
- // LABEL:magicquotes
374
- // restore `magic` WordPress quotes to maintain compatibility
375
- $_POST = add_magic_quotes( $_POST );
 
 
 
376
  }
377
 
378
  protected function current_action() {
379
- $action = '';
380
- if ( isset( $_REQUEST['delete_all'] ) || isset( $_REQUEST['delete_all2'] ) ) {
381
- $action = 'delete';
382
- } else {
383
- if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] ) {
384
- $action = $_REQUEST['action'];
385
- }
386
- if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] ) {
387
- $action = $_REQUEST['action2'];
388
- }
389
- }
390
- return $action;
391
  }
392
 
393
- /**
394
- * _create_duplicate_post method
395
- *
396
- * Create copy of event by calling {@uses wp_insert_post} function.
397
- * Using 'post_parent' to add hierarchy.
398
- *
399
- * @param array $data Event instance data to copy
400
- *
401
- * @return int|bool New post ID or false on failure
402
- **/
403
- public function create_duplicate_post() {
404
- if ( ! isset( $_POST['post_ID'] ) ) {
405
- return false;
406
- }
407
- $clean_fields = array(
408
- 'ai1ec_repeat' => NULL,
409
- 'ai1ec_rrule' => '',
410
- 'ai1ec_exrule' => '',
411
- 'ai1ec_exdate' => '',
412
- 'post_ID' => NULL,
413
- 'post_name' => NULL,
414
- 'ai1ec_instance_id' => NULL,
415
- );
416
- $old_post_id = $_POST['post_ID'];
417
- $instance_id = $_POST['ai1ec_instance_id'];
418
- foreach ( $clean_fields as $field => $to_value ) {
419
- if ( NULL === $to_value ) {
420
- unset( $_POST[$field] );
421
- } else {
422
- $_POST[$field] = $to_value;
423
- }
424
- }
425
- $_POST = _wp_translate_postdata( false, $_POST );
426
- $_POST['post_parent'] = $old_post_id;
427
- $post_id = wp_insert_post( $_POST );
428
- $this->_registry->get( 'model.event.parent' )->event_parent(
429
- $post_id,
430
- $old_post_id,
431
- $instance_id
432
- );
433
- return $post_id;
434
- }
435
-
436
- /**
437
- * Cleans calendar shortcodes from event content.
438
- *
439
- * @param array $data An array of slashed post data.
440
- * @param array $postarr An array of sanitized, but otherwise unmodified post data.
441
- *
442
- * @return array An array of slashed post data.
443
- */
444
- public function wp_insert_post_data( $data ) {
445
- global $shortcode_tags;
446
- if (
447
- ! isset( $data['post_type'] ) ||
448
- ! isset( $data['post_content'] ) ||
449
- AI1EC_POST_TYPE !== $data['post_type'] ||
450
- empty( $shortcode_tags ) ||
451
- ! is_array( $shortcode_tags ) ||
452
- false === strpos( $data['post_content'], '[' )
453
- ) {
454
- return $data;
455
- }
456
- $pattern = get_shortcode_regex();
457
- $data['post_content'] = preg_replace_callback(
458
- "/$pattern/s",
459
- array( $this, 'strip_shortcode_tag' ),
460
- $data['post_content']
461
- );
462
- return $data;
463
- }
464
-
465
- /**
466
- * Reutrns shortcode or stripped content for given shortcode.
467
- * Currently regex callback function passes as $tag argument 7-element long
468
- * array.
469
- * First element ($tag[0]) is not modified full shortcode text.
470
- * Third element ($tag[2]) is pure shortcode identifier.
471
- * Sixth element ($tag[5]) contains shortcode content if any
472
- * [ai1ec_test]content[/ai1ec].
473
- *
474
- * @param array $tag Incoming data.
475
- *
476
- * @return string Shortcode replace tag.
477
- */
478
- public function strip_shortcode_tag( $tag ) {
479
- if (
480
- count( $tag ) < 7 ||
481
- 'ai1ec' !== substr( $tag[2], 0, 5 ) ||
482
- ! apply_filters( 'ai1ec_content_remove_shortcode_' . $tag[2], false )
483
- ) {
484
- return $tag[0];
485
- }
486
- return $tag[5];
487
- }
488
-
489
- protected function _remap_recurrence_dates() {
490
- if (
491
- isset( $_POST['ai1ec_exclude'] ) &&
492
- 'EXDATE' === substr( $_POST['ai1ec_exrule'], 0, 6 )
493
- ) {
494
- $_POST['ai1ec_exdate'] = substr( $_POST['ai1ec_exrule'], 7 );
495
- unset( $_POST['ai1ec_exclude'], $_POST['ai1ec_exrule'] );
496
- }
497
- if (
498
- isset( $_POST['ai1ec_repeat'] ) &&
499
- 'RDATE' === substr( $_POST['ai1ec_rrule'], 0, 5 )
500
- ) {
501
- $_POST['ai1ec_rdate'] = substr( $_POST['ai1ec_rrule'], 6 );
502
- unset( $_POST['ai1ec_repeat'], $_POST['ai1ec_rrule'] );
503
- }
504
- }
505
 
506
  }
11
  */
12
  class Ai1ec_Event_Creating extends Ai1ec_Base {
13
 
14
+ protected function is_valid_event( $post ) {
15
+ // verify this came from the our screen and with proper authorization,
16
+ // because save_post can be triggered at other times
17
+ if (
18
+ ! isset( $_POST[AI1EC_POST_TYPE] ) ||
19
+ ! wp_verify_nonce( $_POST[AI1EC_POST_TYPE], 'ai1ec' )
20
+ ) {
21
+ return false;
22
+ }
23
+
24
+ if (
25
+ isset( $post->post_status ) &&
26
+ 'auto-draft' === $post->post_status
27
+ ) {
28
+ return false;
29
+ }
30
+
31
+ // verify if this is not inline-editing
32
+ if (
33
+ isset( $_REQUEST['action'] ) &&
34
+ 'inline-save' === $_REQUEST['action']
35
+ ) {
36
+ return false;
37
+ }
38
+
39
+ // verify that the post_type is that of an event
40
+ if ( $post->post_type !== AI1EC_POST_TYPE ) {
41
+ return false;
42
+ }
43
+
44
+ return true;
45
+ }
46
+
47
+ private function _parse_post_to_event( $post_id ) {
48
+
49
+ /**
50
+ * =====================================================================
51
+ *
52
+ * CHANGE CODE BELLOW TO HAVE FOLLOWING PROPERTIES:
53
+ * - be initializiable from model;
54
+ * - have sane defaults;
55
+ * - avoid that cluster of isset and ternary operator.
56
+ *
57
+ * =====================================================================
58
+ */
59
+
60
+ $all_day = isset( $_POST['ai1ec_all_day_event'] ) ? 1 : 0;
61
+ $instant_event = isset( $_POST['ai1ec_instant_event'] ) ? 1 : 0;
62
+ $timezone_name = isset( $_POST['ai1ec_timezone_name'] ) ? $_POST['ai1ec_timezone_name'] : 'sys.default';
63
+ $start_time = isset( $_POST['ai1ec_start_time'] ) ? $_POST['ai1ec_start_time'] : '';
64
+ $end_time = isset( $_POST['ai1ec_end_time'] ) ? $_POST['ai1ec_end_time'] : '';
65
+ $venue = isset( $_POST['ai1ec_venue'] ) ? $_POST['ai1ec_venue'] : '';
66
+ $address = isset( $_POST['ai1ec_address'] ) ? $_POST['ai1ec_address'] : '';
67
+ $city = isset( $_POST['ai1ec_city'] ) ? $_POST['ai1ec_city'] : '';
68
+ $province = isset( $_POST['ai1ec_province'] ) ? $_POST['ai1ec_province'] : '';
69
+ $postal_code = isset( $_POST['ai1ec_postal_code'] ) ? $_POST['ai1ec_postal_code'] : '';
70
+ $country = isset( $_POST['ai1ec_country'] ) ? $_POST['ai1ec_country'] : '';
71
+ $google_map = isset( $_POST['ai1ec_google_map'] ) ? 1 : 0;
72
+ $cost = isset( $_POST['ai1ec_cost'] ) ? $_POST['ai1ec_cost'] : '';
73
+ $is_free = isset( $_POST['ai1ec_is_free'] ) ? (bool)$_POST['ai1ec_is_free'] : false;
74
+ $ticket_url = isset( $_POST['ai1ec_ticket_url'] ) ? $_POST['ai1ec_ticket_url'] : '';
75
+ $contact_name = isset( $_POST['ai1ec_contact_name'] ) ? $_POST['ai1ec_contact_name'] : '';
76
+ $contact_phone = isset( $_POST['ai1ec_contact_phone'] ) ? $_POST['ai1ec_contact_phone'] : '';
77
+ $contact_email = isset( $_POST['ai1ec_contact_email'] ) ? $_POST['ai1ec_contact_email'] : '';
78
+ $contact_url = isset( $_POST['ai1ec_contact_url'] ) ? $_POST['ai1ec_contact_url'] : '';
79
+ $show_coordinates = isset( $_POST['ai1ec_input_coordinates'] )? 1 : 0;
80
+ $longitude = isset( $_POST['ai1ec_longitude'] ) ? $_POST['ai1ec_longitude'] : '';
81
+ $latitude = isset( $_POST['ai1ec_latitude'] ) ? $_POST['ai1ec_latitude'] : '';
82
+ $cost_type = isset( $_POST['ai1ec_cost_type'] ) ? $_POST['ai1ec_cost_type'] : '';
83
+ $rrule = null;
84
+ $exrule = null;
85
+ $exdate = null;
86
+ $rdate = null;
87
+
88
+ if ( 'external' !== $cost_type ) {
89
+ $ticket_url = '';
90
+ }
91
+
92
+ $this->_remap_recurrence_dates();
93
+ // if rrule is set, convert it from local to UTC time
94
+ if (
95
+ isset( $_POST['ai1ec_repeat'] ) &&
96
+ ! empty( $_POST['ai1ec_repeat'] )
97
+ ) {
98
+ $rrule = $_POST['ai1ec_rrule'];
99
+ }
100
+
101
+ // add manual dates
102
+ if (
103
+ isset( $_POST['ai1ec_exdate'] ) &&
104
+ ! empty( $_POST['ai1ec_exdate'] )
105
+ ) {
106
+ $exdate = $_POST['ai1ec_exdate'];
107
+ }
108
+ if (
109
+ isset( $_POST['ai1ec_rdate'] ) &&
110
+ ! empty( $_POST['ai1ec_rdate'] )
111
+ ) {
112
+ $rdate = $_POST['ai1ec_rdate'];
113
+ }
114
+
115
+ // if exrule is set, convert it from local to UTC time
116
+ if (
117
+ isset( $_POST['ai1ec_exclude'] ) &&
118
+ ! empty( $_POST['ai1ec_exclude'] ) &&
119
+ ( null !== $rrule || null !== $rdate ) // no point for exclusion, if repetition is not set
120
+ ) {
121
+ $exrule = $this->_registry->get( 'recurrence.rule' )->merge_exrule(
122
+ $_POST['ai1ec_exrule'],
123
+ $rrule
124
+ );
125
+ }
126
+
127
+ $is_new = false;
128
+ try {
129
+ $event = $this->_registry->get(
130
+ 'model.event',
131
+ $post_id ? $post_id : null
132
+ );
133
+ } catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
134
+ // Post exists, but event data hasn't been saved yet. Create new event
135
+ // object.
136
+ $is_new = true;
137
+ $event = $this->_registry->get( 'model.event' );
138
+ }
139
+ $formatted_timezone = $this->_registry->get( 'date.timezone' )
140
+ ->get_name( $timezone_name );
141
+ if ( empty( $timezone_name ) || ! $formatted_timezone ) {
142
+ $timezone_name = 'sys.default';
143
+ }
144
+
145
+ unset( $formatted_timezone );
146
+ $start_time_entry = $this->_registry
147
+ ->get( 'date.time', $start_time, $timezone_name );
148
+ $end_time_entry = $this->_registry
149
+ ->get( 'date.time', $end_time, $timezone_name );
150
+
151
+ $timezone_name = $start_time_entry->get_timezone();
152
+ if ( null === $timezone_name ) {
153
+ $timezone_name = $start_time_entry->get_default_format_timezone();
154
+ }
155
+
156
+ $event->set( 'post_id', $post_id );
157
+ $event->set( 'start', $start_time_entry );
158
+ if ( $instant_event ) {
159
+ $event->set_no_end_time();
160
+ } else {
161
+ $event->set( 'end', $end_time_entry );
162
+ $event->set( 'instant_event', false );
163
+ }
164
+ $event->set( 'timezone_name', $timezone_name );
165
+ $event->set( 'allday', $all_day );
166
+ $event->set( 'venue', $venue );
167
+ $event->set( 'address', $address );
168
+ $event->set( 'city', $city );
169
+ $event->set( 'province', $province );
170
+ $event->set( 'postal_code', $postal_code );
171
+ $event->set( 'country', $country );
172
+ $event->set( 'show_map', $google_map );
173
+ $event->set( 'cost', $cost );
174
+ $event->set( 'is_free', $is_free );
175
+ $event->set( 'ticket_url', $ticket_url );
176
+ $event->set( 'contact_name', $contact_name );
177
+ $event->set( 'contact_phone', $contact_phone );
178
+ $event->set( 'contact_email', $contact_email );
179
+ $event->set( 'contact_url', $contact_url );
180
+ $event->set( 'recurrence_rules', $rrule );
181
+ $event->set( 'exception_rules', $exrule );
182
+ $event->set( 'exception_dates', $exdate );
183
+ $event->set( 'recurrence_dates', $rdate );
184
+ $event->set( 'show_coordinates', $show_coordinates );
185
+ $event->set( 'longitude', trim( $longitude ) );
186
+ $event->set( 'latitude', trim( $latitude ) );
187
+ $event->set( 'ical_uid', $event->get_uid() );
188
+
189
+ return array(
190
+ 'event' => $event,
191
+ 'is_new' => $is_new
192
+ );
193
+ }
194
+
195
+ /**
196
+ * Saves meta post data.
197
+ *
198
+ * @wp_hook save_post
199
+ *
200
+ * @param int $post_id Post ID.
201
+ * @param object $post Post object.
202
+ * @param update
203
+ *
204
+ * @return object|null Saved Ai1ec_Event object if successful or null.
205
+ */
206
+ public function save_post( $post_id, $post, $update ) {
207
+
208
+ if ( false === $this->is_valid_event( $post ) ) {
209
+ return null;
210
+ }
211
+
212
+ // LABEL:magicquotes
213
+ // remove WordPress `magical` slashes - we work around it ourselves
214
+ $_POST = stripslashes_deep( $_POST );
215
+
216
+ $data = $this->_parse_post_to_event( $post_id );
217
+ if ( ! $data ) {
218
+ return null;
219
+ }
220
+ $event = $data['event'];
221
+ $is_new = $data[ 'is_new'];
222
+
223
+ $banner_image = isset( $_POST['ai1ec_banner_image'] ) ? $_POST['ai1ec_banner_image'] : '';
224
+ $cost_type = isset( $_POST['ai1ec_cost_type'] ) ? $_POST['ai1ec_cost_type'] : '';
225
+
226
+ update_post_meta( $post_id, 'ai1ec_banner_image', $banner_image );
227
+ if ( $cost_type ) {
228
+ update_post_meta( $post_id, '_ai1ec_cost_type', $cost_type );
229
+ }
230
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
231
+ if ( $update === false ) {
232
+ //this method just creates the API event, the update action
233
+ //is treated by another hook (pre_update_event inside api )
234
+ if ( 'tickets' === $cost_type ) {
235
+ $result = $api->store_event( $event, $post, false );
236
+ if ( true !== $result ) {
237
+ $_POST['_ticket_store_event_error'] = $result;
238
+ } else {
239
+ update_post_meta(
240
+ $post_id,
241
+ '_ai1ec_timely_tickets_url',
242
+ $api->get_api_event_buy_ticket_url( $event->get( 'post_id' ) )
243
+ );
244
+ }
245
+ }
246
+ }
247
+ if ( 'tickets' === $cost_type ) {
248
+ update_post_meta(
249
+ $post_id,
250
+ '_ai1ec_timely_tickets_url',
251
+ $api->get_api_event_buy_ticket_url( $event->get( 'post_id' ) )
252
+ );
253
+ } else {
254
+ delete_post_meta(
255
+ $post_id,
256
+ '_ai1ec_timely_tickets_url'
257
+ );
258
+ }
259
+
260
+ // let other extensions save their fields.
261
+ do_action( 'ai1ec_save_post', $event );
262
+
263
+ $event->save( ! $is_new );
264
+
265
+ // LABEL:magicquotes
266
+ // restore `magic` WordPress quotes to maintain compatibility
267
+ $_POST = add_magic_quotes( $_POST );
268
+
269
+ $api = $this->_registry->get( 'model.api.api-registration' );
270
+ $api->check_settings();
271
+
272
+ return $event;
273
+ }
274
+
275
+ private function get_sendback_page( $post_id ) {
276
+ $sendback = wp_get_referer();
277
+ $page_base = Ai1ec_Wp_Uri_Helper::get_pagebase( $sendback ); //$_SERVER['REQUEST_URI'] );
278
+ if ( 'post.php' === $page_base ) {
279
+ return get_edit_post_link( $post_id, 'url' );
280
+ } else {
281
+ return admin_url( 'edit.php?post_type=ai1ec_event' );
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Handle PRE (ticket event) update.
287
+ * Just handle the Ticket Events, other kind of post are ignored
288
+ * @wp_hook pre_post_update
289
+ *
290
+ */
291
+ public function pre_post_update ( $post_id, $new_post_data ) {
292
+
293
+ // LABEL:magicquotes
294
+ // remove WordPress `magical` slashes - we work around it ourselves
295
+ $_POST = stripslashes_deep( $_POST );
296
+
297
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
298
+ $action = $this->current_action();
299
+ switch( $action ) {
300
+ case 'inline-save': //quick edit from edit page
301
+ $fields = array();
302
+ if ( false === ai1ec_is_blank( $_REQUEST['post_title'] ) ) {
303
+ $fields['title'] = $_REQUEST['post_title'];
304
+ }
305
+ if ( false === ai1ec_is_blank( $_REQUEST['_status'] ) ) {
306
+ $fields['status'] = $_REQUEST['_status'];
307
+ }
308
+ if ( isset( $_REQUEST['keep_private'] ) && 'private' === $_REQUEST['keep_private'] ) {
309
+ $fields['visibility'] = 'private';
310
+ } else if ( isset( $_REQUEST['post_password'] ) && false === ai1ec_is_blank( $_REQUEST['post_password'] ) ) {
311
+ $fields['visibility'] = 'password';
312
+ }
313
+ if ( 0 < count( $fields ) ) {
314
+ $post = get_post( $post_id );
315
+ $ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
316
+ $message = $api->update_api_event_fields( $post, $fields, 'update', $ajax );
317
+ if ( null !== $message ) {
318
+ if ( $ajax ) {
319
+ wp_die( $message );
320
+ } else {
321
+ wp_redirect( $this->get_sendback_page( $post_id ) );
322
+ exit();
323
+ }
324
+ }
325
+ }
326
+ return;
327
+ case 'edit': //bulk edition from edit page
328
+ $fields = array();
329
+ if ( false === ai1ec_is_blank( $_REQUEST['_status'] ) ) {
330
+ $fields['status'] = $_REQUEST['_status'];
331
+ }
332
+ if ( 0 < count( $fields ) ) {
333
+ $post = get_post( $post_id );
334
+ $ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
335
+ $message = $api->update_api_event_fields( $post, $fields, 'update', $ajax );
336
+ if ( null !== $message ) {
337
+ if ( $ajax ) {
338
+ wp_die( $message );
339
+ } else {
340
+ wp_redirect( $this->get_sendback_page( $post_id ) );
341
+ exit();
342
+ }
343
+ }
344
+ }
345
+ return;
346
+ case 'editpost': //edition from post page
347
+ $new_post_data['ID'] = $post_id;
348
+ $post = new WP_Post( (object) $new_post_data );
349
+ if ( false === $this->is_valid_event( $post ) ) {
350
+ break;
351
+ }
352
+ $data = $this->_parse_post_to_event( $post_id );
353
+ if ( ! $data ) {
354
+ break;
355
+ }
356
+ $event = $data['event'];
357
+ $cost_type = isset( $_REQUEST['ai1ec_cost_type'] ) ? $_REQUEST['ai1ec_cost_type'] : '';
358
+ if ( 'tickets' === $cost_type ) {
359
+ $result = $api->store_event( $event, $post, true );
360
+ if ( true !== $result ) {
361
+ wp_redirect( $this->get_sendback_page( $post_id ) );
362
+ exit();
363
+ }
364
+ } else {
365
+ $message = $api->delete_api_event( $post_id, 'update', false );
366
+ if ( null !== $message ) {
367
+ wp_redirect( $this->get_sendback_page( $post_id ) );
368
+ exit();
369
+ }
370
+ }
371
+ break;
372
+ default:
373
+ break;
374
+ }
375
+
376
+ // LABEL:magicquotes
377
+ // restore `magic` WordPress quotes to maintain compatibility
378
+ $_POST = add_magic_quotes( $_POST );
379
  }
380
 
381
  protected function current_action() {
382
+ $action = '';
383
+ if ( isset( $_REQUEST['delete_all'] ) || isset( $_REQUEST['delete_all2'] ) ) {
384
+ $action = 'delete';
385
+ } else {
386
+ if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] ) {
387
+ $action = $_REQUEST['action'];
388
+ }
389
+ if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] ) {
390
+ $action = $_REQUEST['action2'];
391
+ }
392
+ }
393
+ return $action;
394
  }
395
 
396
+ /**
397
+ * _create_duplicate_post method
398
+ *
399
+ * Create copy of event by calling {@uses wp_insert_post} function.
400
+ * Using 'post_parent' to add hierarchy.
401
+ *
402
+ * @param array $data Event instance data to copy
403
+ *
404
+ * @return int|bool New post ID or false on failure
405
+ **/
406
+ public function create_duplicate_post() {
407
+ if ( ! isset( $_POST['post_ID'] ) ) {
408
+ return false;
409
+ }
410
+ $clean_fields = array(
411
+ 'ai1ec_repeat' => NULL,
412
+ 'ai1ec_rrule' => '',
413
+ 'ai1ec_exrule' => '',
414
+ 'ai1ec_exdate' => '',
415
+ 'post_ID' => NULL,
416
+ 'post_name' => NULL,
417
+ 'ai1ec_instance_id' => NULL,
418
+ );
419
+ $old_post_id = $_POST['post_ID'];
420
+ $instance_id = $_POST['ai1ec_instance_id'];
421
+ foreach ( $clean_fields as $field => $to_value ) {
422
+ if ( NULL === $to_value ) {
423
+ unset( $_POST[$field] );
424
+ } else {
425
+ $_POST[$field] = $to_value;
426
+ }
427
+ }
428
+ $_POST = _wp_translate_postdata( false, $_POST );
429
+ $_POST['post_parent'] = $old_post_id;
430
+ $post_id = wp_insert_post( $_POST );
431
+ $this->_registry->get( 'model.event.parent' )->event_parent(
432
+ $post_id,
433
+ $old_post_id,
434
+ $instance_id
435
+ );
436
+ return $post_id;
437
+ }
438
+
439
+ /**
440
+ * Cleans calendar shortcodes from event content.
441
+ *
442
+ * @param array $data An array of slashed post data.
443
+ * @param array $postarr An array of sanitized, but otherwise unmodified post data.
444
+ *
445
+ * @return array An array of slashed post data.
446
+ */
447
+ public function wp_insert_post_data( $data ) {
448
+ global $shortcode_tags;
449
+ if (
450
+ ! isset( $data['post_type'] ) ||
451
+ ! isset( $data['post_content'] ) ||
452
+ AI1EC_POST_TYPE !== $data['post_type'] ||
453
+ empty( $shortcode_tags ) ||
454
+ ! is_array( $shortcode_tags ) ||
455
+ false === strpos( $data['post_content'], '[' )
456
+ ) {
457
+ return $data;
458
+ }
459
+ $pattern = get_shortcode_regex();
460
+ $data['post_content'] = preg_replace_callback(
461
+ "/$pattern/s",
462
+ array( $this, 'strip_shortcode_tag' ),
463
+ $data['post_content']
464
+ );
465
+ return $data;
466
+ }
467
+
468
+ /**
469
+ * Reutrns shortcode or stripped content for given shortcode.
470
+ * Currently regex callback function passes as $tag argument 7-element long
471
+ * array.
472
+ * First element ($tag[0]) is not modified full shortcode text.
473
+ * Third element ($tag[2]) is pure shortcode identifier.
474
+ * Sixth element ($tag[5]) contains shortcode content if any
475
+ * [ai1ec_test]content[/ai1ec].
476
+ *
477
+ * @param array $tag Incoming data.
478
+ *
479
+ * @return string Shortcode replace tag.
480
+ */
481
+ public function strip_shortcode_tag( $tag ) {
482
+ if (
483
+ count( $tag ) < 7 ||
484
+ 'ai1ec' !== substr( $tag[2], 0, 5 ) ||
485
+ ! apply_filters( 'ai1ec_content_remove_shortcode_' . $tag[2], false )
486
+ ) {
487
+ return $tag[0];
488
+ }
489
+ return $tag[5];
490
+ }
491
+
492
+ protected function _remap_recurrence_dates() {
493
+ if (
494
+ isset( $_POST['ai1ec_exclude'] ) &&
495
+ 'EXDATE' === substr( $_POST['ai1ec_exrule'], 0, 6 )
496
+ ) {
497
+ $_POST['ai1ec_exdate'] = substr( $_POST['ai1ec_exrule'], 7 );
498
+ unset( $_POST['ai1ec_exclude'], $_POST['ai1ec_exrule'] );
499
+ }
500
+ if (
501
+ isset( $_POST['ai1ec_repeat'] ) &&
502
+ 'RDATE' === substr( $_POST['ai1ec_rrule'], 0, 5 )
503
+ ) {
504
+ $_POST['ai1ec_rdate'] = substr( $_POST['ai1ec_rrule'], 6 );
505
+ unset( $_POST['ai1ec_repeat'], $_POST['ai1ec_rrule'] );
506
+ }
507
+ }
508
 
509
  }
app/model/event/entity.php CHANGED
@@ -11,361 +11,361 @@
11
  */
12
  class Ai1ec_Event_Entity extends Ai1ec_Base {
13
 
14
- /**
15
- * Get list of object properties.
16
- *
17
- * Special value `registry` ({@see Ai1ec_Registry_Object}) is excluded.
18
- *
19
- * @return array List of accessible properties.
20
- *
21
- * @staticvar array $known List of properties.
22
- */
23
- public function list_properties() {
24
- static $known = null;
25
- if ( null === $known ) {
26
- $known = array();
27
- foreach ( $this as $name => $value ) {
28
- $name = substr( $name, 1 );
29
- if ( 'registry' === $name ) {
30
- continue;
31
- }
32
- $known[] = $name;
33
- }
34
- }
35
- return $known;
36
- }
37
-
38
- /**
39
- * Handle cloning properly to resist property changes.
40
- *
41
- * @return void
42
- */
43
- public function __clone() {
44
- $this->_start = $this->_registry->get( 'date.time', $this->_start );
45
- $this->_end = $this->_registry->get( 'date.time', $this->_end );
46
- $this->_post = clone $this->_post;
47
- }
48
-
49
- /**
50
- * Change stored property.
51
- *
52
- * @param string $name Name of property to change.
53
- * @param mixed $value Arbitrary value to use.
54
- *
55
- * @return Ai1ec_Event_Entity Instance of self for chaining.
56
- *
57
- * @staticvar array $time_fields Map of fields holding a value of
58
- * {@see Ai1ec_Date_Time}, which
59
- * require modification instead of
60
- * replacement.
61
- */
62
- public function set( $name, $value ) {
63
- static $time_fields = array(
64
- 'start' => true,
65
- 'end' => true,
66
- );
67
- if ( 'registry' === $name ) {
68
- return $this; // short-circuit: protection mean.
69
- }
70
- if ( 'timezone_name' === $name && empty( $value ) ) {
71
- return $this; // protection against invalid TZ values.
72
- }
73
- $field = '_' . $name;
74
- if ( isset( $time_fields[$name] ) ) {
75
- // object of Ai1ec_Date_Time type is now handled in it itself
76
- $this->{$field}->set_date_time(
77
- $value,
78
- ( null === $this->_timezone_name )
79
- ? 'UTC'
80
- : $this->_timezone_name
81
- );
82
- $this->adjust_preferred_timezone();
83
- } else {
84
- $this->{$field} = $value;
85
- }
86
- if ( 'timezone_name' === $name ) {
87
- $this->_start->set_timezone( $value );
88
- $this->_end ->set_timezone( $value );
89
- $this->adjust_preferred_timezone();
90
- }
91
- return $this;
92
- }
93
-
94
- /**
95
- * Optionally adjust preferred (display) timezone.
96
- *
97
- * @return bool|DateTimeZone False or new timezone.
98
- *
99
- * @staticvar bool $do_adjust True when adjustment should be performed.
100
- */
101
- public function adjust_preferred_timezone() {
102
- static $do_adjust = null;
103
- if ( null === $do_adjust ) {
104
- $do_adjust = !$this->_registry
105
- ->get( 'model.settings' )
106
- ->get( 'always_use_calendar_timezone', false );
107
- }
108
- if ( ! $do_adjust ) {
109
- return false;
110
- }
111
- $timezone = $this->_registry->get( 'date.timezone' )->get(
112
- $this->_timezone_name
113
- );
114
- $this->set_preferred_timezone( $timezone );
115
- return $timezone;
116
- }
117
-
118
- /**
119
- * Set preferred timezone to datetime fields.
120
- *
121
- * @param DateTimeZone $timezone Preferred timezone instance.
122
- *
123
- * @return void
124
- */
125
- public function set_preferred_timezone( DateTimeZone $timezone ) {
126
- $this->_start->set_preferred_timezone( $timezone );
127
- $this->_end ->set_preferred_timezone( $timezone );
128
- }
129
-
130
- /**
131
- * Get a value of some property.
132
- *
133
- * @param string $name Name of property to get.
134
- * @param mixed $default Value to return if property is not defined.
135
- *
136
- * @return mixed Found value or $default.
137
- */
138
- public function get( $name, $default = null ) {
139
- if ( ! isset( $this->{ '_' . $name } ) ) {
140
- return $default;
141
- }
142
- return $this->{ '_' . $name };
143
- }
144
-
145
- /**
146
- * Initialize values to some sane defaults.
147
- *
148
- * @param Ai1ec_Registry_Object $registry Injected registry.
149
- *
150
- * @return void
151
- */
152
- public function __construct( Ai1ec_Registry_Object $registry ) {
153
- parent::__construct( $registry );
154
- $this->_start = $this->_registry->get( 'date.time' );
155
- $this->_end = $this->_registry->get( 'date.time', '+1 hour' );
156
- }
157
-
158
- /**
159
- * @var object Instance of WP_Post object.
160
- */
161
- private $_post;
162
-
163
- /**
164
- * @var int Post ID.
165
- */
166
- private $_post_id;
167
-
168
- /**
169
- * @var int|null Uniquely identifies the recurrence instance of this event
170
- * object. Value may be null.
171
- */
172
- private $_instance_id;
173
-
174
- /**
175
- * @var string Name of timezone to use for event times.
176
- */
177
- private $_timezone_name;
178
-
179
- /**
180
- * @var Ai1ec_Date_Time Start date-time specifier
181
- */
182
- private $_start;
183
-
184
- /**
185
- * @var Ai1ec_Date_Time End date-time specifier
186
- */
187
- private $_end;
188
-
189
- /**
190
- * @var bool Whether this copy of the event was broken up for rendering and
191
- * the start time is not its "real" start time.
192
- */
193
- private $_start_truncated;
194
-
195
- /**
196
- * @var bool Whether this copy of the event was broken up for rendering and
197
- * the end time is not its "real" end time.
198
- */
199
- private $_end_truncated;
200
-
201
- /**
202
- * @var int If event is all-day long
203
- */
204
- private $_allday;
205
-
206
- /**
207
- * @var int If event has no duration
208
- */
209
- private $_instant_event;
210
-
211
- /**
212
- * ==========================
213
- * = Recurrence information =
214
- * ==========================
215
- */
216
-
217
- /**
218
- * @var string Recurrence rules
219
- */
220
- private $_recurrence_rules;
221
-
222
- /**
223
- * @var string Exception rules
224
- */
225
- private $_exception_rules;
226
-
227
- /**
228
- * @var string Recurrence dates
229
- */
230
- private $_recurrence_dates;
231
-
232
- /**
233
- * @var string Exception dates
234
- */
235
- private $_exception_dates;
236
-
237
- /**
238
- * @var string Venue name - free text
239
- */
240
- private $_venue;
241
-
242
- /**
243
- * @var string Country name - free text
244
- */
245
- private $_country;
246
-
247
- /**
248
- * @var string Address information - free text
249
- */
250
- private $_address;
251
-
252
- /**
253
- * @var string City name - free text
254
- */
255
- private $_city;
256
-
257
- /**
258
- * @var string Province free text definition
259
- */
260
- private $_province;
261
-
262
- /**
263
- * @var int Postal code
264
- */
265
- private $_postal_code;
266
-
267
- /**
268
- * @var int Set to true to display map
269
- */
270
- private $_show_map;
271
-
272
- /**
273
- * @var int Set to true to show coordinates in description
274
- */
275
- private $_show_coordinates;
276
-
277
- /**
278
- * @var float GEO information - longitude
279
- */
280
- private $_longitude;
281
-
282
- /**
283
- * @var float GEO information - latitude
284
- */
285
- private $_latitude;
286
-
287
- /**
288
- * @var string Event contact information - contact person
289
- */
290
- private $_contact_name;
291
-
292
- /**
293
- * @var string Event contact information - phone number
294
- */
295
- private $_contact_phone;
296
-
297
- /**
298
- * @var string Event contact information - email address
299
- */
300
- private $_contact_email;
301
-
302
- /**
303
- * @var string Event contact information - external URL.
304
- */
305
- private $_contact_url;
306
-
307
- /**
308
- * @var string Defines event cost.
309
- */
310
- private $_cost;
311
-
312
- /**
313
- * @var bool Indicates, whereas event is free.
314
- */
315
- private $_is_free;
316
-
317
- /**
318
- * @var string Link to buy tickets
319
- */
320
- private $_ticket_url;
321
-
322
- // ====================================
323
- // = iCalendar feed (.ics) properties =
324
- // ====================================
325
-
326
- /**
327
- * @var string URI of source ICAL feed.
328
- */
329
- private $_ical_feed_url;
330
-
331
- /**
332
- * @var string|null URI of source ICAL entity.
333
- */
334
- private $_ical_source_url;
335
-
336
- /**
337
- * @var string Organiser details
338
- */
339
- private $_ical_organizer;
340
-
341
- /**
342
- * @var string Contact details
343
- */
344
- private $_ical_contact;
345
-
346
- /**
347
- * @var string|int UID of ICAL feed
348
- */
349
- private $_ical_uid;
350
-
351
- // ===============================
352
- // = taxonomy-related properties =
353
- // ===============================
354
-
355
- /**
356
- * @var string Associated event tag names (*not* IDs), joined by commas.
357
- */
358
- private $_tags;
359
-
360
- /**
361
- * @var string Associated event category IDs, joined by commas.
362
- */
363
- private $_categories;
364
-
365
- /**
366
- * @var string Associated event feed object
367
- */
368
- private $_feed;
369
 
370
 
371
  }
11
  */
12
  class Ai1ec_Event_Entity extends Ai1ec_Base {
13
 
14
+ /**
15
+ * Get list of object properties.
16
+ *
17
+ * Special value `registry` ({@see Ai1ec_Registry_Object}) is excluded.
18
+ *
19
+ * @return array List of accessible properties.
20
+ *
21
+ * @staticvar array $known List of properties.
22
+ */
23
+ public function list_properties() {
24
+ static $known = null;
25
+ if ( null === $known ) {
26
+ $known = array();
27
+ foreach ( $this as $name => $value ) {
28
+ $name = substr( $name, 1 );
29
+ if ( 'registry' === $name ) {
30
+ continue;
31
+ }
32
+ $known[] = $name;
33
+ }
34
+ }
35
+ return $known;
36
+ }
37
+
38
+ /**
39
+ * Handle cloning properly to resist property changes.
40
+ *
41
+ * @return void
42
+ */
43
+ public function __clone() {
44
+ $this->_start = $this->_registry->get( 'date.time', $this->_start );
45
+ $this->_end = $this->_registry->get( 'date.time', $this->_end );
46
+ $this->_post = clone $this->_post;
47
+ }
48
+
49
+ /**
50
+ * Change stored property.
51
+ *
52
+ * @param string $name Name of property to change.
53
+ * @param mixed $value Arbitrary value to use.
54
+ *
55
+ * @return Ai1ec_Event_Entity Instance of self for chaining.
56
+ *
57
+ * @staticvar array $time_fields Map of fields holding a value of
58
+ * {@see Ai1ec_Date_Time}, which
59
+ * require modification instead of
60
+ * replacement.
61
+ */
62
+ public function set( $name, $value ) {
63
+ static $time_fields = array(
64
+ 'start' => true,
65
+ 'end' => true,
66
+ );
67
+ if ( 'registry' === $name ) {
68
+ return $this; // short-circuit: protection mean.
69
+ }
70
+ if ( 'timezone_name' === $name && empty( $value ) ) {
71
+ return $this; // protection against invalid TZ values.
72
+ }
73
+ $field = '_' . $name;
74
+ if ( isset( $time_fields[$name] ) ) {
75
+ // object of Ai1ec_Date_Time type is now handled in it itself
76
+ $this->{$field}->set_date_time(
77
+ $value,
78
+ ( null === $this->_timezone_name )
79
+ ? 'UTC'
80
+ : $this->_timezone_name
81
+ );
82
+ $this->adjust_preferred_timezone();
83
+ } else {
84
+ $this->{$field} = $value;
85
+ }
86
+ if ( 'timezone_name' === $name ) {
87
+ $this->_start->set_timezone( $value );
88
+ $this->_end ->set_timezone( $value );
89
+ $this->adjust_preferred_timezone();
90
+ }
91
+ return $this;
92
+ }
93
+
94
+ /**
95
+ * Optionally adjust preferred (display) timezone.
96
+ *
97
+ * @return bool|DateTimeZone False or new timezone.
98
+ *
99
+ * @staticvar bool $do_adjust True when adjustment should be performed.
100
+ */
101
+ public function adjust_preferred_timezone() {
102
+ static $do_adjust = null;
103
+ if ( null === $do_adjust ) {
104
+ $do_adjust = !$this->_registry
105
+ ->get( 'model.settings' )
106
+ ->get( 'always_use_calendar_timezone', false );
107
+ }
108
+ if ( ! $do_adjust ) {
109
+ return false;
110
+ }
111
+ $timezone = $this->_registry->get( 'date.timezone' )->get(
112
+ $this->_timezone_name
113
+ );
114
+ $this->set_preferred_timezone( $timezone );
115
+ return $timezone;
116
+ }
117
+
118
+ /**
119
+ * Set preferred timezone to datetime fields.
120
+ *
121
+ * @param DateTimeZone $timezone Preferred timezone instance.
122
+ *
123
+ * @return void
124
+ */
125
+ public function set_preferred_timezone( DateTimeZone $timezone ) {
126
+ $this->_start->set_preferred_timezone( $timezone );
127
+ $this->_end ->set_preferred_timezone( $timezone );
128
+ }
129
+
130
+ /**
131
+ * Get a value of some property.
132
+ *
133
+ * @param string $name Name of property to get.
134
+ * @param mixed $default Value to return if property is not defined.
135
+ *
136
+ * @return mixed Found value or $default.
137
+ */
138
+ public function get( $name, $default = null ) {
139
+ if ( ! isset( $this->{ '_' . $name } ) ) {
140
+ return $default;
141
+ }
142
+ return $this->{ '_' . $name };
143
+ }
144
+
145
+ /**
146
+ * Initialize values to some sane defaults.
147
+ *
148
+ * @param Ai1ec_Registry_Object $registry Injected registry.
149
+ *
150
+ * @return void
151
+ */
152
+ public function __construct( Ai1ec_Registry_Object $registry ) {
153
+ parent::__construct( $registry );
154
+ $this->_start = $this->_registry->get( 'date.time' );
155
+ $this->_end = $this->_registry->get( 'date.time', '+1 hour' );
156
+ }
157
+
158
+ /**
159
+ * @var object Instance of WP_Post object.
160
+ */
161
+ private $_post;
162
+
163
+ /**
164
+ * @var int Post ID.
165
+ */
166
+ private $_post_id;
167
+
168
+ /**
169
+ * @var int|null Uniquely identifies the recurrence instance of this event
170
+ * object. Value may be null.
171
+ */
172
+ private $_instance_id;
173
+
174
+ /**
175
+ * @var string Name of timezone to use for event times.
176
+ */
177
+ private $_timezone_name;
178
+
179
+ /**
180
+ * @var Ai1ec_Date_Time Start date-time specifier
181
+ */
182
+ private $_start;
183
+
184
+ /**
185
+ * @var Ai1ec_Date_Time End date-time specifier
186
+ */
187
+ private $_end;
188
+
189
+ /**
190
+ * @var bool Whether this copy of the event was broken up for rendering and
191
+ * the start time is not its "real" start time.
192
+ */
193
+ private $_start_truncated;
194
+
195
+ /**
196
+ * @var bool Whether this copy of the event was broken up for rendering and
197
+ * the end time is not its "real" end time.
198
+ */
199
+ private $_end_truncated;
200
+
201
+ /**
202
+ * @var int If event is all-day long
203
+ */
204
+ private $_allday;
205
+
206
+ /**
207
+ * @var int If event has no duration
208
+ */
209
+ private $_instant_event;
210
+
211
+ /**
212
+ * ==========================
213
+ * = Recurrence information =
214
+ * ==========================
215
+ */
216
+
217
+ /**
218
+ * @var string Recurrence rules
219
+ */
220
+ private $_recurrence_rules;
221
+
222
+ /**
223
+ * @var string Exception rules
224
+ */
225
+ private $_exception_rules;
226
+
227
+ /**
228
+ * @var string Recurrence dates
229
+ */
230
+ private $_recurrence_dates;
231
+
232
+ /**
233
+ * @var string Exception dates
234
+ */
235
+ private $_exception_dates;
236
+
237
+ /**
238
+ * @var string Venue name - free text
239
+ */
240
+ private $_venue;
241
+
242
+ /**
243
+ * @var string Country name - free text
244
+ */
245
+ private $_country;
246
+
247
+ /**
248
+ * @var string Address information - free text
249
+ */
250
+ private $_address;
251
+
252
+ /**
253
+ * @var string City name - free text
254
+ */
255
+ private $_city;
256
+
257
+ /**
258
+ * @var string Province free text definition
259
+ */
260
+ private $_province;
261
+
262
+ /**
263
+ * @var int Postal code
264
+ */
265
+ private $_postal_code;
266
+
267
+ /**
268
+ * @var int Set to true to display map
269
+ */
270
+ private $_show_map;
271
+
272
+ /**
273
+ * @var int Set to true to show coordinates in description
274
+ */
275
+ private $_show_coordinates;
276
+
277
+ /**
278
+ * @var float GEO information - longitude
279
+ */
280
+ private $_longitude;
281
+
282
+ /**
283
+ * @var float GEO information - latitude
284
+ */
285
+ private $_latitude;
286
+
287
+ /**
288
+ * @var string Event contact information - contact person
289
+ */
290
+ private $_contact_name;
291
+
292
+ /**
293
+ * @var string Event contact information - phone number
294
+ */
295
+ private $_contact_phone;
296
+
297
+ /**
298
+ * @var string Event contact information - email address
299
+ */
300
+ private $_contact_email;
301
+
302
+ /**
303
+ * @var string Event contact information - external URL.
304
+ */
305
+ private $_contact_url;
306
+
307
+ /**
308
+ * @var string Defines event cost.
309
+ */
310
+ private $_cost;
311
+
312
+ /**
313
+ * @var bool Indicates, whereas event is free.
314
+ */
315
+ private $_is_free;
316
+
317
+ /**
318
+ * @var string Link to buy tickets
319
+ */
320
+ private $_ticket_url;
321
+
322
+ // ====================================
323
+ // = iCalendar feed (.ics) properties =
324
+ // ====================================
325
+
326
+ /**
327
+ * @var string URI of source ICAL feed.
328
+ */
329
+ private $_ical_feed_url;
330
+
331
+ /**
332
+ * @var string|null URI of source ICAL entity.
333
+ */
334
+ private $_ical_source_url;
335
+
336
+ /**
337
+ * @var string Organiser details
338
+ */
339
+ private $_ical_organizer;
340
+
341
+ /**
342
+ * @var string Contact details
343
+ */
344
+ private $_ical_contact;
345
+
346
+ /**
347
+ * @var string|int UID of ICAL feed
348
+ */
349
+ private $_ical_uid;
350
+
351
+ // ===============================
352
+ // = taxonomy-related properties =
353
+ // ===============================
354
+
355
+ /**
356
+ * @var string Associated event tag names (*not* IDs), joined by commas.
357
+ */
358
+ private $_tags;
359
+
360
+ /**
361
+ * @var string Associated event category IDs, joined by commas.
362
+ */
363
+ private $_categories;
364
+
365
+ /**
366
+ * @var string Associated event feed object
367
+ */
368
+ private $_feed;
369
 
370
 
371
  }
app/model/event/instance.php CHANGED
@@ -11,397 +11,397 @@
11
  */
12
  class Ai1ec_Event_Instance extends Ai1ec_Base {
13
 
14
- /**
15
- * @var Ai1ec_Dbi Instance of database abstraction.
16
- */
17
- protected $_dbi = null;
18
 
19
- /**
20
- * DBI utils.
21
- *
22
- * @var Ai1ec_Dbi_Utils
23
- */
24
- protected $_dbi_utils;
25
 
26
- /**
27
- * Store locally instance of Ai1ec_Dbi.
28
- *
29
- * @param Ai1ec_Registry_Object $registry Injected object registry.
30
- *
31
- */
32
- public function __construct( Ai1ec_Registry_Object $registry ) {
33
- parent::__construct( $registry );
34
- $this->_dbi = $this->_registry->get( 'dbi.dbi' );
35
- $this->_dbi_utils = $this->_registry->get( 'dbi.dbi-utils' );
36
- }
37
 
38
- /**
39
- * Remove entries for given post. Optionally delete particular instance.
40
- *
41
- * @param int $post_id Event ID to remove instances for.
42
- * @param int|null $instance_id Instance ID, or null for all.
43
- *
44
- * @return int|bool Number of entries removed, or false on failure.
45
- */
46
- public function clean( $post_id, $instance_id = null ) {
47
- $where = array( 'post_id' => $post_id );
48
- $format = array( '%d' );
49
- if ( null !== $instance_id ) {
50
- $where['id'] = $instance_id;
51
- $format[] = '%d';
52
- }
53
- return $this->_dbi->delete( 'ai1ec_event_instances', $where, $format );
54
- }
55
 
56
- /**
57
- * Remove and then create instance entries for given event.
58
- *
59
- * @param Ai1ec_Event $event Instance of event to recreate entries for.
60
- *
61
- * @return bool Success.
62
- */
63
- public function recreate( Ai1ec_Event $event ) {
64
- $old_instances = $this->_load_instances( $event->get( 'post_id' ) );
65
- $instances = $this->_create_instances_collection( $event );
66
- $insert = array();
67
- foreach ( $instances as $instance ) {
68
- if ( ! isset( $old_instances[$instance['start'] . ':' . $instance['end']] ) ) {
69
- $insert[] = $instance;
70
- continue;
71
- }
72
- unset( $old_instances[$instance['start'] . ':' . $instance['end']] );
73
- }
74
- $this->_remove_instances_by_ids( array_values( $old_instances ) );
75
- $this->_add_instances( $insert );
76
- return true;
77
- }
78
 
79
- /**
80
- * Create list of recurrent instances.
81
- *
82
- * @param Ai1ec_Event $event Event to generate instances for.
83
- * @param array $event_instance First instance contents.
84
- * @param int $_start Timestamp of first occurence.
85
- * @param int $duration Event duration in seconds.
86
- * @param string $timezone Target timezone.
87
- *
88
- * @return array List of event instances.
89
- */
90
- public function create_instances_by_recurrence(
91
- Ai1ec_Event $event,
92
- array $event_instance,
93
- $_start,
94
- $duration,
95
- $timezone
96
- ) {
97
- $restore_timezone = date_default_timezone_get();
98
- $recurrence_parser = $this->_registry->get( 'recurrence.rule' );
99
- $events = array();
100
 
101
- $start = $event_instance['start'];
102
- $wdate = $startdate = $enddate
103
- = $this->_parsed_date_array( $_start, $timezone );
104
- $enddate['year'] = $enddate['year'] + 10;
105
- $exclude_dates = array();
106
- $recurrence_dates = array();
107
- if ( $recurrence_dates = $event->get( 'recurrence_dates' ) ) {
108
- $recurrence_dates = $this->_populate_recurring_dates(
109
- $recurrence_dates,
110
- $startdate,
111
- $timezone
112
- );
113
- }
114
- if ( $exception_dates = $event->get( 'exception_dates' ) ) {
115
- $exclude_dates = $this->_populate_recurring_dates(
116
- $exception_dates,
117
- $startdate,
118
- $timezone
119
- );
120
- }
121
- if ( $event->get( 'exception_rules' ) ) {
122
- // creat an array for the rules
123
- $exception_rules = $recurrence_parser
124
- ->build_recurrence_rules_array(
125
- $event->get( 'exception_rules' )
126
- );
127
- unset($exception_rules['EXDATE']);
128
- if ( ! empty( $exception_rules ) ) {
129
- $exception_rules = iCalUtilityFunctions::_setRexrule(
130
- $exception_rules
131
- );
132
- $result = array();
133
- date_default_timezone_set( $timezone );
134
- // The first array is the result and it is passed by reference
135
- iCalUtilityFunctions::_recur2date(
136
- $exclude_dates,
137
- $exception_rules,
138
- $wdate,
139
- $startdate,
140
- $enddate
141
- );
142
- date_default_timezone_set( $restore_timezone );
143
- }
144
- }
145
- $recurrence_rules = $recurrence_parser
146
- ->build_recurrence_rules_array(
147
- $event->get( 'recurrence_rules' )
148
- );
149
 
150
- $recurrence_rules = iCalUtilityFunctions::_setRexrule( $recurrence_rules );
151
- if ( $recurrence_rules ) {
152
- date_default_timezone_set( $timezone );
153
- iCalUtilityFunctions::_recur2date(
154
- $recurrence_dates,
155
- $recurrence_rules,
156
- $wdate,
157
- $startdate,
158
- $enddate
159
- );
160
- date_default_timezone_set( $restore_timezone );
161
- }
162
 
163
- if ( ! is_array( $recurrence_dates ) ) {
164
- $recurrence_dates = array();
165
- }
166
- $recurrence_dates = array_keys( $recurrence_dates );
167
- // Add the instances
168
- foreach ( $recurrence_dates as $timestamp ) {
169
- // The arrays are in the form timestamp => true so an isset call is what we need
170
- if ( ! isset( $exclude_dates[$timestamp] ) ) {
171
- $event_instance['start'] = $timestamp;
172
- $event_instance['end'] = $timestamp + $duration;
173
- $events[$timestamp] = $event_instance;
174
- }
175
- }
176
 
177
- return $events;
178
- }
179
 
180
- /**
181
- * Generate and store instance entries in database for given event.
182
- *
183
- * @param Ai1ec_Event $event Instance of event to create entries for.
184
- *
185
- * @return bool Success.
186
- */
187
- public function create( Ai1ec_Event $event ) {
188
- $instances = $this->_create_instances_collection( $event );
189
- $this->_add_instances( $instances );
190
- return true;
191
- }
192
 
193
- /**
194
- * Check if given date match dates in EXDATES rule.
195
- *
196
- * @param string $date Date to check.
197
- * @param string $ics_rule ICS EXDATES rule.
198
- * @param string $timezone Timezone to evaluate value in.
199
- *
200
- * @return bool True if given date is in rule.
201
- */
202
- public function date_match_exdates( $date, $ics_rule, $timezone ) {
203
- $ranges = $this->_get_date_ranges( $ics_rule, $timezone );
204
- foreach ( $ranges as $interval ) {
205
- if ( $date >= $interval[0] && $date <= $interval[1] ) {
206
- return true;
207
- }
208
- if ( $date <= $interval[0] ) {
209
- break;
210
- }
211
- }
212
- return false;
213
- }
214
 
215
- /**
216
- * Prepare date range list for fast exdate search.
217
- *
218
- * NOTICE: timezone is relevant in only first run.
219
- *
220
- * @param string $date_list ICS list provided from data model.
221
- * @param string $timezone Timezone in which to evaluate.
222
- *
223
- * @return array List of date ranges, sorted in increasing order.
224
- */
225
- protected function _get_date_ranges( $date_list, $timezone ) {
226
- static $ranges = array();
227
- if ( ! isset( $ranges[$date_list] ) ) {
228
- $ranges[$date_list] = array();
229
- $exploded = explode( ',', $date_list );
230
- sort( $exploded );
231
- foreach ( $exploded as $date ) {
232
- // COMMENT on `rtrim( $date, 'Z' )`:
233
- // user selects exclusion date in event timezone thus it
234
- // must be parsed as such as opposed to UTC which happen
235
- // when 'Z' is preserved.
236
- $date = $this->_registry
237
- ->get( 'date.time', rtrim( $date, 'Z' ), $timezone )
238
- ->format_to_gmt();
239
- $ranges[$date_list][] = array(
240
- $date,
241
- $date + (24 * 60 * 60) - 1
242
- );
243
- }
244
- }
245
- return $ranges[$date_list];
246
- }
247
 
248
- protected function _populate_recurring_dates( $rule, array $start_struct, $timezone ) {
249
- $start = clone $start_struct['_dt'];
250
- $dates = array();
251
- foreach ( explode( ',', $rule ) as $date ) {
252
- $i_date = clone $start;
253
- $spec = sscanf( $date, '%04d%02d%02d' );
254
- $i_date->set_date(
255
- $spec[0],
256
- $spec[1],
257
- $spec[2]
258
- );
259
- $dates[$i_date->format_to_gmt()] = $i_date;
260
- }
261
- return $dates;
262
- }
263
 
264
- protected function _parsed_date_array( $startdate, $timezone ) {
265
- $datetime = $this->_registry->get( 'date.time', $startdate, $timezone );
266
- $parsed = array(
267
- 'year' => intval( $datetime->format( 'Y' ) ),
268
- 'month' => intval( $datetime->format( 'm' ) ),
269
- 'day' => intval( $datetime->format( 'd' ) ),
270
- 'hour' => intval( $datetime->format( 'H' ) ),
271
- 'min' => intval( $datetime->format( 'i' ) ),
272
- 'sec' => intval( $datetime->format( 's' ) ),
273
- 'tz' => $datetime->get_timezone(),
274
- '_dt' => $datetime,
275
- );
276
- return $parsed;
277
- }
278
 
279
- /**
280
- * Returns current instances map.
281
- *
282
- * @param int post_id Post ID.
283
- *
284
- * @return array Array of data.
285
- */
286
- protected function _load_instances( $post_id ) {
287
- $query = $this->_dbi->prepare(
288
- 'SELECT `id`, `start`, `end` FROM ' .
289
- $this->_dbi->get_table_name( 'ai1ec_event_instances' ) .
290
- ' WHERE post_id = %d',
291
- $post_id
292
- );
293
- $results = $this->_dbi->get_results( $query );
294
- $instances = array();
295
- foreach ( $results as $result ) {
296
- $instances[(int)$result->start . ':' . (int)$result->end] = (int)$result->id;
297
- }
298
- return $instances;
299
- }
300
 
301
- /**
302
- * Generate and store instance entries in database for given event.
303
- *
304
- * @param Ai1ec_Event $event Instance of event to create entries for.
305
- *
306
- * @return bool Success.
307
- */
308
- protected function _create_instances_collection( Ai1ec_Event $event ) {
309
- $events = array();
310
- $event_item = array(
311
- 'post_id' => $event->get( 'post_id' ),
312
- 'start' => $event->get( 'start' )->format_to_gmt(),
313
- 'end' => $event->get( 'end' )->format_to_gmt(),
314
- );
315
- $duration = $event->get( 'end' )->diff_sec( $event->get( 'start' ) );
316
 
317
- $_start = $event->get( 'start' )->format_to_gmt();
318
- $_end = $event->get( 'end' )->format_to_gmt();
319
 
320
- // Always cache initial instance
321
- $events[$_start] = $event_item;
322
 
323
- if ( $event->get( 'recurrence_rules' ) || $event->get( 'recurrence_dates' ) ) {
324
- $start_timezone = $this->_registry->get( 'model.option' )
325
- ->get( 'timezone_string' );
326
- if ( empty( $start_timezone ) ) {
327
- $start_timezone = $this->_registry->get( 'date.timezone' )->get_default_timezone();
328
- }
329
 
330
- $events += $this->create_instances_by_recurrence(
331
- $event,
332
- $event_item,
333
- $_start,
334
- $duration,
335
- $start_timezone
336
- );
337
- }
338
 
339
- $search_helper = $this->_registry->get( 'model.search' );
340
- foreach ( $events as &$event_item ) {
341
- // Find out if this event instance is already accounted for by an
342
- // overriding 'RECURRENCE-ID' of the same iCalendar feed (by comparing the
343
- // UID, start date, recurrence). If so, then do not create duplicate
344
- // instance of event.
345
- $start = $event_item['start'];
346
- $matching_event_id = null;
347
- if ( $event->get( 'ical_uid' ) ) {
348
- $matching_event_id = $search_helper->get_matching_event_id(
349
- $event->get( 'ical_uid' ),
350
- $event->get( 'ical_feed_url' ),
351
- $event->get( 'start' ),
352
- false,
353
- $event->get( 'post_id' )
354
- );
355
- }
356
 
357
- // If no other instance was found
358
- if ( null !== $matching_event_id ) {
359
- $event_item = false;
360
- }
361
- }
362
 
363
- return array_filter( $events );
364
- }
365
 
366
- /**
367
- * Removes ai1ec_event_instances entries using their IDS.
368
- *
369
- * @param array $ids Collection of IDS.
370
- *
371
- * @return bool Result.
372
- */
373
- protected function _remove_instances_by_ids( array $ids ) {
374
- if ( empty( $ids ) ) {
375
- return false;
376
- }
377
- $query = 'DELETE FROM ' . $this->_dbi->get_table_name(
378
- 'ai1ec_event_instances'
379
- ) . ' WHERE id IN (';
380
- $ids = array_filter( array_map( 'intval', $ids ) );
381
- $query .= implode( ',', $ids ) . ')';
382
- $this->_dbi->query( $query );
383
- return true;
384
- }
385
 
386
- /**
387
- * Adds new instances collection.
388
- *
389
- * @param array $instances Collection of instances.
390
- *
391
- * @return void
392
- */
393
- protected function _add_instances( array $instances ) {
394
- $chunks = array_chunk( $instances, 50 );
395
- foreach ( $chunks as $chunk ) {
396
- $query = 'INSERT INTO ' . $this->_dbi->get_table_name(
397
- 'ai1ec_event_instances'
398
- ) . '(`post_id`, `start`, `end`) VALUES';
399
- $chunk = array_map(
400
- array( $this->_dbi_utils, 'array_value_to_sql_value' ),
401
- $chunk
402
- );
403
- $query .= implode( ',', $chunk );
404
- $this->_dbi->query( $query );
405
- }
406
- }
407
  }
11
  */
12
  class Ai1ec_Event_Instance extends Ai1ec_Base {
13
 
14
+ /**
15
+ * @var Ai1ec_Dbi Instance of database abstraction.
16
+ */
17
+ protected $_dbi = null;
18
 
19
+ /**
20
+ * DBI utils.
21
+ *
22
+ * @var Ai1ec_Dbi_Utils
23
+ */
24
+ protected $_dbi_utils;
25
 
26
+ /**
27
+ * Store locally instance of Ai1ec_Dbi.
28
+ *
29
+ * @param Ai1ec_Registry_Object $registry Injected object registry.
30
+ *
31
+ */
32
+ public function __construct( Ai1ec_Registry_Object $registry ) {
33
+ parent::__construct( $registry );
34
+ $this->_dbi = $this->_registry->get( 'dbi.dbi' );
35
+ $this->_dbi_utils = $this->_registry->get( 'dbi.dbi-utils' );
36
+ }
37
 
38
+ /**
39
+ * Remove entries for given post. Optionally delete particular instance.
40
+ *
41
+ * @param int $post_id Event ID to remove instances for.
42
+ * @param int|null $instance_id Instance ID, or null for all.
43
+ *
44
+ * @return int|bool Number of entries removed, or false on failure.
45
+ */
46
+ public function clean( $post_id, $instance_id = null ) {
47
+ $where = array( 'post_id' => $post_id );
48
+ $format = array( '%d' );
49
+ if ( null !== $instance_id ) {
50
+ $where['id'] = $instance_id;
51
+ $format[] = '%d';
52
+ }
53
+ return $this->_dbi->delete( 'ai1ec_event_instances', $where, $format );
54
+ }
55
 
56
+ /**
57
+ * Remove and then create instance entries for given event.
58
+ *
59
+ * @param Ai1ec_Event $event Instance of event to recreate entries for.
60
+ *
61
+ * @return bool Success.
62
+ */
63
+ public function recreate( Ai1ec_Event $event ) {
64
+ $old_instances = $this->_load_instances( $event->get( 'post_id' ) );
65
+ $instances = $this->_create_instances_collection( $event );
66
+ $insert = array();
67
+ foreach ( $instances as $instance ) {
68
+ if ( ! isset( $old_instances[$instance['start'] . ':' . $instance['end']] ) ) {
69
+ $insert[] = $instance;
70
+ continue;
71
+ }
72
+ unset( $old_instances[$instance['start'] . ':' . $instance['end']] );
73
+ }
74
+ $this->_remove_instances_by_ids( array_values( $old_instances ) );
75
+ $this->_add_instances( $insert );
76
+ return true;
77
+ }
78
 
79
+ /**
80
+ * Create list of recurrent instances.
81
+ *
82
+ * @param Ai1ec_Event $event Event to generate instances for.
83
+ * @param array $event_instance First instance contents.
84
+ * @param int $_start Timestamp of first occurence.
85
+ * @param int $duration Event duration in seconds.
86
+ * @param string $timezone Target timezone.
87
+ *
88
+ * @return array List of event instances.
89
+ */
90
+ public function create_instances_by_recurrence(
91
+ Ai1ec_Event $event,
92
+ array $event_instance,
93
+ $_start,
94
+ $duration,
95
+ $timezone
96
+ ) {
97
+ $restore_timezone = date_default_timezone_get();
98
+ $recurrence_parser = $this->_registry->get( 'recurrence.rule' );
99
+ $events = array();
100
 
101
+ $start = $event_instance['start'];
102
+ $wdate = $startdate = $enddate
103
+ = $this->_parsed_date_array( $_start, $timezone );
104
+ $enddate['year'] = $enddate['year'] + 10;
105
+ $exclude_dates = array();
106
+ $recurrence_dates = array();
107
+ if ( $recurrence_dates = $event->get( 'recurrence_dates' ) ) {
108
+ $recurrence_dates = $this->_populate_recurring_dates(
109
+ $recurrence_dates,
110
+ $startdate,
111
+ $timezone
112
+ );
113
+ }
114
+ if ( $exception_dates = $event->get( 'exception_dates' ) ) {
115
+ $exclude_dates = $this->_populate_recurring_dates(
116
+ $exception_dates,
117
+ $startdate,
118
+ $timezone
119
+ );
120
+ }
121
+ if ( $event->get( 'exception_rules' ) ) {
122
+ // creat an array for the rules
123
+ $exception_rules = $recurrence_parser
124
+ ->build_recurrence_rules_array(
125
+ $event->get( 'exception_rules' )
126
+ );
127
+ unset($exception_rules['EXDATE']);
128
+ if ( ! empty( $exception_rules ) ) {
129
+ $exception_rules = iCalUtilityFunctions::_setRexrule(
130
+ $exception_rules
131
+ );
132
+ $result = array();
133
+ date_default_timezone_set( $timezone );
134
+ // The first array is the result and it is passed by reference
135
+ iCalUtilityFunctions::_recur2date(
136
+ $exclude_dates,
137
+ $exception_rules,
138
+ $wdate,
139
+ $startdate,
140
+ $enddate
141
+ );
142
+ date_default_timezone_set( $restore_timezone );
143
+ }
144
+ }
145
+ $recurrence_rules = $recurrence_parser
146
+ ->build_recurrence_rules_array(
147
+ $event->get( 'recurrence_rules' )
148
+ );
149
 
150
+ $recurrence_rules = iCalUtilityFunctions::_setRexrule( $recurrence_rules );
151
+ if ( $recurrence_rules ) {
152
+ date_default_timezone_set( $timezone );
153
+ iCalUtilityFunctions::_recur2date(
154
+ $recurrence_dates,
155
+ $recurrence_rules,
156
+ $wdate,
157
+ $startdate,
158
+ $enddate
159
+ );
160
+ date_default_timezone_set( $restore_timezone );
161
+ }
162
 
163
+ if ( ! is_array( $recurrence_dates ) ) {
164
+ $recurrence_dates = array();
165
+ }
166
+ $recurrence_dates = array_keys( $recurrence_dates );
167
+ // Add the instances
168
+ foreach ( $recurrence_dates as $timestamp ) {
169
+ // The arrays are in the form timestamp => true so an isset call is what we need
170
+ if ( ! isset( $exclude_dates[$timestamp] ) ) {
171
+ $event_instance['start'] = $timestamp;
172
+ $event_instance['end'] = $timestamp + $duration;
173
+ $events[$timestamp] = $event_instance;
174
+ }
175
+ }
176
 
177
+ return $events;
178
+ }
179
 
180
+ /**
181
+ * Generate and store instance entries in database for given event.
182
+ *
183
+ * @param Ai1ec_Event $event Instance of event to create entries for.
184
+ *
185
+ * @return bool Success.
186
+ */
187
+ public function create( Ai1ec_Event $event ) {
188
+ $instances = $this->_create_instances_collection( $event );
189
+ $this->_add_instances( $instances );
190
+ return true;
191
+ }
192
 
193
+ /**
194
+ * Check if given date match dates in EXDATES rule.
195
+ *
196
+ * @param string $date Date to check.
197
+ * @param string $ics_rule ICS EXDATES rule.
198
+ * @param string $timezone Timezone to evaluate value in.
199
+ *
200
+ * @return bool True if given date is in rule.
201
+ */
202
+ public function date_match_exdates( $date, $ics_rule, $timezone ) {
203
+ $ranges = $this->_get_date_ranges( $ics_rule, $timezone );
204
+ foreach ( $ranges as $interval ) {
205
+ if ( $date >= $interval[0] && $date <= $interval[1] ) {
206
+ return true;
207
+ }
208
+ if ( $date <= $interval[0] ) {
209
+ break;
210
+ }
211
+ }
212
+ return false;
213
+ }
214
 
215
+ /**
216
+ * Prepare date range list for fast exdate search.
217
+ *
218
+ * NOTICE: timezone is relevant in only first run.
219
+ *
220
+ * @param string $date_list ICS list provided from data model.
221
+ * @param string $timezone Timezone in which to evaluate.
222
+ *
223
+ * @return array List of date ranges, sorted in increasing order.
224
+ */
225
+ protected function _get_date_ranges( $date_list, $timezone ) {
226
+ static $ranges = array();
227
+ if ( ! isset( $ranges[$date_list] ) ) {
228
+ $ranges[$date_list] = array();
229
+ $exploded = explode( ',', $date_list );
230
+ sort( $exploded );
231
+ foreach ( $exploded as $date ) {
232
+ // COMMENT on `rtrim( $date, 'Z' )`:
233
+ // user selects exclusion date in event timezone thus it
234
+ // must be parsed as such as opposed to UTC which happen
235
+ // when 'Z' is preserved.
236
+ $date = $this->_registry
237
+ ->get( 'date.time', rtrim( $date, 'Z' ), $timezone )
238
+ ->format_to_gmt();
239
+ $ranges[$date_list][] = array(
240
+ $date,
241
+ $date + (24 * 60 * 60) - 1
242
+ );
243
+ }
244
+ }
245
+ return $ranges[$date_list];
246
+ }
247
 
248
+ protected function _populate_recurring_dates( $rule, array $start_struct, $timezone ) {
249
+ $start = clone $start_struct['_dt'];
250
+ $dates = array();
251
+ foreach ( explode( ',', $rule ) as $date ) {
252
+ $i_date = clone $start;
253
+ $spec = sscanf( $date, '%04d%02d%02d' );
254
+ $i_date->set_date(
255
+ $spec[0],
256
+ $spec[1],
257
+ $spec[2]
258
+ );
259
+ $dates[$i_date->format_to_gmt()] = $i_date;
260
+ }
261
+ return $dates;
262
+ }
263
 
264
+ protected function _parsed_date_array( $startdate, $timezone ) {
265
+ $datetime = $this->_registry->get( 'date.time', $startdate, $timezone );
266
+ $parsed = array(
267
+ 'year' => intval( $datetime->format( 'Y' ) ),
268
+ 'month' => intval( $datetime->format( 'm' ) ),
269
+ 'day' => intval( $datetime->format( 'd' ) ),
270
+ 'hour' => intval( $datetime->format( 'H' ) ),
271
+ 'min' => intval( $datetime->format( 'i' ) ),
272
+ 'sec' => intval( $datetime->format( 's' ) ),
273
+ 'tz' => $datetime->get_timezone(),
274
+ '_dt' => $datetime,
275
+ );
276
+ return $parsed;
277
+ }
278
 
279
+ /**
280
+ * Returns current instances map.
281
+ *
282
+ * @param int post_id Post ID.
283
+ *
284
+ * @return array Array of data.
285
+ */
286
+ protected function _load_instances( $post_id ) {
287
+ $query = $this->_dbi->prepare(
288
+ 'SELECT `id`, `start`, `end` FROM ' .
289
+ $this->_dbi->get_table_name( 'ai1ec_event_instances' ) .
290
+ ' WHERE post_id = %d',
291
+ $post_id
292
+ );
293
+ $results = $this->_dbi->get_results( $query );
294
+ $instances = array();
295
+ foreach ( $results as $result ) {
296
+ $instances[(int)$result->start . ':' . (int)$result->end] = (int)$result->id;
297
+ }
298
+ return $instances;
299
+ }
300
 
301
+ /**
302
+ * Generate and store instance entries in database for given event.
303
+ *
304
+ * @param Ai1ec_Event $event Instance of event to create entries for.
305
+ *
306
+ * @return bool Success.
307
+ */
308
+ protected function _create_instances_collection( Ai1ec_Event $event ) {
309
+ $events = array();
310
+ $event_item = array(
311
+ 'post_id' => $event->get( 'post_id' ),
312
+ 'start' => $event->get( 'start' )->format_to_gmt(),
313
+ 'end' => $event->get( 'end' )->format_to_gmt(),
314
+ );
315
+ $duration = $event->get( 'end' )->diff_sec( $event->get( 'start' ) );
316
 
317
+ $_start = $event->get( 'start' )->format_to_gmt();
318
+ $_end = $event->get( 'end' )->format_to_gmt();
319
 
320
+ // Always cache initial instance
321
+ $events[$_start] = $event_item;
322
 
323
+ if ( $event->get( 'recurrence_rules' ) || $event->get( 'recurrence_dates' ) ) {
324
+ $start_timezone = $this->_registry->get( 'model.option' )
325
+ ->get( 'timezone_string' );
326
+ if ( empty( $start_timezone ) ) {
327
+ $start_timezone = $this->_registry->get( 'date.timezone' )->get_default_timezone();
328
+ }
329
 
330
+ $events += $this->create_instances_by_recurrence(
331
+ $event,
332
+ $event_item,
333
+ $_start,
334
+ $duration,
335
+ $start_timezone
336
+ );
337
+ }
338
 
339
+ $search_helper = $this->_registry->get( 'model.search' );
340
+ foreach ( $events as &$event_item ) {
341
+ // Find out if this event instance is already accounted for by an
342
+ // overriding 'RECURRENCE-ID' of the same iCalendar feed (by comparing the
343
+ // UID, start date, recurrence). If so, then do not create duplicate
344
+ // instance of event.
345
+ $start = $event_item['start'];
346
+ $matching_event_id = null;
347
+ if ( $event->get( 'ical_uid' ) ) {
348
+ $matching_event_id = $search_helper->get_matching_event_id(
349
+ $event->get( 'ical_uid' ),
350
+ $event->get( 'ical_feed_url' ),
351
+ $event->get( 'start' ),
352
+ false,
353
+ $event->get( 'post_id' )
354
+ );
355
+ }
356
 
357
+ // If no other instance was found
358
+ if ( null !== $matching_event_id ) {
359
+ $event_item = false;
360
+ }
361
+ }
362
 
363
+ return array_filter( $events );
364
+ }
365
 
366
+ /**
367
+ * Removes ai1ec_event_instances entries using their IDS.
368
+ *
369
+ * @param array $ids Collection of IDS.
370
+ *
371
+ * @return bool Result.
372
+ */
373
+ protected function _remove_instances_by_ids( array $ids ) {
374
+ if ( empty( $ids ) ) {
375
+ return false;
376
+ }
377
+ $query = 'DELETE FROM ' . $this->_dbi->get_table_name(
378
+ 'ai1ec_event_instances'
379
+ ) . ' WHERE id IN (';
380
+ $ids = array_filter( array_map( 'intval', $ids ) );
381
+ $query .= implode( ',', $ids ) . ')';
382
+ $this->_dbi->query( $query );
383
+ return true;
384
+ }
385
 
386
+ /**
387
+ * Adds new instances collection.
388
+ *
389
+ * @param array $instances Collection of instances.
390
+ *
391
+ * @return void
392
+ */
393
+ protected function _add_instances( array $instances ) {
394
+ $chunks = array_chunk( $instances, 50 );
395
+ foreach ( $chunks as $chunk ) {
396
+ $query = 'INSERT INTO ' . $this->_dbi->get_table_name(
397
+ 'ai1ec_event_instances'
398
+ ) . '(`post_id`, `start`, `end`) VALUES';
399
+ $chunk = array_map(
400
+ array( $this->_dbi_utils, 'array_value_to_sql_value' ),
401
+ $chunk
402
+ );
403
+ $query .= implode( ',', $chunk );
404
+ $this->_dbi->query( $query );
405
+ }
406
+ }
407
  }
app/model/event/legacy.php CHANGED
@@ -11,138 +11,138 @@
11
  */
12
  class Ai1ec_Event_Legacy extends Ai1ec_Event {
13
 
14
- /**
15
- * @var array map of method => class for legacy code.
16
- */
17
- protected static $_classes = array(
18
- 'get_category_colors' => 'taxonomy',
19
- 'get_color_style' => 'taxonomy',
20
- 'get_categories_html' => 'taxonomy',
21
- 'get_tags_html' => 'taxonomy',
22
- 'get_category_text_color' => 'taxonomy',
23
- 'get_category_bg_color' => 'taxonomy',
24
- 'get_faded_color' => 'color',
25
- 'get_rgba_color' => 'color',
26
- 'get_event_avatar' => 'avatar',
27
- 'get_event_avatar_url' => 'avatar',
28
- 'get_post_thumbnail_url' => 'avatar',
29
- 'get_content_img_url' => 'avatar',
30
- 'get_short_location' => 'location',
31
- 'get_location' => 'location',
32
- 'get_map_view' => 'location',
33
- 'get_latlng' => 'location',
34
- 'get_gmap_url' => 'location',
35
- 'get_tickets_url_label' => 'ticket',
36
- 'get_contact_html' => 'ticket',
37
- 'get_timespan_html' => 'time',
38
- 'get_exclude_html' => 'time',
39
- 'get_back_to_calendar_button_html' => 'content',
40
- 'get_post_excerpt' => 'content',
41
- );
42
 
43
- public function get_long_end_date( $adjust = 0 ) {
44
- $time = $this->_registry->get( 'view.event.time' );
45
- $end = $this->_registry->get( 'date.time', $this->get( 'end' ) );
46
- if ( ! empty( $adjust ) ) {
47
- $end->set_time(
48
- $end->format( 'H' ),
49
- $end->format( 'i' ),
50
- $adjust
51
- );
52
- }
53
- return $time->get_long_date( $end );
54
- }
55
 
56
- public function get_long_start_date() {
57
- $time = $this->_registry->get( 'view.event.time' );
58
- return $time->get_long_date( $this->get( 'start' ) );
59
- }
60
 
61
- public function get_multiday() {
62
- return $this->is_multiday();
63
- }
64
 
65
- public function get_recurrence_html() {
66
- $rrule = $this->_registry->get( 'recurrence.rule' );
67
- return $rrule->rrule_to_text( $this->get( 'recurrence_rules' ) );
68
- }
69
 
70
- public function get_short_end_date() {
71
- $time = $this->_registry->get( 'view.event.time' );
72
- $end = $this->_registry->get( 'date.time', $this->get( 'end' ) );
73
- $end->set_time(
74
- $end->format( 'H' ),
75
- $end->format( 'i' ),
76
- -1
77
- );
78
- return $time->get_short_date( $end );
79
- }
80
 
81
- public function get_short_end_time() {
82
- $time = $this->_registry->get( 'view.event.time' );
83
- return $time->get_short_time( $this->get( 'end' ) );
84
- }
85
 
86
- public function get_short_start_date() {
87
- $time = $this->_registry->get( 'view.event.time' );
88
- return $time->get_short_date( $this->get( 'start' ) );
89
- }
90
 
91
- public function get_short_start_time() {
92
- $time = $this->_registry->get( 'view.event.time' );
93
- return $time->get_short_time( $this->get( 'start' ) );
94
- }
95
 
96
- /**
97
- * Handles legacy property setters.
98
- *
99
- * @param string $property Name of property being set.
100
- * @param mixed $value Value attempted to set.
101
- *
102
- * @return Ai1ec_Event Instance of self for chaining.
103
- */
104
- public function __set( $property, $value ) {
105
- return $this->set( $property, $value );
106
- }
107
 
108
- /**
109
- * Handle property accessors.
110
- *
111
- * @param string $name Property name
112
- *
113
- * @return mixed Property value
114
- */
115
- public function __get( $name ) {
116
- $method = 'get_' . $name;
117
- if ( method_exists( $this, $name ) ) {
118
- return $this->{$method}();
119
- }
120
- return $this->get( $name );
121
- }
122
 
123
- /**
124
- * Handle legacy methods calls.
125
- *
126
- * @param string $method Legacy method name.
127
- * @param array $arguments Arguments passed to method.
128
- *
129
- * @return mixed
130
- *
131
- * @throws Ai1ec_Invalid_Argument_Exception If there is no method handler.
132
- */
133
- public function __call( $method, $arguments ) {
134
- if ( ! isset( self::$_classes[$method] ) ) {
135
- throw new Ai1ec_Invalid_Argument_Exception(
136
- 'Requested method \'' . $method . '\' is unknown'
137
- );
138
- }
139
- array_unshift( $arguments, $this );
140
- $class = 'view.event.' . self::$_classes[$method];
141
- return $this->_registry->dispatch(
142
- $class,
143
- $method,
144
- $arguments
145
- );
146
- }
147
 
148
  }
11
  */
12
  class Ai1ec_Event_Legacy extends Ai1ec_Event {
13
 
14
+ /**
15
+ * @var array map of method => class for legacy code.
16
+ */
17
+ protected static $_classes = array(
18
+ 'get_category_colors' => 'taxonomy',
19
+ 'get_color_style' => 'taxonomy',
20
+ 'get_categories_html' => 'taxonomy',
21
+ 'get_tags_html' => 'taxonomy',
22
+ 'get_category_text_color' => 'taxonomy',
23
+ 'get_category_bg_color' => 'taxonomy',
24
+ 'get_faded_color' => 'color',
25
+ 'get_rgba_color' => 'color',
26
+ 'get_event_avatar' => 'avatar',
27
+ 'get_event_avatar_url' => 'avatar',
28
+ 'get_post_thumbnail_url' => 'avatar',
29
+ 'get_content_img_url' => 'avatar',
30
+ 'get_short_location' => 'location',
31
+ 'get_location' => 'location',
32
+ 'get_map_view' => 'location',
33
+ 'get_latlng' => 'location',
34
+ 'get_gmap_url' => 'location',
35
+ 'get_tickets_url_label' => 'ticket',
36
+ 'get_contact_html' => 'ticket',
37
+ 'get_timespan_html' => 'time',
38
+ 'get_exclude_html' => 'time',
39
+ 'get_back_to_calendar_button_html' => 'content',
40
+ 'get_post_excerpt' => 'content',
41
+ );
42
 
43
+ public function get_long_end_date( $adjust = 0 ) {
44
+ $time = $this->_registry->get( 'view.event.time' );
45
+ $end = $this->_registry->get( 'date.time', $this->get( 'end' ) );
46
+ if ( ! empty( $adjust ) ) {
47
+ $end->set_time(
48
+ $end->format( 'H' ),
49
+ $end->format( 'i' ),
50
+ $adjust
51
+ );
52
+ }
53
+ return $time->get_long_date( $end );
54
+ }
55
 
56
+ public function get_long_start_date() {
57
+ $time = $this->_registry->get( 'view.event.time' );
58
+ return $time->get_long_date( $this->get( 'start' ) );
59
+ }
60
 
61
+ public function get_multiday() {
62
+ return $this->is_multiday();
63
+ }
64
 
65
+ public function get_recurrence_html() {
66
+ $rrule = $this->_registry->get( 'recurrence.rule' );
67
+ return $rrule->rrule_to_text( $this->get( 'recurrence_rules' ) );
68
+ }
69
 
70
+ public function get_short_end_date() {
71
+ $time = $this->_registry->get( 'view.event.time' );
72
+ $end = $this->_registry->get( 'date.time', $this->get( 'end' ) );
73
+ $end->set_time(
74
+ $end->format( 'H' ),
75
+ $end->format( 'i' ),
76
+ -1
77
+ );
78
+ return $time->get_short_date( $end );
79
+ }
80
 
81
+ public function get_short_end_time() {
82
+ $time = $this->_registry->get( 'view.event.time' );
83
+ return $time->get_short_time( $this->get( 'end' ) );
84
+ }
85
 
86
+ public function get_short_start_date() {
87
+ $time = $this->_registry->get( 'view.event.time' );
88
+ return $time->get_short_date( $this->get( 'start' ) );
89
+ }
90
 
91
+ public function get_short_start_time() {
92
+ $time = $this->_registry->get( 'view.event.time' );
93
+ return $time->get_short_time( $this->get( 'start' ) );
94
+ }
95
 
96
+ /**
97
+ * Handles legacy property setters.
98
+ *
99
+ * @param string $property Name of property being set.
100
+ * @param mixed $value Value attempted to set.
101
+ *
102
+ * @return Ai1ec_Event Instance of self for chaining.
103
+ */
104
+ public function __set( $property, $value ) {
105
+ return $this->set( $property, $value );
106
+ }
107
 
108
+ /**
109
+ * Handle property accessors.
110
+ *
111
+ * @param string $name Property name
112
+ *
113
+ * @return mixed Property value
114
+ */
115
+ public function __get( $name ) {
116
+ $method = 'get_' . $name;
117
+ if ( method_exists( $this, $name ) ) {
118
+ return $this->{$method}();
119
+ }
120
+ return $this->get( $name );
121
+ }
122
 
123
+ /**
124
+ * Handle legacy methods calls.
125
+ *
126
+ * @param string $method Legacy method name.
127
+ * @param array $arguments Arguments passed to method.
128
+ *
129
+ * @return mixed
130
+ *
131
+ * @throws Ai1ec_Invalid_Argument_Exception If there is no method handler.
132
+ */
133
+ public function __call( $method, $arguments ) {
134
+ if ( ! isset( self::$_classes[$method] ) ) {
135
+ throw new Ai1ec_Invalid_Argument_Exception(
136
+ 'Requested method \'' . $method . '\' is unknown'
137
+ );
138
+ }
139
+ array_unshift( $arguments, $this );
140
+ $class = 'view.event.' . self::$_classes[$method];
141
+ return $this->_registry->dispatch(
142
+ $class,
143
+ $method,
144
+ $arguments
145
+ );
146
+ }
147
 
148
  }
app/model/event/parent.php CHANGED
@@ -9,358 +9,358 @@
9
  */
10
  class Ai1ec_Event_Parent extends Ai1ec_Base {
11
 
12
- /**
13
- * event_parent method
14
- *
15
- * Get/set event parent
16
- *
17
- * @param int $event_id ID of checked event
18
- * @param int $parent_id ID of new parent [optional=NULL, acts as getter]
19
- * @param int $instance_id ID of old instance id
20
- *
21
- * @return int|bool Value depends on mode:
22
- * Getter: {@see self::get_parent_event()} for details
23
- * Setter: true on success.
24
- */
25
- public function event_parent(
26
- $event_id,
27
- $parent_id = null,
28
- $instance_id = null
29
- ) {
30
- $meta_key = '_ai1ec_event_parent';
31
- if ( null === $parent_id ) {
32
- return $this->get_parent_event( $event_id );
33
- }
34
- $meta_value = json_encode( array(
35
- 'created' => $this->_registry->get( 'date.system' )->current_time(),
36
- 'instance' => $instance_id,
37
- ) );
38
- return add_post_meta( $event_id, $meta_key, $meta_value, true );
39
- }
40
 
41
- /**
42
- * Get parent ID for given event
43
- *
44
- * @param int $current_id Current event ID
45
- *
46
- * @return int|bool ID of parent event or bool(false)
47
- */
48
- public function get_parent_event( $current_id ) {
49
- static $parents = null;
50
- if ( null === $parents ) {
51
- $parents = $this->_registry->get( 'cache.memory' );
52
- }
53
- $current_id = (int)$current_id;
54
- if ( null === ( $parent_id = $parents->get( $current_id ) ) ) {
55
- $db = $this->_registry->get( 'dbi.dbi' );
56
- /* @var $db Ai1ec_Dbi */
57
- $query = '
58
- SELECT parent.ID, parent.post_status
59
- FROM
60
- ' . $db->get_table_name( 'posts' ) . ' AS child
61
- INNER JOIN ' . $db->get_table_name( 'posts' ) . ' AS parent
62
- ON ( parent.ID = child.post_parent )
63
- WHERE child.ID = ' . $current_id;
64
- $parent = $db->get_row( $query );
65
- if (
66
- empty( $parent ) ||
67
- 'trash' === $parent->post_status
68
- ) {
69
- $parent_id = false;
70
- } else {
71
- $parent_id = $parent->ID;
72
- }
73
- $parents->set( $current_id, $parent_id );
74
- unset( $query );
75
- }
76
- return $parent_id;
77
- }
78
 
79
- /**
80
- * Returns a list of modified (children) event objects
81
- *
82
- * @param int $parent_id ID of parent event
83
- * @param bool $include_trash Includes trashed when `true` [optional=false]
84
- *
85
- * @return array List (might be empty) of Ai1ec_Event objects
86
- */
87
- public function get_child_event_objects(
88
- $parent_id,
89
- $include_trash = false
90
- ) {
91
- $db = $this->_registry->get( 'dbi.dbi' );
92
- /* @var $db Ai1ec_Dbi */
93
- $parent_id = (int)$parent_id;
94
- $sql_query = 'SELECT ID FROM ' . $db->get_table_name( 'posts' ) .
95
- ' WHERE post_parent = ' . $parent_id;
96
- $children = (array)$db->get_col( $sql_query );
97
- $objects = array();
98
- foreach ( $children as $child_id ) {
99
- try {
100
- $instance = $this->_registry->get( 'model.event', $child_id );
101
- if (
102
- $include_trash ||
103
- 'trash' !== $instance->get( 'post' )->post_status
104
- ) {
105
- $objects[$child_id] = $instance;
106
- }
107
- } catch ( Ai1ec_Event_Not_Found_Exception $exception ) {
108
- // ignore
109
- }
110
- }
111
- return $objects;
112
- }
113
 
114
- /**
115
- * admin_init_post method
116
- *
117
- * Bind to admin_action_editpost action to override default save
118
- * method when user is editing single instance.
119
- * New post is created with some fields unset.
120
- */
121
- public function admin_init_post( ) {
122
- if (
123
- isset( $_POST['ai1ec_instance_id'] ) &&
124
- isset( $_POST['action'] ) &&
125
- 'editpost' === $_POST['action']
126
- ) {
127
- $old_post_id = $_POST['post_ID'];
128
- $instance_id = $_POST['ai1ec_instance_id'];
129
- $post_id = $this->_registry->get( 'model.event.creating' )
130
- ->create_duplicate_post();
131
- if ( false !== $post_id ) {
132
- $this->_handle_instances(
133
- $this->_registry->get( 'model.event', $post_id ),
134
- $this->_registry->get( 'model.event', $old_post_id ),
135
- $instance_id
136
- );
137
- $this->_registry->get( 'model.event.instance' )->clean(
138
- $old_post_id,
139
- $instance_id
140
- );
141
- $location = add_query_arg(
142
- 'message',
143
- 1,
144
- get_edit_post_link( $post_id, 'url' )
145
- );
146
- wp_redirect(
147
- apply_filters(
148
- 'redirect_post_location',
149
- $location,
150
- $post_id
151
- )
152
- );
153
- exit();
154
- }
155
- }
156
- }
157
 
158
- /**
159
- * Inject base event edit link for modified instances
160
- *
161
- * Modified instances are events, belonging to some parent having recurrence
162
- * rule, and having some of it's properties altered.
163
- *
164
- * @param array $actions List of defined actions
165
- * @param stdClass $post Instance being rendered (WP_Post class instance in WP 3.5+)
166
- *
167
- * @return array Optionally modified $actions list
168
- */
169
- public function post_row_actions( $actions, $post ) {
170
- if ( $this->_registry->get( 'acl.aco' )->is_our_post_type( $post ) ) {
171
- $parent_post_id = $this->event_parent( $post->ID );
172
- if (
173
- $parent_post_id &&
174
- NULL !== ( $parent_post = get_post( $parent_post_id ) ) &&
175
- isset( $parent_post->post_status ) &&
176
- 'trash' !== $parent_post->post_status
177
- ) {
178
- $parent_link = get_edit_post_link(
179
- $parent_post_id,
180
- 'display'
181
- );
182
- $actions['ai1ec_parent'] = sprintf(
183
- '<a href="%s" title="%s">%s</a>',
184
- wp_nonce_url( $parent_link ),
185
- sprintf(
186
- __( 'Edit &#8220;%s&#8221;', AI1EC_PLUGIN_NAME ),
187
- apply_filters(
188
- 'the_title',
189
- $parent_post->post_title,
190
- $parent_post->ID
191
- )
192
- ),
193
- __( 'Base Event', AI1EC_PLUGIN_NAME )
194
- );
195
- }
196
- }
197
- return $actions;
198
- }
199
 
200
- /**
201
- * add_exception_date method
202
- *
203
- * Add exception (date) to event.
204
- *
205
- * @param int $post_id Event edited post ID
206
- * @param mixed $date Parseable date representation to exclude
207
- *
208
- * @return bool Success
209
- */
210
- public function add_exception_date( $post_id, Ai1ec_Date_Time $date ) {
211
- $event = $this->_registry->get( 'model.event', $post_id );
212
- $dates_list = explode( ',', $event->get( 'exception_dates' ) );
213
- if ( empty( $dates_list[0] ) ) {
214
- unset( $dates_list[0] );
215
- }
216
- $date->set_time( 0, 0, 0 );
217
- $dates_list[] = $date->format(
218
- 'Ymd\THis\Z'
219
- );
220
- $event->set( 'exception_dates', implode( ',', $dates_list ) );
221
- return $event->save( true );
222
- }
223
 
224
- /**
225
- * Handles instances saving and switching if needed. If original event
226
- * and created event have different start dates proceed in old style
227
- * otherwise find next instance, switch original start date to next
228
- * instance start date. If there are no next instances mark event as
229
- * non recurring. Filter also exception dates if are past.
230
- *
231
- * @param Ai1ec_Event $created_event Created event object.
232
- * @param Ai1ec_Event $original_event Original event object.
233
- *
234
- * @return void Method does not return.
235
- */
236
- protected function _handle_instances(
237
- Ai1ec_Event $created_event,
238
- Ai1ec_Event $original_event,
239
- $instance_id
240
- ) {
241
- $ce_start = $created_event->get( 'start' );
242
- $oe_start = $original_event->get( 'start' );
243
- if (
244
- $ce_start->format() !== $oe_start->format()
245
- ) {
246
- $this->add_exception_date(
247
- $original_event->get( 'post_id' ),
248
- $ce_start
249
- );
250
- return;
251
- }
252
- $next_instance = $this->_find_next_instance(
253
- $original_event->get( 'post_id' ),
254
- $instance_id
255
- );
256
- if ( ! $next_instance ) {
257
- $original_event->set( 'recurrence_rules', null );
258
- $original_event->save( true );
259
- return;
260
- }
261
- $original_event->set(
262
- 'start',
263
- $this->_registry->get( 'date.time', $next_instance->get( 'start' ) )
264
- );
265
- $original_event->set(
266
- 'end',
267
- $this->_registry->get( 'date.time', $next_instance->get( 'end' ) )
268
- );
269
- $edates = $this->_filter_exception_dates( $original_event );
270
- $original_event->set( 'exception_dates', implode( ',', $edates ) );
271
- $recurrence_rules = $original_event->get( 'recurrence_rules' );
272
- $rules_info = $this->_registry->get( 'recurrence.rule' )
273
- ->build_recurrence_rules_array( $recurrence_rules );
274
- if ( isset( $rules_info['COUNT'] ) ) {
275
- $next_instances_count = $this->_count_next_instances(
276
- $original_event->get( 'post_id' ),
277
- $instance_id
278
- );
279
- $rules_info['COUNT'] = (int)$next_instances_count + count( $edates );
280
- $rules = '';
281
- if ( $rules_info['COUNT'] <= 1 ) {
282
- $rules_info = array();
283
- }
284
- foreach ( $rules_info as $key => $value ) {
285
- $rules .= $key . '=' . $value . ';';
286
- }
287
- $original_event->set(
288
- 'recurrence_rules',
289
- $rules
290
- );
291
- }
292
- $original_event->save( true );
293
- }
294
 
295
- /**
296
- * Returns next instance.
297
- *
298
- * @param int $post_id Post ID.
299
- * @param int $instance_id Instance ID.
300
- *
301
- * @return null|Ai1ec_Event Result.
302
- */
303
- protected function _find_next_instance( $post_id, $instance_id ) {
304
- $dbi = $this->_registry->get( 'dbi.dbi' );
305
- $table_instances = $dbi->get_table_name( 'ai1ec_event_instances' );
306
- $table_posts = $dbi->get_table_name( 'posts' );
307
- $query = $dbi->prepare(
308
- 'SELECT i.id FROM ' . $table_instances . ' i JOIN ' .
309
- $table_posts . ' p ON (p.ID = i.post_id) ' .
310
- 'WHERE i.post_id = %d AND i.id > %d ' .
311
- 'AND p.post_status = \'publish\' ' .
312
- 'ORDER BY id ASC LIMIT 1',
313
- $post_id,
314
- $instance_id
315
- );
316
- $next_instance_id = $dbi->get_var( $query );
317
- if ( ! $next_instance_id ) {
318
- return null;
319
- }
320
- return $this->_registry->get( 'model.search' )
321
- ->get_event( $post_id, $next_instance_id );
322
- }
323
 
324
- /**
325
- * Counts future instances.
326
- *
327
- * @param int $post_id Post ID.
328
- * @param int $instance_id Instance ID.
329
- *
330
- * @return int Result.
331
- */
332
- protected function _count_next_instances( $post_id, $instance_id ) {
333
- $dbi = $this->_registry->get( 'dbi.dbi' );
334
- $table_instances = $dbi->get_table_name( 'ai1ec_event_instances' );
335
- $table_posts = $dbi->get_table_name( 'posts' );
336
- $query = $dbi->prepare(
337
- 'SELECT COUNT(i.id) FROM ' . $table_instances . ' i JOIN ' .
338
- $table_posts . ' p ON (p.ID = i.post_id) ' .
339
- 'WHERE i.post_id = %d AND i.id > %d ' .
340
- 'AND p.post_status = \'publish\'',
341
- $post_id,
342
- $instance_id
343
- );
344
- return (int)$dbi->get_var( $query );
345
- }
346
 
347
- /**
348
- * Filters past or out of range exception dates.
349
- *
350
- * @param Ai1ec_Event $event Event.
351
- *
352
- * @return array Filtered exception dates.
353
- */
354
- protected function _filter_exception_dates( Ai1ec_Event $event ) {
355
- $start = (int)$event->get( 'start' )->format();
356
- $exception_dates = explode( ',', $event->get( 'exception_dates' ) );
357
- $dates = array();
358
- foreach ( $exception_dates as $date ) {
359
- $ex_date = (int)$this->_registry->get( 'date.time', $date )->format();
360
- if ( $ex_date > $start ) {
361
- $dates[] = $date;
362
- }
363
- }
364
- return $dates;
365
- }
366
  }
9
  */
10
  class Ai1ec_Event_Parent extends Ai1ec_Base {
11
 
12
+ /**
13
+ * event_parent method
14
+ *
15
+ * Get/set event parent
16
+ *
17
+ * @param int $event_id ID of checked event
18
+ * @param int $parent_id ID of new parent [optional=NULL, acts as getter]
19
+ * @param int $instance_id ID of old instance id
20
+ *
21
+ * @return int|bool Value depends on mode:
22
+ * Getter: {@see self::get_parent_event()} for details
23
+ * Setter: true on success.
24
+ */
25
+ public function event_parent(
26
+ $event_id,
27
+ $parent_id = null,
28
+ $instance_id = null
29
+ ) {
30
+ $meta_key = '_ai1ec_event_parent';
31
+ if ( null === $parent_id ) {
32
+ return $this->get_parent_event( $event_id );
33
+ }
34
+ $meta_value = json_encode( array(
35
+ 'created' => $this->_registry->get( 'date.system' )->current_time(),
36
+ 'instance' => $instance_id,
37
+ ) );
38
+ return add_post_meta( $event_id, $meta_key, $meta_value, true );
39
+ }
40
 
41
+ /**
42
+ * Get parent ID for given event
43
+ *
44
+ * @param int $current_id Current event ID
45
+ *
46
+ * @return int|bool ID of parent event or bool(false)
47
+ */
48
+ public function get_parent_event( $current_id ) {
49
+ static $parents = null;
50
+ if ( null === $parents ) {
51
+ $parents = $this->_registry->get( 'cache.memory' );
52
+ }
53
+ $current_id = (int)$current_id;
54
+ if ( null === ( $parent_id = $parents->get( $current_id ) ) ) {
55
+ $db = $this->_registry->get( 'dbi.dbi' );
56
+ /* @var $db Ai1ec_Dbi */
57
+ $query = '
58
+ SELECT parent.ID, parent.post_status
59
+ FROM
60
+ ' . $db->get_table_name( 'posts' ) . ' AS child
61
+ INNER JOIN ' . $db->get_table_name( 'posts' ) . ' AS parent
62
+ ON ( parent.ID = child.post_parent )
63
+ WHERE child.ID = ' . $current_id;
64
+ $parent = $db->get_row( $query );
65
+ if (
66
+ empty( $parent ) ||
67
+ 'trash' === $parent->post_status
68
+ ) {
69
+ $parent_id = false;
70
+ } else {
71
+ $parent_id = $parent->ID;
72
+ }
73
+ $parents->set( $current_id, $parent_id );
74
+ unset( $query );
75
+ }
76
+ return $parent_id;
77
+ }
78
 
79
+ /**
80
+ * Returns a list of modified (children) event objects
81
+ *
82
+ * @param int $parent_id ID of parent event
83
+ * @param bool $include_trash Includes trashed when `true` [optional=false]
84
+ *
85
+ * @return array List (might be empty) of Ai1ec_Event objects
86
+ */
87
+ public function get_child_event_objects(
88
+ $parent_id,
89
+ $include_trash = false
90
+ ) {
91
+ $db = $this->_registry->get( 'dbi.dbi' );
92
+ /* @var $db Ai1ec_Dbi */
93
+ $parent_id = (int)$parent_id;
94
+ $sql_query = 'SELECT ID FROM ' . $db->get_table_name( 'posts' ) .
95
+ ' WHERE post_parent = ' . $parent_id;
96
+ $children = (array)$db->get_col( $sql_query );
97
+ $objects = array();
98
+ foreach ( $children as $child_id ) {
99
+ try {
100
+ $instance = $this->_registry->get( 'model.event', $child_id );
101
+ if (
102
+ $include_trash ||
103
+ 'trash' !== $instance->get( 'post' )->post_status
104
+ ) {
105
+ $objects[$child_id] = $instance;
106
+ }
107
+ } catch ( Ai1ec_Event_Not_Found_Exception $exception ) {
108
+ // ignore
109
+ }
110
+ }
111
+ return $objects;
112
+ }
113
 
114
+ /**
115
+ * admin_init_post method
116
+ *
117
+ * Bind to admin_action_editpost action to override default save
118
+ * method when user is editing single instance.
119
+ * New post is created with some fields unset.
120
+ */
121
+ public function admin_init_post( ) {
122
+ if (
123
+ isset( $_POST['ai1ec_instance_id'] ) &&
124
+ isset( $_POST['action'] ) &&
125
+ 'editpost' === $_POST['action']
126
+ ) {
127
+ $old_post_id = $_POST['post_ID'];
128
+ $instance_id = $_POST['ai1ec_instance_id'];
129
+ $post_id = $this->_registry->get( 'model.event.creating' )
130
+ ->create_duplicate_post();
131
+ if ( false !== $post_id ) {
132
+ $this->_handle_instances(
133
+ $this->_registry->get( 'model.event', $post_id ),
134
+ $this->_registry->get( 'model.event', $old_post_id ),
135
+ $instance_id
136
+ );
137
+ $this->_registry->get( 'model.event.instance' )->clean(
138
+ $old_post_id,
139
+ $instance_id
140
+ );
141
+ $location = add_query_arg(
142
+ 'message',
143
+ 1,
144
+ get_edit_post_link( $post_id, 'url' )
145
+ );
146
+ wp_redirect(
147
+ apply_filters(
148
+ 'redirect_post_location',
149
+ $location,
150
+ $post_id
151
+ )
152
+ );
153
+ exit();
154
+ }
155
+ }
156
+ }
157
 
158
+ /**
159
+ * Inject base event edit link for modified instances
160
+ *
161
+ * Modified instances are events, belonging to some parent having recurrence
162
+ * rule, and having some of it's properties altered.
163
+ *
164
+ * @param array $actions List of defined actions
165
+ * @param stdClass $post Instance being rendered (WP_Post class instance in WP 3.5+)
166
+ *
167
+ * @return array Optionally modified $actions list
168
+ */
169
+ public function post_row_actions( $actions, $post ) {
170
+ if ( $this->_registry->get( 'acl.aco' )->is_our_post_type( $post ) ) {
171
+ $parent_post_id = $this->event_parent( $post->ID );
172
+ if (
173
+ $parent_post_id &&
174
+ NULL !== ( $parent_post = get_post( $parent_post_id ) ) &&
175
+ isset( $parent_post->post_status ) &&
176
+ 'trash' !== $parent_post->post_status
177
+ ) {
178
+ $parent_link = get_edit_post_link(
179
+ $parent_post_id,
180
+ 'display'
181
+ );
182
+ $actions['ai1ec_parent'] = sprintf(
183
+ '<a href="%s" title="%s">%s</a>',
184
+ wp_nonce_url( $parent_link ),
185
+ sprintf(
186
+ __( 'Edit &#8220;%s&#8221;', AI1EC_PLUGIN_NAME ),
187
+ apply_filters(
188
+ 'the_title',
189
+ $parent_post->post_title,
190
+ $parent_post->ID
191
+ )
192
+ ),
193
+ __( 'Base Event', AI1EC_PLUGIN_NAME )
194
+ );
195
+ }
196
+ }
197
+ return $actions;
198
+ }
199
 
200
+ /**
201
+ * add_exception_date method
202
+ *
203
+ * Add exception (date) to event.
204
+ *
205
+ * @param int $post_id Event edited post ID
206
+ * @param mixed $date Parseable date representation to exclude
207
+ *
208
+ * @return bool Success
209
+ */
210
+ public function add_exception_date( $post_id, Ai1ec_Date_Time $date ) {
211
+ $event = $this->_registry->get( 'model.event', $post_id );
212
+ $dates_list = explode( ',', $event->get( 'exception_dates' ) );
213
+ if ( empty( $dates_list[0] ) ) {
214
+ unset( $dates_list[0] );
215
+ }
216
+ $date->set_time( 0, 0, 0 );
217
+ $dates_list[] = $date->format(
218
+ 'Ymd\THis\Z'
219
+ );
220
+ $event->set( 'exception_dates', implode( ',', $dates_list ) );
221
+ return $event->save( true );
222
+ }
223
 
224
+ /**
225
+ * Handles instances saving and switching if needed. If original event
226
+ * and created event have different start dates proceed in old style
227
+ * otherwise find next instance, switch original start date to next
228
+ * instance start date. If there are no next instances mark event as
229
+ * non recurring. Filter also exception dates if are past.
230
+ *
231
+ * @param Ai1ec_Event $created_event Created event object.
232
+ * @param Ai1ec_Event $original_event Original event object.
233
+ *
234
+ * @return void Method does not return.
235
+ */
236
+ protected function _handle_instances(
237
+ Ai1ec_Event $created_event,
238
+ Ai1ec_Event $original_event,
239
+ $instance_id
240
+ ) {
241
+ $ce_start = $created_event->get( 'start' );
242
+ $oe_start = $original_event->get( 'start' );
243
+ if (
244
+ $ce_start->format() !== $oe_start->format()
245
+ ) {
246
+ $this->add_exception_date(
247
+ $original_event->get( 'post_id' ),
248
+ $ce_start
249
+ );
250
+ return;
251
+ }
252
+ $next_instance = $this->_find_next_instance(
253
+ $original_event->get( 'post_id' ),
254
+ $instance_id
255
+ );
256
+ if ( ! $next_instance ) {
257
+ $original_event->set( 'recurrence_rules', null );
258
+ $original_event->save( true );
259
+ return;
260
+ }
261
+ $original_event->set(
262
+ 'start',
263
+ $this->_registry->get( 'date.time', $next_instance->get( 'start' ) )
264
+ );
265
+ $original_event->set(
266
+ 'end',
267
+ $this->_registry->get( 'date.time', $next_instance->get( 'end' ) )
268
+ );
269
+ $edates = $this->_filter_exception_dates( $original_event );
270
+ $original_event->set( 'exception_dates', implode( ',', $edates ) );
271
+ $recurrence_rules = $original_event->get( 'recurrence_rules' );
272
+ $rules_info = $this->_registry->get( 'recurrence.rule' )
273
+ ->build_recurrence_rules_array( $recurrence_rules );
274
+ if ( isset( $rules_info['COUNT'] ) ) {
275
+ $next_instances_count = $this->_count_next_instances(
276
+ $original_event->get( 'post_id' ),
277
+ $instance_id
278
+ );
279
+ $rules_info['COUNT'] = (int)$next_instances_count + count( $edates );
280
+ $rules = '';
281
+ if ( $rules_info['COUNT'] <= 1 ) {
282
+ $rules_info = array();
283
+ }
284
+ foreach ( $rules_info as $key => $value ) {
285
+ $rules .= $key . '=' . $value . ';';
286
+ }
287
+ $original_event->set(
288
+ 'recurrence_rules',
289
+ $rules
290
+ );
291
+ }
292
+ $original_event->save( true );
293
+ }
294
 
295
+ /**
296
+ * Returns next instance.
297
+ *
298
+ * @param int $post_id Post ID.
299
+ * @param int $instance_id Instance ID.
300
+ *
301
+ * @return null|Ai1ec_Event Result.
302
+ */
303
+ protected function _find_next_instance( $post_id, $instance_id ) {
304
+ $dbi = $this->_registry->get( 'dbi.dbi' );
305
+ $table_instances = $dbi->get_table_name( 'ai1ec_event_instances' );
306
+ $table_posts = $dbi->get_table_name( 'posts' );
307
+ $query = $dbi->prepare(
308
+ 'SELECT i.id FROM ' . $table_instances . ' i JOIN ' .
309
+ $table_posts . ' p ON (p.ID = i.post_id) ' .
310
+ 'WHERE i.post_id = %d AND i.id > %d ' .
311
+ 'AND p.post_status = \'publish\' ' .
312
+ 'ORDER BY id ASC LIMIT 1',
313
+ $post_id,
314
+ $instance_id
315
+ );
316
+ $next_instance_id = $dbi->get_var( $query );
317
+ if ( ! $next_instance_id ) {
318
+ return null;
319
+ }
320
+ return $this->_registry->get( 'model.search' )
321
+ ->get_event( $post_id, $next_instance_id );
322
+ }
323
 
324
+ /**
325
+ * Counts future instances.
326
+ *
327
+ * @param int $post_id Post ID.
328
+ * @param int $instance_id Instance ID.
329
+ *
330
+ * @return int Result.
331
+ */
332
+ protected function _count_next_instances( $post_id, $instance_id ) {
333
+ $dbi = $this->_registry->get( 'dbi.dbi' );
334
+ $table_instances = $dbi->get_table_name( 'ai1ec_event_instances' );
335
+ $table_posts = $dbi->get_table_name( 'posts' );
336
+ $query = $dbi->prepare(
337
+ 'SELECT COUNT(i.id) FROM ' . $table_instances . ' i JOIN ' .
338
+ $table_posts . ' p ON (p.ID = i.post_id) ' .
339
+ 'WHERE i.post_id = %d AND i.id > %d ' .
340
+ 'AND p.post_status = \'publish\'',
341
+ $post_id,
342
+ $instance_id
343
+ );
344
+ return (int)$dbi->get_var( $query );
345
+ }
346
 
347
+ /**
348
+ * Filters past or out of range exception dates.
349
+ *
350
+ * @param Ai1ec_Event $event Event.
351
+ *
352
+ * @return array Filtered exception dates.
353
+ */
354
+ protected function _filter_exception_dates( Ai1ec_Event $event ) {
355
+ $start = (int)$event->get( 'start' )->format();
356
+ $exception_dates = explode( ',', $event->get( 'exception_dates' ) );
357
+ $dates = array();
358
+ foreach ( $exception_dates as $date ) {
359
+ $ex_date = (int)$this->_registry->get( 'date.time', $date )->format();
360
+ if ( $ex_date > $start ) {
361
+ $dates[] = $date;
362
+ }
363
+ }
364
+ return $dates;
365
+ }
366
  }
app/model/event/taxonomy.php CHANGED
@@ -12,185 +12,185 @@
12
 
13
  class Ai1ec_Event_Taxonomy extends Ai1ec_Base {
14
 
15
- /**
16
- * @var string Name of categories taxonomy.
17
- */
18
- const CATEGORIES = 'events_categories';
19
 
20
- /**
21
- * @var string Name of tags taxonomy.
22
- */
23
- const TAGS = 'events_tags';
24
 
25
- /**
26
- * @var string Name of feeds taxonomy.
27
- */
28
- const FEEDS = 'events_feeds';
29
 
30
- /**
31
- * @var int ID of related post object
32
- */
33
- protected $_post_id = 0;
34
 
35
- /**
36
- * Store event ID in local variable.
37
- *
38
- * @param int $post_id ID of post being managed.
39
- *
40
- * @return void
41
- */
42
- public function __construct( Ai1ec_Registry_Object $registry, $post_id = 0 ) {
43
- parent::__construct( $registry );
44
- $this->_post_id = (int)$post_id;
45
- }
46
 
47
- /**
48
- * Get ID of term. Optionally create it if it doesn't exist.
49
- *
50
- * @param string $term Name of term to create.
51
- * @param string $taxonomy Name of taxonomy to contain term within.
52
- * @param bool $is_id Set to true if $term is ID.
53
- * @param array $attrs Attributes to creatable entity.
54
- *
55
- * @return array|bool Associative array with term_id
56
- * and taxonomy keys or false on error
57
- */
58
- public function initiate_term(
59
- $term,
60
- $taxonomy,
61
- $is_id = false,
62
- array $attrs = array()
63
- ) {
64
- // cast to int to have it working with term_exists
65
- $term = ( $is_id ) ? (int) $term : $term;
66
- $term_to_check = term_exists( $term, $taxonomy );
67
- $to_return = array(
68
- 'taxonomy' => $taxonomy
69
- );
70
- // if term doesn't exist, create it.
71
- if ( 0 === $term_to_check || null === $term_to_check ) {
72
- $alias_to_use = apply_filters( 'ai1ec_ics_import_alias', $term );
73
- // the filter will either return null, the term_id to use or the original $term
74
- // if the filter is not run. Thus in need to check that $term !== $alias_to_use
75
- if ( $alias_to_use && $alias_to_use !== $term ) {
76
- $to_return['term_id'] = (int) $alias_to_use;
77
- // check that the term matches the taxonomy
78
- $tax = $this->get_taxonomy_for_term_id( term_exists( (int) $alias_to_use ) );
79
- $to_return['taxonomy'] = $tax->taxonomy;
80
- } else {
81
- $term_to_check = wp_insert_term( $term, $taxonomy, $attrs );
82
- if ( is_wp_error( $term_to_check ) ) {
83
- return false;
84
- }
85
- $term_to_check = (object)$term_to_check;
86
- $to_return['term_id'] = (int)$term_to_check->term_id;
87
- }
88
- } else {
89
- $term_id = is_array( $term_to_check )
90
- ? $term_to_check['term_id']
91
- : $term_to_check;
92
- $to_return['term_id'] = (int)$term_id;
93
- // when importing categories, use the mapping of the current site
94
- // so place the term in the current taxonomy
95
- if ( self::CATEGORIES === $taxonomy ) {
96
- // check that the term matches the taxonomy
97
- $tax = $this->get_taxonomy_for_term_id( $term_id );
98
- $to_return['taxonomy'] = $tax->taxonomy;
99
- }
100
 
101
- }
102
- return $to_return;
103
- }
104
 
105
- /**
106
- * Wrapper for terms setting to post.
107
- *
108
- * @param array $terms List of terms to set.
109
- * @param string $taxonomy Name of taxonomy to set terms to.
110
- * @param bool $append When true post may have multiple same instances.
111
- *
112
- * @return bool Success.
113
- */
114
- public function set_terms( array $terms, $taxonomy, $append = false ) {
115
- $result = wp_set_post_terms(
116
- $this->_post_id,
117
- $terms,
118
- $taxonomy,
119
- $append
120
- );
121
- if ( is_wp_error( $result ) ) {
122
- return false;
123
- }
124
- return $result;
125
- }
126
 
127
- /**
128
- * Update event categories.
129
- *
130
- * @param array $categories List of category IDs.
131
- *
132
- * @return bool Success.
133
- */
134
- public function set_categories( array $categories ) {
135
- return $this->set_terms( $categories, self::CATEGORIES );
136
- }
137
 
138
- /**
139
- * Update event tags.
140
- *
141
- * @param array $tags List of tag IDs.
142
- *
143
- * @return bool Success.
144
- */
145
- public function set_tags( array $tags ) {
146
- return $this->set_terms( $tags, self::TAGS );
147
- }
148
 
149
- /**
150
- * Update event feed description.
151
- *
152
- * @param object $feed Feed object.
153
- *
154
- * @return bool Success.
155
- */
156
- public function set_feed( $feed ) {
157
- $feed_name = $feed->feed_url;
158
- // If the feed is not from an imported file, parse the url.
159
- if ( ! isset( $feed->feed_imported_file ) ) {
160
- $url_components = parse_url( $feed->feed_url );
161
- $feed_name = $url_components['host'];
162
- }
163
- $term = $this->initiate_term(
164
- $feed_name,
165
- self::FEEDS,
166
- false,
167
- array(
168
- 'description' => $feed->feed_url,
169
- )
170
- );
171
- if ( false === $term ) {
172
- return false;
173
- }
174
- $term_id = $term['term_id'];
175
- return $this->set_terms( array( $term_id ), self::FEEDS );
176
- }
177
 
178
- /**
179
- * Get the taxonomy name from term id
180
- *
181
- * @param int $term
182
- *
183
- * @return stdClass The taxonomy nane
184
- */
185
- public function get_taxonomy_for_term_id( $term_id ) {
186
- $db = $this->_registry->get( 'dbi.dbi' );
187
- return $db->get_row(
188
- $db->prepare(
189
- 'SELECT terms_taxonomy.taxonomy FROM ' . $db->get_table_name( 'terms' ) .
190
- ' AS terms INNER JOIN ' .
191
- $db->get_table_name( 'term_taxonomy' ) .
192
- ' AS terms_taxonomy USING(term_id) '.
193
- 'WHERE terms.term_id = %d LIMIT 1', $term_id )
194
- );
195
- }
196
  }
12
 
13
  class Ai1ec_Event_Taxonomy extends Ai1ec_Base {
14
 
15
+ /**
16
+ * @var string Name of categories taxonomy.
17
+ */
18
+ const CATEGORIES = 'events_categories';
19
 
20
+ /**
21
+ * @var string Name of tags taxonomy.
22
+ */
23
+ const TAGS = 'events_tags';
24
 
25
+ /**
26
+ * @var string Name of feeds taxonomy.
27
+ */
28
+ const FEEDS = 'events_feeds';
29
 
30
+ /**
31
+ * @var int ID of related post object
32
+ */
33
+ protected $_post_id = 0;
34
 
35
+ /**
36
+ * Store event ID in local variable.
37
+ *
38
+ * @param int $post_id ID of post being managed.
39
+ *
40
+ * @return void
41
+ */
42
+ public function __construct( Ai1ec_Registry_Object $registry, $post_id = 0 ) {
43
+ parent::__construct( $registry );
44
+ $this->_post_id = (int)$post_id;
45
+ }
46
 
47
+ /**
48
+ * Get ID of term. Optionally create it if it doesn't exist.
49
+ *
50
+ * @param string $term Name of term to create.
51
+ * @param string $taxonomy Name of taxonomy to contain term within.
52
+ * @param bool $is_id Set to true if $term is ID.
53
+ * @param array $attrs Attributes to creatable entity.
54
+ *
55
+ * @return array|bool Associative array with term_id
56
+ * and taxonomy keys or false on error
57
+ */
58
+ public function initiate_term(
59
+ $term,
60
+ $taxonomy,
61
+ $is_id = false,
62
+ array $attrs = array()
63
+ ) {
64
+ // cast to int to have it working with term_exists
65
+ $term = ( $is_id ) ? (int) $term : $term;
66
+ $term_to_check = term_exists( $term, $taxonomy );
67
+ $to_return = array(
68
+ 'taxonomy' => $taxonomy
69
+ );
70
+ // if term doesn't exist, create it.
71
+ if ( 0 === $term_to_check || null === $term_to_check ) {
72
+ $alias_to_use = apply_filters( 'ai1ec_ics_import_alias', $term );
73
+ // the filter will either return null, the term_id to use or the original $term
74
+ // if the filter is not run. Thus in need to check that $term !== $alias_to_use
75
+ if ( $alias_to_use && $alias_to_use !== $term ) {
76
+ $to_return['term_id'] = (int) $alias_to_use;
77
+ // check that the term matches the taxonomy
78
+ $tax = $this->get_taxonomy_for_term_id( term_exists( (int) $alias_to_use ) );
79
+ $to_return['taxonomy'] = $tax->taxonomy;
80
+ } else {
81
+ $term_to_check = wp_insert_term( $term, $taxonomy, $attrs );
82
+ if ( is_wp_error( $term_to_check ) ) {
83
+ return false;
84
+ }
85
+ $term_to_check = (object)$term_to_check;
86
+ $to_return['term_id'] = (int)$term_to_check->term_id;
87
+ }
88
+ } else {
89
+ $term_id = is_array( $term_to_check )
90
+ ? $term_to_check['term_id']
91
+ : $term_to_check;
92
+ $to_return['term_id'] = (int)$term_id;
93
+ // when importing categories, use the mapping of the current site
94
+ // so place the term in the current taxonomy
95
+ if ( self::CATEGORIES === $taxonomy ) {
96
+ // check that the term matches the taxonomy
97
+ $tax = $this->get_taxonomy_for_term_id( $term_id );
98
+ $to_return['taxonomy'] = $tax->taxonomy;
99
+ }
100
 
101
+ }
102
+ return $to_return;
103
+ }
104
 
105
+ /**
106
+ * Wrapper for terms setting to post.
107
+ *
108
+ * @param array $terms List of terms to set.
109
+ * @param string $taxonomy Name of taxonomy to set terms to.
110
+ * @param bool $append When true post may have multiple same instances.
111
+ *
112
+ * @return bool Success.
113
+ */
114
+ public function set_terms( array $terms, $taxonomy, $append = false ) {
115
+ $result = wp_set_post_terms(
116
+ $this->_post_id,
117
+ $terms,
118
+ $taxonomy,
119
+ $append
120
+ );
121
+ if ( is_wp_error( $result ) ) {
122
+ return false;
123
+ }
124
+ return $result;
125
+ }
126
 
127
+ /**
128
+ * Update event categories.
129
+ *
130
+ * @param array $categories List of category IDs.
131
+ *
132
+ * @return bool Success.
133
+ */
134
+ public function set_categories( array $categories ) {
135
+ return $this->set_terms( $categories, self::CATEGORIES );
136
+ }
137
 
138
+ /**
139
+ * Update event tags.
140
+ *
141
+ * @param array $tags List of tag IDs.
142
+ *
143
+ * @return bool Success.
144
+ */
145
+ public function set_tags( array $tags ) {
146
+ return $this->set_terms( $tags, self::TAGS );
147
+ }
148
 
149
+ /**
150
+ * Update event feed description.
151
+ *
152
+ * @param object $feed Feed object.
153
+ *
154
+ * @return bool Success.
155
+ */
156
+ public function set_feed( $feed ) {
157
+ $feed_name = $feed->feed_url;
158
+ // If the feed is not from an imported file, parse the url.
159
+ if ( ! isset( $feed->feed_imported_file ) ) {
160
+ $url_components = parse_url( $feed->feed_url );
161
+ $feed_name = $url_components['host'];
162
+ }
163
+ $term = $this->initiate_term(
164
+ $feed_name,
165
+ self::FEEDS,
166
+ false,
167
+ array(
168
+ 'description' => $feed->feed_url,
169
+ )
170
+ );
171
+ if ( false === $term ) {
172
+ return false;
173
+ }
174
+ $term_id = $term['term_id'];
175
+ return $this->set_terms( array( $term_id ), self::FEEDS );
176
+ }
177
 
178
+ /**
179
+ * Get the taxonomy name from term id
180
+ *
181
+ * @param int $term
182
+ *
183
+ * @return stdClass The taxonomy nane
184
+ */
185
+ public function get_taxonomy_for_term_id( $term_id ) {
186
+ $db = $this->_registry->get( 'dbi.dbi' );
187
+ return $db->get_row(
188
+ $db->prepare(
189
+ 'SELECT terms_taxonomy.taxonomy FROM ' . $db->get_table_name( 'terms' ) .
190
+ ' AS terms INNER JOIN ' .
191
+ $db->get_table_name( 'term_taxonomy' ) .
192
+ ' AS terms_taxonomy USING(term_id) '.
193
+ 'WHERE terms.term_id = %d LIMIT 1', $term_id )
194
+ );
195
+ }
196
  }
app/model/event/trashing.php CHANGED
@@ -15,202 +15,202 @@
15
  */
16
  class Ai1ec_Event_Trashing extends Ai1ec_Base {
17
 
18
- /**
19
- * Trash/untrash/deletes child posts
20
- *
21
- * @param id $post_id
22
- * @param string $action
23
- */
24
- protected function _manage_children( $post_id, $action ) {
25
- try {
26
- $ai1ec_event = $this->_registry->get( 'model.event', $post_id );
27
- if (
28
- $ai1ec_event->get( 'post' ) &&
29
- $ai1ec_event->get( 'recurrence_rules' )
30
- ) {
31
- // when untrashing also get trashed object
32
- $children = $this->_registry->get( 'model.event.parent' )
33
- ->get_child_event_objects( $ai1ec_event->get( 'post_id' ), $action === 'untrash' );
34
- $function = 'wp_' . $action . '_post';
35
- foreach ( $children as $child ) {
36
- $function( $child->get( 'post_id' ) );
37
- }
38
- }
39
- } catch ( Ai1ec_Event_Not_Found_Exception $exception ) {
40
- // ignore - not an event
41
- }
42
- }
43
 
44
- /**
45
- * Trashes child posts
46
- *
47
- * @param int $post_id
48
- */
49
- public function trash_children( $post_id ) {
50
- $this->_manage_children( $post_id, 'trash' );
51
- }
52
 
53
- /**
54
- * Delete child posts
55
- *
56
- * @param int $post_id
57
- */
58
- public function delete_children( $post_id ) {
59
- $this->_manage_children( $post_id, 'delete' );
60
- }
61
 
62
- /**
63
- * Untrashes child posts
64
- *
65
- * @param int $post_id
66
- */
67
- public function untrash_children( $post_id ) {
68
- $this->_manage_children( $post_id, 'untrash' );
69
- }
70
 
71
- /**
72
- * Handle PRE (event) trashing.
73
- *
74
- * @wp_hook trash_post
75
- *
76
- * @param int $post_id ID of post, which was trashed.
77
- *
78
- * @return bool Success.
79
- */
80
- public function trash_post( $post_id ) {
81
- $api = $this->_registry->get( 'model.api.api-ticketing' );
82
- $post = get_post( $post_id );
83
- $restored_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
84
- $fields = array(
85
- 'status' => 'trash'
86
- );
87
- $ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
88
- $message = $api->update_api_event_fields( $post, $fields, 'trash', $ajax );
89
- if ( null !== $message ) {
90
- if ( $ajax ) {
91
- wp_die( $message );
92
- } else {
93
- wp_redirect( $this->get_sendback_page( $post_id ) );
94
- exit();
95
- }
96
- }
97
- return true;
98
- }
99
 
100
- /**
101
- * Handle POST (event) trashing.
102
- *
103
- * @wp_hook trashed_post
104
- *
105
- * @param int $post_id ID of post, which was trashed.
106
- *
107
- * @return bool Success.
108
- */
109
- public function trashed_post( $post_id ) {
110
- return $this->trash_children( $post_id );
111
- }
112
 
113
- private function get_sendback_page( $post_id ) {
114
- $sendback = wp_get_referer();
115
- $page_base = Ai1ec_Wp_Uri_Helper::get_pagebase( $sendback ); //$_SERVER['REQUEST_URI'] );
116
- if ( 'post.php' === $page_base ) {
117
- return get_edit_post_link( $post_id, 'url' );
118
- } else {
119
- return admin_url( 'edit.php?post_type=ai1ec_event' );
120
- }
121
- }
122
 
123
- /**
124
- * Handle PRE (event) untrashing.
125
- *
126
- * @wp_hook untrash_post
127
- *
128
- * @param int $post_id ID of post, which was untrashed.
129
- *
130
- * @return bool Success. Interrupt the action with exit is
131
- * the integration with API fails
132
- */
133
- public function untrash_post ( $post_id ) {
134
- $api = $this->_registry->get( 'model.api.api-ticketing' );
135
- $post = get_post( $post_id );
136
- $restored_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
137
- $fields = array(
138
- 'status' => $restored_status
139
- );
140
- $ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
141
- $message = $api->update_api_event_fields( $post, $fields, 'untrash', $ajax );
142
- if ( null !== $message ) {
143
- if ( $ajax ) {
144
- wp_die( $message );
145
- } else {
146
- wp_redirect( $this->get_sendback_page( $post_id ) );
147
- exit();
148
- }
149
- }
150
- return true;
151
- }
152
 
153
- /**
154
- * Handle POST (event) untrashing.
155
- *
156
- * @wp_hook untrashed_post
157
- *
158
- * @param int $post_id ID of post, which was untrashed.
159
- *
160
- * @return bool Success.
161
- */
162
- public function untrashed_post( $post_id ) {
163
- return $this->untrash_children( $post_id );
164
- }
165
 
166
- /**
167
- * Handle PRE (event) deletion.
168
- *
169
- * Executed before post is deleted, but after meta is removed.
170
- *
171
- * @wp_hook delete_post
172
- *
173
- * @param int $post_id ID of post, which was trashed.
174
- *
175
- * @return bool Success. Interrupt the action with exit is
176
- * the integration with API fails
177
- */
178
  public function before_delete_post( $post_id ) {
179
- $api = $this->_registry->get( 'model.api.api-ticketing' );
180
- $ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
181
- $message = $api->delete_api_event( $post_id, 'delete', $ajax );
182
- if ( null !== $message ) {
183
- if ( $ajax ) {
184
- wp_die( $message );
185
- } else {
186
- wp_redirect( $this->get_sendback_page( $post_id ) );
187
- exit();
188
- }
189
- }
190
- return true;
191
- }
192
 
193
- /**
194
- * Handle POST (event) deletion.
195
- *
196
- * Executed before post is deleted, but after meta is removed.
197
- *
198
- * @wp_hook delete_post
199
- *
200
- * @param int $post_id ID of post, which was trashed.
201
- *
202
- * @return bool Success.
203
- */
204
- public function delete( $post_id ) {
205
- $post_id = (int)$post_id;
206
- $where = array( 'post_id' => (int)$post_id );
207
- $format = array( '%d' );
208
- $dbi = $this->_registry->get( 'dbi.dbi' );
209
- $success = $this->delete_children( $post_id );
210
- $success = $dbi->delete( 'ai1ec_events', $where, $format );
211
- $success = $this->_registry->get( 'model.event.instance' )->clean( $post_id );
212
- unset( $where, $dbi );
213
- return $success;
214
- }
215
 
216
  }
15
  */
16
  class Ai1ec_Event_Trashing extends Ai1ec_Base {
17
 
18
+ /**
19
+ * Trash/untrash/deletes child posts
20
+ *
21
+ * @param id $post_id
22
+ * @param string $action
23
+ */
24
+ protected function _manage_children( $post_id, $action ) {
25
+ try {
26
+ $ai1ec_event = $this->_registry->get( 'model.event', $post_id );
27
+ if (
28
+ $ai1ec_event->get( 'post' ) &&
29
+ $ai1ec_event->get( 'recurrence_rules' )
30
+ ) {
31
+ // when untrashing also get trashed object
32
+ $children = $this->_registry->get( 'model.event.parent' )
33
+ ->get_child_event_objects( $ai1ec_event->get( 'post_id' ), $action === 'untrash' );
34
+ $function = 'wp_' . $action . '_post';
35
+ foreach ( $children as $child ) {
36
+ $function( $child->get( 'post_id' ) );
37
+ }
38
+ }
39
+ } catch ( Ai1ec_Event_Not_Found_Exception $exception ) {
40
+ // ignore - not an event
41
+ }
42
+ }
43
 
44
+ /**
45
+ * Trashes child posts
46
+ *
47
+ * @param int $post_id
48
+ */
49
+ public function trash_children( $post_id ) {
50
+ $this->_manage_children( $post_id, 'trash' );
51
+ }
52
 
53
+ /**
54
+ * Delete child posts
55
+ *
56
+ * @param int $post_id
57
+ */
58
+ public function delete_children( $post_id ) {
59
+ $this->_manage_children( $post_id, 'delete' );
60
+ }
61
 
62
+ /**
63
+ * Untrashes child posts
64
+ *
65
+ * @param int $post_id
66
+ */
67
+ public function untrash_children( $post_id ) {
68
+ $this->_manage_children( $post_id, 'untrash' );
69
+ }
70
 
71
+ /**
72
+ * Handle PRE (event) trashing.
73
+ *
74
+ * @wp_hook trash_post
75
+ *
76
+ * @param int $post_id ID of post, which was trashed.
77
+ *
78
+ * @return bool Success.
79
+ */
80
+ public function trash_post( $post_id ) {
81
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
82
+ $post = get_post( $post_id );
83
+ $restored_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
84
+ $fields = array(
85
+ 'status' => 'trash'
86
+ );
87
+ $ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
88
+ $message = $api->update_api_event_fields( $post, $fields, 'trash', $ajax );
89
+ if ( null !== $message ) {
90
+ if ( $ajax ) {
91
+ wp_die( $message );
92
+ } else {
93
+ wp_redirect( $this->get_sendback_page( $post_id ) );
94
+ exit();
95
+ }
96
+ }
97
+ return true;
98
+ }
99
 
100
+ /**
101
+ * Handle POST (event) trashing.
102
+ *
103
+ * @wp_hook trashed_post
104
+ *
105
+ * @param int $post_id ID of post, which was trashed.
106
+ *
107
+ * @return bool Success.
108
+ */
109
+ public function trashed_post( $post_id ) {
110
+ return $this->trash_children( $post_id );
111
+ }
112
 
113
+ private function get_sendback_page( $post_id ) {
114
+ $sendback = wp_get_referer();
115
+ $page_base = Ai1ec_Wp_Uri_Helper::get_pagebase( $sendback ); //$_SERVER['REQUEST_URI'] );
116
+ if ( 'post.php' === $page_base ) {
117
+ return get_edit_post_link( $post_id, 'url' );
118
+ } else {
119
+ return admin_url( 'edit.php?post_type=ai1ec_event' );
120
+ }
121
+ }
122
 
123
+ /**
124
+ * Handle PRE (event) untrashing.
125
+ *
126
+ * @wp_hook untrash_post
127
+ *
128
+ * @param int $post_id ID of post, which was untrashed.
129
+ *
130
+ * @return bool Success. Interrupt the action with exit is
131
+ * the integration with API fails
132
+ */
133
+ public function untrash_post ( $post_id ) {
134
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
135
+ $post = get_post( $post_id );
136
+ $restored_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
137
+ $fields = array(
138
+ 'status' => $restored_status
139
+ );
140
+ $ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
141
+ $message = $api->update_api_event_fields( $post, $fields, 'untrash', $ajax );
142
+ if ( null !== $message ) {
143
+ if ( $ajax ) {
144
+ wp_die( $message );
145
+ } else {
146
+ wp_redirect( $this->get_sendback_page( $post_id ) );
147
+ exit();
148
+ }
149
+ }
150
+ return true;
151
+ }
152
 
153
+ /**
154
+ * Handle POST (event) untrashing.
155
+ *
156
+ * @wp_hook untrashed_post
157
+ *
158
+ * @param int $post_id ID of post, which was untrashed.
159
+ *
160
+ * @return bool Success.
161
+ */
162
+ public function untrashed_post( $post_id ) {
163
+ return $this->untrash_children( $post_id );
164
+ }
165
 
166
+ /**
167
+ * Handle PRE (event) deletion.
168
+ *
169
+ * Executed before post is deleted, but after meta is removed.
170
+ *
171
+ * @wp_hook delete_post
172
+ *
173
+ * @param int $post_id ID of post, which was trashed.
174
+ *
175
+ * @return bool Success. Interrupt the action with exit is
176
+ * the integration with API fails
177
+ */
178
  public function before_delete_post( $post_id ) {
179
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
180
+ $ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
181
+ $message = $api->delete_api_event( $post_id, 'delete', $ajax );
182
+ if ( null !== $message ) {
183
+ if ( $ajax ) {
184
+ wp_die( $message );
185
+ } else {
186
+ wp_redirect( $this->get_sendback_page( $post_id ) );
187
+ exit();
188
+ }
189
+ }
190
+ return true;
191
+ }
192
 
193
+ /**
194
+ * Handle POST (event) deletion.
195
+ *
196
+ * Executed before post is deleted, but after meta is removed.
197
+ *
198
+ * @wp_hook delete_post
199
+ *
200
+ * @param int $post_id ID of post, which was trashed.
201
+ *
202
+ * @return bool Success.
203
+ */
204
+ public function delete( $post_id ) {
205
+ $post_id = (int)$post_id;
206
+ $where = array( 'post_id' => (int)$post_id );
207
+ $format = array( '%d' );
208
+ $dbi = $this->_registry->get( 'dbi.dbi' );
209
+ $success = $this->delete_children( $post_id );
210
+ $success = $dbi->delete( 'ai1ec_events', $where, $format );
211
+ $success = $this->_registry->get( 'model.event.instance' )->clean( $post_id );
212
+ unset( $where, $dbi );
213
+ return $success;
214
+ }
215
 
216
  }
app/model/filter/auth_ids.php CHANGED
@@ -11,8 +11,8 @@
11
  */
12
  class Ai1ec_Filter_Authors extends Ai1ec_Filter_Int {
13
 
14
- public function get_field() {
15
- return 'p.post_author';
16
- }
17
 
18
  }
11
  */
12
  class Ai1ec_Filter_Authors extends Ai1ec_Filter_Int {
13
 
14
+ public function get_field() {
15
+ return 'p.post_author';
16
+ }
17
 
18
  }
app/model/filter/cat_ids.php CHANGED
@@ -11,8 +11,8 @@
11
  */
12
  class Ai1ec_Filter_Categories extends Ai1ec_Filter_Taxonomy {
13
 
14
- public function get_taxonomy() {
15
- return 'events_categories';
16
- }
17
 
18
  }
11
  */
12
  class Ai1ec_Filter_Categories extends Ai1ec_Filter_Taxonomy {
13
 
14
+ public function get_taxonomy() {
15
+ return 'events_categories';
16
+ }
17
 
18
  }
app/model/filter/instance_ids.php CHANGED
@@ -11,8 +11,8 @@
11
  */
12
  class Ai1ec_Filter_Posts_By_Instance extends Ai1ec_Filter_Int {
13
 
14
- public function get_field() {
15
- return 'i.id';
16
- }
17
 
18
  }
11
  */
12
  class Ai1ec_Filter_Posts_By_Instance extends Ai1ec_Filter_Int {
13
 
14
+ public function get_field() {
15
+ return 'i.id';
16
+ }
17
 
18
  }
app/model/filter/int.php CHANGED
@@ -11,65 +11,65 @@
11
  */
12
  abstract class Ai1ec_Filter_Int implements Ai1ec_Filter_Interface {
13
 
14
- /**
15
- * @var Ai1ec_Registry_Object Injected object registry.
16
- */
17
- protected $_registry = null;
18
 
19
- /**
20
- * @var array Sanitized input values with only positive integers kept.
21
- */
22
- protected $_values = array();
23
 
24
- /**
25
- * Sanitize input values upon construction.
26
- *
27
- * @param Ai1ec_Registry_Object $registry Injected registry.
28
- * @param array $filter_values Values to sanitize.
29
- *
30
- * @return void
31
- */
32
- public function __construct(
33
- Ai1ec_Registry_Object $registry,
34
- array $filter_values = array()
35
- ) {
36
- $this->_registry = $registry;
37
- $this->_values = array_filter(
38
- array_map(
39
- array( $this->_registry->get( 'primitive.int' ), 'positive' ),
40
- $filter_values
41
- )
42
- );
43
- }
44
 
45
- /**
46
- * These simple filters does not require new joins.
47
- *
48
- * @return string Empty string is returned.
49
- */
50
- public function get_join() {
51
- return '';
52
- }
53
 
54
- /**
55
- * Get condition part of query for single field.
56
- *
57
- * @param string $inner_operator Inner logics to use. It is ignored.
58
- *
59
- * @return string Conditional snippet for query.
60
- */
61
- public function get_where( $inner_operator = null ) {
62
- if ( empty( $this->_values ) ) {
63
- return '';
64
- }
65
- return $this->get_field() . ' IN ( ' . join( ',', $this->_values ) . ' )';
66
- }
67
 
68
- /**
69
- * Require ancestors to override this to build correct conditional snippet.
70
- *
71
- * @return string Column alias to use in condition.
72
- */
73
- abstract public function get_field();
74
 
75
  }
11
  */
12
  abstract class Ai1ec_Filter_Int implements Ai1ec_Filter_Interface {
13
 
14
+ /**
15
+ * @var Ai1ec_Registry_Object Injected object registry.
16
+ */
17
+ protected $_registry = null;
18
 
19
+ /**
20
+ * @var array Sanitized input values with only positive integers kept.
21
+ */
22
+ protected $_values = array();
23
 
24
+ /**
25
+ * Sanitize input values upon construction.
26
+ *
27
+ * @param Ai1ec_Registry_Object $registry Injected registry.
28
+ * @param array $filter_values Values to sanitize.
29
+ *
30
+ * @return void
31
+ */
32
+ public function __construct(
33
+ Ai1ec_Registry_Object $registry,
34
+ array $filter_values = array()
35
+ ) {
36
+ $this->_registry = $registry;
37
+ $this->_values = array_filter(
38
+ array_map(
39
+ array( $this->_registry->get( 'primitive.int' ), 'positive' ),
40
+ $filter_values
41
+ )
42
+ );
43
+ }
44
 
45
+ /**
46
+ * These simple filters does not require new joins.
47
+ *
48
+ * @return string Empty string is returned.
49
+ */
50
+ public function get_join() {
51
+ return '';
52
+ }
53
 
54
+ /**
55
+ * Get condition part of query for single field.
56
+ *
57
+ * @param string $inner_operator Inner logics to use. It is ignored.
58
+ *
59
+ * @return string Conditional snippet for query.
60
+ */
61
+ public function get_where( $inner_operator = null ) {
62
+ if ( empty( $this->_values ) ) {
63
+ return '';
64
+ }
65
+ return $this->get_field() . ' IN ( ' . join( ',', $this->_values ) . ' )';
66
+ }
67
 
68
+ /**
69
+ * Require ancestors to override this to build correct conditional snippet.
70
+ *
71
+ * @return string Column alias to use in condition.
72
+ */
73
+ abstract public function get_field();
74
 
75
  }
app/model/filter/interface.php CHANGED
@@ -11,34 +11,34 @@
11
  */
12
  interface Ai1ec_Filter_Interface {
13
 
14
- /**
15
- * Store user-input locally.
16
- *
17
- * @param Ai1ec_Registry_Object $registry Injected registry.
18
- * @param array $filter_values User provided input.
19
- *
20
- * @return void
21
- */
22
- public function __construct(
23
- Ai1ec_Registry_Object $registry,
24
- array $filter_values = array()
25
- );
26
 
27
- /**
28
- * Return SQL snippet for `FROM` part.
29
- *
30
- * @return string Valid SQL snippet for `FROM` part.
31
- */
32
- public function get_join();
33
 
34
- /**
35
- * Return SQL snippet for `WHERE` part.
36
- *
37
- * Snippet should not be put in brackets - this will be performed
38
- * in upper level.
39
- *
40
- * @return string Valid SQL snippet.
41
- */
42
- public function get_where();
43
 
44
  }
11
  */
12
  interface Ai1ec_Filter_Interface {
13
 
14
+ /**
15
+ * Store user-input locally.
16
+ *
17
+ * @param Ai1ec_Registry_Object $registry Injected registry.
18
+ * @param array $filter_values User provided input.
19
+ *
20
+ * @return void
21
+ */
22
+ public function __construct(
23
+ Ai1ec_Registry_Object $registry,
24
+ array $filter_values = array()
25
+ );
26
 
27
+ /**
28
+ * Return SQL snippet for `FROM` part.
29
+ *
30
+ * @return string Valid SQL snippet for `FROM` part.
31
+ */
32
+ public function get_join();
33
 
34
+ /**
35
+ * Return SQL snippet for `WHERE` part.
36
+ *
37
+ * Snippet should not be put in brackets - this will be performed
38
+ * in upper level.
39
+ *
40
+ * @return string Valid SQL snippet.
41
+ */
42
+ public function get_where();
43
 
44
  }
app/model/filter/post_ids.php CHANGED
@@ -11,8 +11,8 @@
11
  */
12
  class Ai1ec_Filter_Posts extends Ai1ec_Filter_Int {
13
 
14
- public function get_field() {
15
- return 'e.post_id';
16
- }
17
 
18
  }
11
  */
12
  class Ai1ec_Filter_Posts extends Ai1ec_Filter_Int {
13
 
14
+ public function get_field() {
15
+ return 'e.post_id';
16
+ }
17
 
18
  }
app/model/filter/tag_ids.php CHANGED
@@ -11,8 +11,8 @@
11
  */
12
  class Ai1ec_Filter_Tags extends Ai1ec_Filter_Taxonomy {
13
 
14
- public function get_taxonomy() {
15
- return 'events_tags';
16
- }
17
 
18
  }
11
  */
12
  class Ai1ec_Filter_Tags extends Ai1ec_Filter_Taxonomy {
13
 
14
+ public function get_taxonomy() {
15
+ return 'events_tags';
16
+ }
17
 
18
  }
app/model/filter/taxonomy.php CHANGED
@@ -11,89 +11,89 @@
11
  */
12
  abstract class Ai1ec_Filter_Taxonomy extends Ai1ec_Filter_Int {
13
 
14
- /**
15
- * @var Ai1ec_Dbi Instance of database interface.
16
- */
17
- protected $_dbi = null;
18
 
19
- /**
20
- * Sanitize input values upon construction.
21
- *
22
- * @param Ai1ec_Registry_Object $registry Injected registry.
23
- * @param array $filter_values Values to sanitize.
24
- *
25
- * @return void
26
- */
27
- public function __construct(
28
- Ai1ec_Registry_Object $registry,
29
- array $filter_values = array()
30
- ) {
31
- parent::__construct( $registry, $filter_values );
32
- $this->_dbi = $this->_registry->get( 'dbi.dbi' );
33
- }
34
 
35
- /**
36
- * Build SQL snippet for `FROM` particle.
37
- *
38
- * @return string Valid SQL snippet.
39
- */
40
- public function get_join() {
41
- if ( empty( $this->_values ) ) {
42
- return '';
43
- }
44
- $sql_query =
45
- 'LEFT JOIN `{{RELATIONSHIPS_TABLE}}` AS `{{RELATIONSHIP_ALIAS}}` ' .
46
- 'ON ( `e` . `post_id` = `{{RELATIONSHIP_ALIAS}}` . `object_id` ) ' .
47
- 'LEFT JOIN `{{TAXONOMY_TABLE}}` AS `{{TAXONOMY_ALIAS}}` ' .
48
- 'ON (' .
49
- '`{{RELATIONSHIP_ALIAS}}` . `term_taxonomy_id` = ' .
50
- '`{{TAXONOMY_ALIAS}}` . `term_taxonomy_id` ' .
51
- 'AND `{{TAXONOMY_ALIAS}}` . taxonomy = {{TAXONOMY}} ' .
52
- ')';
53
- return str_replace(
54
- array(
55
- '{{RELATIONSHIPS_TABLE}}',
56
- '{{RELATIONSHIP_ALIAS}}',
57
- '{{TAXONOMY_TABLE}}',
58
- '{{TAXONOMY_ALIAS}}',
59
- '{{TAXONOMY}}',
60
- ),
61
- array(
62
- $this->_dbi->get_table_name( 'term_relationships' ),
63
- $this->_table_alias( 'term_relationships' ),
64
- $this->_dbi->get_table_name( 'term_taxonomy' ),
65
- $this->_table_alias( 'term_taxonomy' ),
66
- '\'' . addslashes( $this->get_taxonomy() ) . '\'',
67
- ),
68
- $sql_query
69
- );
70
- }
71
 
72
- /**
73
- * Required by parent class. Using internal abstractions.
74
- *
75
- * @return string Field name to use in `WHERE` particle.
76
- */
77
- public function get_field() {
78
- return $this->_table_alias( 'term_taxonomy' ) . '.term_id';
79
- }
80
 
81
- /**
82
- * Return the qualified name for the taxonomy.
83
- *
84
- * @return string Valid taxonomy name (see `term_taxonomy` table).
85
- */
86
- abstract public function get_taxonomy();
87
 
88
- /**
89
- * Generate table alias given taxonomy.
90
- *
91
- * @param string $table Table to generate alias for.
92
- *
93
- * @return string Table alias.
94
- */
95
- protected function _table_alias( $table ) {
96
- return $table . '_' . $this->get_taxonomy();
97
- }
98
 
99
  }
11
  */
12
  abstract class Ai1ec_Filter_Taxonomy extends Ai1ec_Filter_Int {
13
 
14
+ /**
15
+ * @var Ai1ec_Dbi Instance of database interface.
16
+ */
17
+ protected $_dbi = null;
18
 
19
+ /**
20
+ * Sanitize input values upon construction.
21
+ *
22
+ * @param Ai1ec_Registry_Object $registry Injected registry.
23
+ * @param array $filter_values Values to sanitize.
24
+ *
25
+ * @return void
26
+ */
27
+ public function __construct(
28
+ Ai1ec_Registry_Object $registry,
29
+ array $filter_values = array()
30
+ ) {
31
+ parent::__construct( $registry, $filter_values );
32
+ $this->_dbi = $this->_registry->get( 'dbi.dbi' );
33
+ }
34
 
35
+ /**
36
+ * Build SQL snippet for `FROM` particle.
37
+ *
38
+ * @return string Valid SQL snippet.
39
+ */
40
+ public function get_join() {
41
+ if ( empty( $this->_values ) ) {
42
+ return '';
43
+ }
44
+ $sql_query =
45
+ 'LEFT JOIN `{{RELATIONSHIPS_TABLE}}` AS `{{RELATIONSHIP_ALIAS}}` ' .
46
+ 'ON ( `e` . `post_id` = `{{RELATIONSHIP_ALIAS}}` . `object_id` ) ' .
47
+ 'LEFT JOIN `{{TAXONOMY_TABLE}}` AS `{{TAXONOMY_ALIAS}}` ' .
48
+ 'ON (' .
49
+ '`{{RELATIONSHIP_ALIAS}}` . `term_taxonomy_id` = ' .
50
+ '`{{TAXONOMY_ALIAS}}` . `term_taxonomy_id` ' .
51
+ 'AND `{{TAXONOMY_ALIAS}}` . taxonomy = {{TAXONOMY}} ' .
52
+ ')';
53
+ return str_replace(
54
+ array(
55
+ '{{RELATIONSHIPS_TABLE}}',
56
+ '{{RELATIONSHIP_ALIAS}}',
57
+ '{{TAXONOMY_TABLE}}',
58
+ '{{TAXONOMY_ALIAS}}',
59
+ '{{TAXONOMY}}',
60
+ ),
61
+ array(
62
+ $this->_dbi->get_table_name( 'term_relationships' ),
63
+ $this->_table_alias( 'term_relationships' ),
64
+ $this->_dbi->get_table_name( 'term_taxonomy' ),
65
+ $this->_table_alias( 'term_taxonomy' ),
66
+ '\'' . addslashes( $this->get_taxonomy() ) . '\'',
67
+ ),
68
+ $sql_query
69
+ );
70
+ }
71
 
72
+ /**
73
+ * Required by parent class. Using internal abstractions.
74
+ *
75
+ * @return string Field name to use in `WHERE` particle.
76
+ */
77
+ public function get_field() {
78
+ return $this->_table_alias( 'term_taxonomy' ) . '.term_id';
79
+ }
80
 
81
+ /**
82
+ * Return the qualified name for the taxonomy.
83
+ *
84
+ * @return string Valid taxonomy name (see `term_taxonomy` table).
85
+ */
86
+ abstract public function get_taxonomy();
87
 
88
+ /**
89
+ * Generate table alias given taxonomy.
90
+ *
91
+ * @param string $table Table to generate alias for.
92
+ *
93
+ * @return string Table alias.
94
+ */
95
+ protected function _table_alias( $table ) {
96
+ return $table . '_' . $this->get_taxonomy();
97
+ }
98
 
99
  }
app/model/meta-user.php CHANGED
@@ -12,85 +12,85 @@
12
  */
13
  class Ai1ec_Meta_User extends Ai1ec_Meta {
14
 
15
- /**
16
- * Get meta value for current user.
17
- *
18
- * @param string $meta_key Name of meta entry to get for current user.
19
- * @param mixed $default Value to return if no entry found.
20
- *
21
- * @return mixed Current user's option or $default if none found.
22
- */
23
- public function get_current( $meta_key, $default = null ) {
24
- $user_id = 0;
25
- if ( is_callable( 'wp_get_current_user' ) ) {
26
- $user = wp_get_current_user();
27
- $user_id = (int)$user->ID;
28
- unset( $user );
29
- }
30
- if ( $user_id <= 0 ) {
31
- return $default;
32
- }
33
- return $this->get( $user_id, $meta_key, $default );
34
- }
35
 
36
- /**
37
- * user_selected_tz method
38
- *
39
- * Get/set user selected (preferred) timezone.
40
- * If only {@see $user_id} is provided - method acts as getter.
41
- * Otherwise it acts as setter.
42
- *
43
- * @param int $user_id ID of user whose timezone is being checked/changed
44
- * @param string $new_value New timezone string value to set user preferrence
45
- * @param bool $force_update Set to true to force value update instead of add
46
- *
47
- * @return mixed Return value depends on activity:
48
- * - [getter] string User preferred timezone name (might be empty string)
49
- * - [setter] bool Success of preferrence change
50
- */
51
- public function user_selected_tz(
52
- $user_id,
53
- $new_value = NULL,
54
- $force_update = false
55
- ) {
56
- $meta_key = 'ai1ec_timezone';
57
- $user_id = (int)$user_id;
58
- $old_value = $this->get(
59
- $user_id,
60
- $meta_key,
61
- NULL,
62
- true
63
- );
64
- if ( NULL !== $new_value ) {
65
- if ( ! in_array( $new_value, timezone_identifiers_list() ) ) {
66
- return false;
67
- }
68
- $success = false;
69
- if ( true === $force_update || ! empty( $old_value ) ) {
70
- $success = update_user_meta(
71
- $user_id,
72
- $meta_key,
73
- $new_value,
74
- $old_value
75
- );
76
- } else {
77
- $success = add_user_meta(
78
- $user_id,
79
- $meta_key,
80
- $new_value,
81
- true
82
- );
83
- if ( false === $success ) {
84
- return $this->user_selected_tz(
85
- $user_id,
86
- $new_value,
87
- true
88
- );
89
- }
90
- }
91
- return $success;
92
- }
93
- return $old_value;
94
- }
95
 
96
  }
12
  */
13
  class Ai1ec_Meta_User extends Ai1ec_Meta {
14
 
15
+ /**
16
+ * Get meta value for current user.
17
+ *
18
+ * @param string $meta_key Name of meta entry to get for current user.
19
+ * @param mixed $default Value to return if no entry found.
20
+ *
21
+ * @return mixed Current user's option or $default if none found.
22
+ */
23
+ public function get_current( $meta_key, $default = null ) {
24
+ $user_id = 0;
25
+ if ( is_callable( 'wp_get_current_user' ) ) {
26
+ $user = wp_get_current_user();
27
+ $user_id = (int)$user->ID;
28
+ unset( $user );
29
+ }
30
+ if ( $user_id <= 0 ) {
31
+ return $default;
32
+ }
33
+ return $this->get( $user_id, $meta_key, $default );
34
+ }
35
 
36
+ /**
37
+ * user_selected_tz method
38
+ *
39
+ * Get/set user selected (preferred) timezone.
40
+ * If only {@see $user_id} is provided - method acts as getter.
41
+ * Otherwise it acts as setter.
42
+ *
43
+ * @param int $user_id ID of user whose timezone is being checked/changed
44
+ * @param string $new_value New timezone string value to set user preferrence
45
+ * @param bool $force_update Set to true to force value update instead of add
46
+ *
47
+ * @return mixed Return value depends on activity:
48
+ * - [getter] string User preferred timezone name (might be empty string)
49
+ * - [setter] bool Success of preferrence change
50
+ */
51
+ public function user_selected_tz(
52
+ $user_id,
53
+ $new_value = NULL,
54
+ $force_update = false
55
+ ) {
56
+ $meta_key = 'ai1ec_timezone';
57
+ $user_id = (int)$user_id;
58
+ $old_value = $this->get(
59
+ $user_id,
60
+ $meta_key,
61
+ NULL,
62
+ true
63
+ );
64
+ if ( NULL !== $new_value ) {
65
+ if ( ! in_array( $new_value, timezone_identifiers_list() ) ) {
66
+ return false;
67
+ }
68
+ $success = false;
69
+ if ( true === $force_update || ! empty( $old_value ) ) {
70
+ $success = update_user_meta(
71
+ $user_id,
72
+ $meta_key,
73
+ $new_value,
74
+ $old_value
75
+ );
76
+ } else {
77
+ $success = add_user_meta(
78
+ $user_id,
79
+ $meta_key,
80
+ $new_value,
81
+ true
82
+ );
83
+ if ( false === $success ) {
84
+ return $this->user_selected_tz(
85
+ $user_id,
86
+ $new_value,
87
+ true
88
+ );
89
+ }
90
+ }
91
+ return $success;
92
+ }
93
+ return $old_value;
94
+ }
95
 
96
  }
app/model/meta.php CHANGED
@@ -12,183 +12,183 @@
12
  */
13
  abstract class Ai1ec_Meta extends Ai1ec_App {
14
 
15
- /**
16
- * @var string Name of base object for storage.
17
- */
18
- protected $_object = '';
19
 
20
- /**
21
- * @var Ai1ec_Cache_Memory In-memory cache operator.
22
- */
23
- protected $_cache = null;
24
 
25
- /**
26
- * Initialize instance-specific in-memory cache storage.
27
- *
28
- * @return void Method does not return.
29
- */
30
- protected function _initialize() {
31
- $class = get_class( $this );
32
- $this->_object = strtolower(
33
- substr( $class, strlen( __CLASS__ ) + 1 )
34
- );
35
- $this->_cache = $this->_registry->get( 'cache.memory' );
36
- }
37
 
38
- /**
39
- * Create new entry if it does not exist and cache provided value.
40
- *
41
- * @param string $object_id ID of object to store.
42
- * @param string $key Key particle for ID to store.
43
- * @param mixed $value Serializable value to store.
44
- *
45
- * @return bool Success.
46
- */
47
- final public function add( $object_id, $key, $value ) {
48
- if ( ! $this->_add( $object_id, $key, $value ) ) {
49
- return false;
50
- }
51
- $this->_cache->set( $this->_cache_key( $object_id, $key ), $value );
52
- return true;
53
- }
54
 
55
- /**
56
- * Update existing entry and cache it's value.
57
- *
58
- * @param string $object_id ID of object to store.
59
- * @param string $key Key particle for ID to store.
60
- * @param mixed $value Serializable value to store.
61
- *
62
- * @return bool Success.
63
- */
64
- final public function update( $object_id, $key, $value ) {
65
- if ( ! $this->_update( $object_id, $key, $value ) ) {
66
- return false;
67
- }
68
- $this->_cache->set( $this->_cache_key( $object_id, $key ), $value );
69
- return true;
70
- }
71
 
72
- /**
73
- * Get object value - from cache or actual store.
74
- *
75
- * @param string $object_id ID of object to get.
76
- * @param string $key Key particle for ID to get.
77
- * @param mixed $default Value to return if nothing found.
78
- *
79
- * @return mixed Value stored or {$default}.
80
- */
81
- final public function get( $object_id, $key, $default = null ) {
82
- $cache_key = $this->_cache_key( $object_id, $key );
83
- $value = $this->_cache->get( $cache_key, $default );
84
- if ( $default === $value ) {
85
- $value = $this->_get( $object_id, $key );
86
- $this->_cache->set( $cache_key, $value );
87
- }
88
- return $value;
89
- }
90
 
91
- /**
92
- * Create or update an entry cache new value.
93
- *
94
- * @param string $object_id ID of object to store.
95
- * @param string $key Key particle for ID to store.
96
- * @param mixed $value Serializable value to store.
97
- *
98
- * @return bool Success.
99
- */
100
- final public function set( $object_id, $key, $value ) {
101
- if ( ! $this->get( $object_id, $key ) ) {
102
- if ( ! $this->_add( $object_id, $key, $value ) ) {
103
- return false;
104
- }
105
- } else {
106
- if ( ! $this->_update( $object_id, $key, $value ) ) {
107
- return false;
108
- }
109
- }
110
- $this->_cache->set( $this->_cache_key( $object_id, $key ), $value );
111
- return true;
112
- }
113
 
114
- /**
115
- * Remove object entry based on ID and key.
116
- *
117
- * @param string $object_id ID of object to remove.
118
- * @param string $key Key particle for ID to remove.
119
- *
120
- * @return bool Success.
121
- */
122
- final public function delete( $object_id, $key ) {
123
- $this->_cache->delete( $this->_cache_key( $object_id, $key ) );
124
- return $this->_delete( $object_id, $key );
125
- }
126
 
127
- /**
128
- * Get object value from actual store.
129
- *
130
- * @param string $object_id ID of object to get.
131
- * @param string $key Key particle for ID to get.
132
- *
133
- * @return mixed Value as found.
134
- */
135
- protected function _get( $object_id, $key ) {
136
- $function = 'get_' . $this->_object . '_meta';
137
- return $function( $object_id, $key, true );
138
- }
139
 
140
- /**
141
- * Create new entry if it does not exist.
142
- *
143
- * @param string $object_id ID of object to store.
144
- * @param string $key Key particle for ID to store.
145
- * @param mixed $value Serializable value to store.
146
- *
147
- * @return bool Success.
148
- */
149
- protected function _add( $object_id, $key, $value ) {
150
- $function = 'add_' . $this->_object . '_meta';
151
- return $function( $object_id, $key, $value, true );
152
- }
153
 
154
- /**
155
- * Update existing entry.
156
- *
157
- * @param string $object_id ID of object to store.
158
- * @param string $key Key particle for ID to store.
159
- * @param mixed $value Serializable value to store.
160
- *
161
- * @return bool Success.
162
- */
163
- protected function _update( $object_id, $key, $value ) {
164
- $function = 'update_' . $this->_object . '_meta';
165
- return $function( $object_id, $key, $value );
166
- }
167
 
168
- /**
169
- * Remove object entry based on ID and key.
170
- *
171
- * @param string $object_id ID of object to remove.
172
- * @param string $key Key particle for ID to remove.
173
- *
174
- * @return bool Success.
175
- */
176
- protected function _delete( $object_id, $key ) {
177
- $function = 'delete_' . $this->_object . '_meta';
178
- return $function( $object_id, $key );
179
- }
180
 
181
- /**
182
- * Generate key for use with cache engine.
183
- *
184
- * @param string $object_id ID of object.
185
- * @param string $key Key particle for ID.
186
- *
187
- * @return string Single identifier for given keys.
188
- */
189
- protected function _cache_key( $object_id, $key ) {
190
- static $separator = "\0";
191
- return $object_id . $separator . $key;
192
- }
193
 
194
  }
12
  */
13
  abstract class Ai1ec_Meta extends Ai1ec_App {
14
 
15
+ /**
16
+ * @var string Name of base object for storage.
17
+ */
18
+ protected $_object = '';
19
 
20
+ /**
21
+ * @var Ai1ec_Cache_Memory In-memory cache operator.
22
+ */
23
+ protected $_cache = null;
24
 
25
+ /**
26
+ * Initialize instance-specific in-memory cache storage.
27
+ *
28
+ * @return void Method does not return.
29
+ */
30
+ protected function _initialize() {
31
+ $class = get_class( $this );
32
+ $this->_object = strtolower(
33
+ substr( $class, strlen( __CLASS__ ) + 1 )
34
+ );
35
+ $this->_cache = $this->_registry->get( 'cache.memory' );
36
+ }
37
 
38
+ /**
39
+ * Create new entry if it does not exist and cache provided value.
40
+ *
41
+ * @param string $object_id ID of object to store.
42
+ * @param string $key Key particle for ID to store.
43
+ * @param mixed $value Serializable value to store.
44
+ *
45
+ * @return bool Success.
46
+ */
47
+ final public function add( $object_id, $key, $value ) {
48
+ if ( ! $this->_add( $object_id, $key, $value ) ) {
49
+ return false;
50
+ }
51
+ $this->_cache->set( $this->_cache_key( $object_id, $key ), $value );
52
+ return true;
53
+ }
54
 
55
+ /**
56
+ * Update existing entry and cache it's value.
57
+ *
58
+ * @param string $object_id ID of object to store.
59
+ * @param string $key Key particle for ID to store.
60
+ * @param mixed $value Serializable value to store.
61
+ *
62
+ * @return bool Success.
63
+ */
64
+ final public function update( $object_id, $key, $value ) {
65
+ if ( ! $this->_update( $object_id, $key, $value ) ) {
66
+ return false;
67
+ }
68
+ $this->_cache->set( $this->_cache_key( $object_id, $key ), $value );
69
+ return true;
70
+ }
71
 
72
+ /**
73
+ * Get object value - from cache or actual store.
74
+ *
75
+ * @param string $object_id ID of object to get.
76
+ * @param string $key Key particle for ID to get.
77
+ * @param mixed $default Value to return if nothing found.
78
+ *
79
+ * @return mixed Value stored or {$default}.
80
+ */
81
+ final public function get( $object_id, $key, $default = null ) {
82
+ $cache_key = $this->_cache_key( $object_id, $key );
83
+ $value = $this->_cache->get( $cache_key, $default );
84
+ if ( $default === $value ) {
85
+ $value = $this->_get( $object_id, $key );
86
+ $this->_cache->set( $cache_key, $value );
87
+ }
88
+ return $value;
89
+ }
90
 
91
+ /**
92
+ * Create or update an entry cache new value.
93
+ *
94
+ * @param string $object_id ID of object to store.
95
+ * @param string $key Key particle for ID to store.
96
+ * @param mixed $value Serializable value to store.
97
+ *
98
+ * @return bool Success.
99
+ */
100
+ final public function set( $object_id, $key, $value ) {
101
+ if ( ! $this->get( $object_id, $key ) ) {
102
+ if ( ! $this->_add( $object_id, $key, $value ) ) {
103
+ return false;
104
+ }
105
+ } else {
106
+ if ( ! $this->_update( $object_id, $key, $value ) ) {
107
+ return false;
108
+ }
109
+ }
110
+ $this->_cache->set( $this->_cache_key( $object_id, $key ), $value );
111
+ return true;
112
+ }
113
 
114
+ /**
115
+ * Remove object entry based on ID and key.
116
+ *
117
+ * @param string $object_id ID of object to remove.
118
+ * @param string $key Key particle for ID to remove.
119
+ *
120
+ * @return bool Success.
121
+ */
122
+ final public function delete( $object_id, $key ) {
123
+ $this->_cache->delete( $this->_cache_key( $object_id, $key ) );
124
+ return $this->_delete( $object_id, $key );
125
+ }
126
 
127
+ /**
128
+ * Get object value from actual store.
129
+ *
130
+ * @param string $object_id ID of object to get.
131
+ * @param string $key Key particle for ID to get.
132
+ *
133
+ * @return mixed Value as found.
134
+ */
135
+ protected function _get( $object_id, $key ) {
136
+ $function = 'get_' . $this->_object . '_meta';
137
+ return $function( $object_id, $key, true );
138
+ }
139
 
140
+ /**
141
+ * Create new entry if it does not exist.
142
+ *
143
+ * @param string $object_id ID of object to store.
144
+ * @param string $key Key particle for ID to store.
145
+ * @param mixed $value Serializable value to store.
146
+ *
147
+ * @return bool Success.
148
+ */
149
+ protected function _add( $object_id, $key, $value ) {
150
+ $function = 'add_' . $this->_object . '_meta';
151
+ return $function( $object_id, $key, $value, true );
152
+ }
153
 
154
+ /**
155
+ * Update existing entry.
156
+ *
157
+ * @param string $object_id ID of object to store.
158
+ * @param string $key Key particle for ID to store.
159
+ * @param mixed $value Serializable value to store.
160
+ *
161
+ * @return bool Success.
162
+ */
163
+ protected function _update( $object_id, $key, $value ) {
164
+ $function = 'update_' . $this->_object . '_meta';
165
+ return $function( $object_id, $key, $value );
166
+ }
167
 
168
+ /**
169
+ * Remove object entry based on ID and key.
170
+ *
171
+ * @param string $object_id ID of object to remove.
172
+ * @param string $key Key particle for ID to remove.
173
+ *
174
+ * @return bool Success.
175
+ */
176
+ protected function _delete( $object_id, $key ) {
177
+ $function = 'delete_' . $this->_object . '_meta';
178
+ return $function( $object_id, $key );
179
+ }
180
 
181
+ /**
182
+ * Generate key for use with cache engine.
183
+ *
184
+ * @param string $object_id ID of object.
185
+ * @param string $key Key particle for ID.
186
+ *
187
+ * @return string Single identifier for given keys.
188
+ */
189
+ protected function _cache_key( $object_id, $key ) {
190
+ static $separator = "\0";
191
+ return $object_id . $separator . $key;
192
+ }
193
 
194
  }
app/model/option.php CHANGED
@@ -10,111 +10,111 @@
10
  */
11
  class Ai1ec_Option extends Ai1ec_App {
12
 
13
- /**
14
- * @var Ai1ec_Cache_Memory In-memory cache storage engine for fast access.
15
- */
16
- protected $_cache = null;
17
 
18
- /**
19
- * @var Ai1ec_Registry_Object instance of the registry object.
20
- */
21
- protected $_registry;
22
 
23
- /**
24
- * Add cache instance to object scope.
25
- *
26
- * @param Ai1ec_Registry_Object $registry Registry object.
27
- *
28
- * @return Ai1ec_Option
29
- */
30
- public function __construct( Ai1ec_Registry_Object $registry ) {
31
- $this->_registry = $registry;
32
- $this->_cache = $registry->get( 'cache.memory' );
33
- }
34
 
35
- /**
36
- * Create an option if it does not exist.
37
- *
38
- * @param string $name Key to put value under.
39
- * @param mixed $value Value to put to storage.
40
- * @param bool $autoload Set to true to load on start.
41
- *
42
- * @return bool Success.
43
- */
44
- public function add( $name, $value, $autoload = false ) {
45
- $autoload = $this->_parse_autoload( $autoload );
46
- if ( ! add_option( $name, $value, '', $autoload ) ) {
47
- return false;
48
- }
49
- $this->_cache->set( $name, $value );
50
- return true;
51
- }
52
 
53
- /**
54
- * Create an option if it does not exist, or update existing.
55
- *
56
- * @param string $name Key to put value under.
57
- * @param mixed $value Value to put to storage.
58
- * @param bool $autoload Set to true to load on start.
59
- *
60
- * @return bool Success.
61
- */
62
- public function set( $name, $value, $autoload = false ) {
63
- $comparator = "\0t\0";
64
- if ( $this->get( $name, $comparator ) === $comparator ) {
65
- return $this->add( $name, $value, $autoload );
66
- }
67
- if ( ! update_option( $name, $value ) ) {
68
- return false;
69
- }
70
- $this->_cache->set( $name, $value );
71
- return true;
72
- }
73
 
74
- /**
75
- * Get a value from storage.
76
- *
77
- * @param string $name Key to retrieve.
78
- * @param mixed $default Value to return if key was not set previously.
79
- *
80
- * @return mixed Value from storage or {$default}.
81
- */
82
- public function get( $name, $default = null ) {
83
- $value = $this->_cache->get( $name, $default );
84
- if ( $default === $value ) {
85
- $value = get_option( $name, $default );
86
- $this->_cache->set( $name, $value );
87
- }
88
- return $value;
89
- }
90
 
91
- /**
92
- * Delete value from storage.
93
- *
94
- * @param string $name Key to delete.
95
- *
96
- * @wp_hook deleted_option Fire after deletion.
97
- *
98
- * @return bool Success.
99
- */
100
- public function delete( $name ) {
101
- $this->_cache->delete( $name );
102
- if ( 'deleted_option' === current_filter() ) {
103
- return true; // avoid loops
104
- }
105
- return delete_option( $name );
106
- }
107
 
108
 
109
- /**
110
- * Convert autoload flag input to value recognized by WordPress.
111
- *
112
- * @param bool $input Autoload flag value.
113
- *
114
- * @return string Autoload identifier.
115
- */
116
- protected function _parse_autoload( $input ) {
117
- return $input ? 'yes' : 'no';
118
- }
119
 
120
  }
10
  */
11
  class Ai1ec_Option extends Ai1ec_App {
12
 
13
+ /**
14
+ * @var Ai1ec_Cache_Memory In-memory cache storage engine for fast access.
15
+ */
16
+ protected $_cache = null;
17
 
18
+ /**
19
+ * @var Ai1ec_Registry_Object instance of the registry object.
20
+ */
21
+ protected $_registry;
22
 
23
+ /**
24
+ * Add cache instance to object scope.
25
+ *
26
+ * @param Ai1ec_Registry_Object $registry Registry object.
27
+ *
28
+ * @return Ai1ec_Option
29
+ */
30
+ public function __construct( Ai1ec_Registry_Object $registry ) {
31
+ $this->_registry = $registry;
32
+ $this->_cache = $registry->get( 'cache.memory' );
33
+ }
34
 
35
+ /**
36
+ * Create an option if it does not exist.
37
+ *
38
+ * @param string $name Key to put value under.
39
+ * @param mixed $value Value to put to storage.
40
+ * @param bool $autoload Set to true to load on start.
41
+ *
42
+ * @return bool Success.
43
+ */
44
+ public function add( $name, $value, $autoload = false ) {
45
+ $autoload = $this->_parse_autoload( $autoload );
46
+ if ( ! add_option( $name, $value, '', $autoload ) ) {
47
+ return false;
48
+ }
49
+ $this->_cache->set( $name, $value );
50
+ return true;
51
+ }
52
 
53
+ /**
54
+ * Create an option if it does not exist, or update existing.
55
+ *
56
+ * @param string $name Key to put value under.
57
+ * @param mixed $value Value to put to storage.
58
+ * @param bool $autoload Set to true to load on start.
59
+ *
60
+ * @return bool Success.
61
+ */
62
+ public function set( $name, $value, $autoload = false ) {
63
+ $comparator = "\0t\0";
64
+ if ( $this->get( $name, $comparator ) === $comparator ) {
65
+ return $this->add( $name, $value, $autoload );
66
+ }
67
+ if ( ! update_option( $name, $value ) ) {
68
+ return false;
69
+ }
70
+ $this->_cache->set( $name, $value );
71
+ return true;
72
+ }
73
 
74
+ /**
75
+ * Get a value from storage.
76
+ *
77
+ * @param string $name Key to retrieve.
78
+ * @param mixed $default Value to return if key was not set previously.
79
+ *
80
+ * @return mixed Value from storage or {$default}.
81
+ */
82
+ public function get( $name, $default = null ) {
83
+ $value = $this->_cache->get( $name, $default );
84
+ if ( $default === $value ) {
85
+ $value = get_option( $name, $default );
86
+ $this->_cache->set( $name, $value );
87
+ }
88
+ return $value;
89
+ }
90
 
91
+ /**
92
+ * Delete value from storage.
93
+ *
94
+ * @param string $name Key to delete.
95
+ *
96
+ * @wp_hook deleted_option Fire after deletion.
97
+ *
98
+ * @return bool Success.
99
+ */
100
+ public function delete( $name ) {
101
+ $this->_cache->delete( $name );
102
+ if ( 'deleted_option' === current_filter() ) {
103
+ return true; // avoid loops
104
+ }
105
+ return delete_option( $name );
106
+ }
107
 
108
 
109
+ /**
110
+ * Convert autoload flag input to value recognized by WordPress.
111
+ *
112
+ * @param bool $input Autoload flag value.
113
+ *
114
+ * @return string Autoload identifier.
115
+ */
116
+ protected function _parse_autoload( $input ) {
117
+ return $input ? 'yes' : 'no';
118
+ }
119
 
120
  }
app/model/review.php CHANGED
@@ -3,193 +3,193 @@
3
  /**
4
  * Class to manage the review made by the user.
5
  * The data are saved inside the wp_option table
6
- *
7
  * @author Time.ly Network Inc.
8
  *
9
  * @package AI1ECCFG
10
  */
11
  class Ai1ec_Review extends Ai1ec_Base {
12
 
13
- const EMAIL_FEEDBACK_DESTINATION = 'info@time.ly';
14
- const OPTION_KEY = '_ai1ec_review';
15
- const FEEDBACK_FIELD = 'feedback';
16
- const RELEASE_DATE_FIELD = 'release_date';
17
- const PUBLISHED_THRESHOLD = 15;
18
- const FUTURE_EVENTS_THRESHOLD = 3;
19
- const WEEK_OFFSET_SEC_FIELD = 604800; //1 week in seconds (7 * 86400)
20
 
21
- /**
22
- * review colletion.
23
- *
24
- * @var array
25
- */
26
- protected $_review_items = array();
27
 
28
- /**
29
- * Option class.
30
- *
31
- * @var Ai1ec_Option
32
- */
33
- protected $_option;
34
 
35
- public function __construct( Ai1ec_Registry_Object $registry ) {
36
- parent::__construct( $registry );
37
- $this->_option = $registry->get( 'model.option' );
38
- $this->_review_items = $this->_get_array( self::OPTION_KEY );
39
- }
40
 
41
- private function _get_array( $option_key ) {
42
- $items = $this->_option->get( $option_key );
43
- if ( ! is_array( $items ) ) {
44
- $items = array();
45
- }
46
- return $items;
47
- }
48
 
49
- private function _get_field( $field_name, $default_value ) {
50
- if ( isset( $this->_review_items[$field_name] ) ) {
51
- $value = $this->_review_items[$field_name];
52
- if ( ai1ec_is_blank( $value ) ) {
53
- return $default_value;
54
- } else {
55
- return $value;
56
- }
57
- } else {
58
- return $default_value;
59
- }
60
- }
61
 
62
- protected function _save( array $values ) {
63
- foreach ($values as $key => $value) {
64
- $this->_review_items[$key] = $value;
65
- }
66
- $this->_option->set( self::OPTION_KEY, $this->_review_items );
67
- return true;
68
- }
69
 
70
- protected function _is_show_box_review() {
71
- //only show for admins
72
- if ( false === is_admin() ) {
73
- return false;
74
- }
75
- $user_id = get_current_user_id();
76
- if ( empty( $user_id ) ) {
77
- return false;
78
- }
79
- //if the user already gave his feedback does not ask him again
80
- if ( $this->_has_feedback( $user_id ) ) {
81
- return false;
82
- }
83
- $release_date_str = $this->_get_field( self::RELEASE_DATE_FIELD, '' );
84
- if ( ai1ec_is_blank( $release_date_str ) ) {
85
- //the first time this page is loaded is save the moment as the release date
86
- //to just ask the user a review after 2 weeks
87
- $this->_save( array(
88
- self::RELEASE_DATE_FIELD => $this->_registry->get( 'date.time' )->format()
89
- ) );
90
- return false;
91
- } else {
92
- $current_time = $this->_registry->get( 'date.time' );
93
- $release_date = $this->_registry->get( 'date.time', $release_date_str );
94
- $diff_sec = $release_date->diff_sec( $current_time );
95
- //verify is passed 2 weeks after we release this feature
96
- if ( $diff_sec < self::WEEK_OFFSET_SEC_FIELD ) {
97
- return false;
98
- }
99
- }
100
- //count the published events
101
- $event_count = count_user_posts( $user_id, AI1EC_POST_TYPE, true );
102
- if ( $event_count < self::PUBLISHED_THRESHOLD ) {
103
- return false;
104
- }
105
- //count the future events
106
- $count_future_events = apply_filters( 'ai1ec_count_future_events', $user_id );
107
- if ( $count_future_events < self::FUTURE_EVENTS_THRESHOLD ) {
108
- return false;
109
- }
110
- return true;
111
- }
112
 
113
- public function get_content( $theme_loader ) {
114
- if ( $this->_is_show_box_review() ) {
115
- $current_user = wp_get_current_user();
116
  $review_args = array();
117
  if ( $current_user instanceof WP_User ) {
118
  $review_args['contact_name'] = $current_user->display_name;
119
  $review_args['contact_email'] = $current_user->user_email;
120
  } else {
121
- $review_args['contact_name'] = '';
122
- $review_args['contact_email'] = '';
123
  }
124
  $review_args['site_url'] = get_option( 'siteurl' );
125
- $theme_loader = $this->_registry->get( 'theme.loader' );
126
- return $theme_loader->get_file( 'box_ask_customer_review.php', $review_args, true )->get_content();
127
- } else {
128
- return null;
129
- }
130
- }
131
 
132
- public function save_feedback_review() {
133
- $user_id = get_current_user_id();
134
- if ( empty( $user_id ) ) {
135
- throw new Exception( 'User not identified' );
136
- }
137
- if ( ai1ec_is_blank( $_POST['feedback'] ) ||
138
- !in_array( $_POST['feedback'], array( 'y', 'n' ) ) ) {
139
- throw new Exception( 'The field is not filled or invalid' );
140
- }
141
- $values = $this->_get_field( self::FEEDBACK_FIELD, null );
142
- if ( null === $values ) {
143
- $values = array();
144
- }
145
- $values[ $user_id ] = $_POST['feedback'];
146
- $this->_save( array(
147
- self::FEEDBACK_FIELD => $values
148
- ) );
149
- }
150
 
151
- protected function _has_feedback( $user_id ) {
152
- $values = $this->_get_field( self::FEEDBACK_FIELD, null );
153
- if ( null === $values ) {
154
- return false;
155
- }
156
- $user = (string) $user_id;
157
- $value = isset( $values[$user] ) ? $values[$user] : '';
158
- return 0 === strcasecmp( 'y', $value ) || 0 === strcasecmp( 'n', $value );
159
- }
160
 
161
- public function send_feedback_message() {
162
- if ( ai1ec_is_blank( $_POST['name'] ) ||
163
- ai1ec_is_blank( $_POST['email'] ) ||
164
- ai1ec_is_blank( $_POST['site'] ) ||
165
- ai1ec_is_blank( $_POST['message'] )
166
- ) {
167
- throw new Exception( 'All fields are required' );
168
- }
169
- $subject = __( 'Feedback provided by user', AI1EC_PLUGIN_NAME );
170
- $content = sprintf( '<b>%s:</b><br/>%s<br/><br/><b>%s:</b><br/>%s<br/><br/><b>%s:</b><br/>%s<br/><br/><b>%s:</b><br/>%s',
171
- __( 'Name', AI1EC_PLUGIN_NAME ),
172
- $_POST['name'],
173
- __( 'E-mail', AI1EC_PLUGIN_NAME ),
174
- $_POST['email'],
175
- __( 'Site URL', AI1EC_PLUGIN_NAME ),
176
- $_POST['site'],
177
- __( 'Message', AI1EC_PLUGIN_NAME ),
178
- nl2br( $_POST['message'] )
179
- );
180
- $dispatcher = $this->_registry->get(
181
- 'notification.email',
182
- $content,
183
- explode( ',', self::EMAIL_FEEDBACK_DESTINATION ),
184
- $subject
185
- );
186
- $headers = array(
187
- 'Content-type: text/html',
188
- sprintf( 'From: %s <%s>', $_POST['name'], $_POST['email'])
189
- );
190
- if ( $dispatcher->send( $headers ) ) {
191
- $_POST['feedback'] = 'n';
192
- $this->save_feedback_review();
193
- }
194
- }
195
  }
3
  /**
4
  * Class to manage the review made by the user.
5
  * The data are saved inside the wp_option table
6
+ *
7
  * @author Time.ly Network Inc.
8
  *
9
  * @package AI1ECCFG
10
  */
11
  class Ai1ec_Review extends Ai1ec_Base {
12
 
13
+ const EMAIL_FEEDBACK_DESTINATION = 'info@time.ly';
14
+ const OPTION_KEY = '_ai1ec_review';
15
+ const FEEDBACK_FIELD = 'feedback';
16
+ const RELEASE_DATE_FIELD = 'release_date';
17
+ const PUBLISHED_THRESHOLD = 15;
18
+ const FUTURE_EVENTS_THRESHOLD = 3;
19
+ const WEEK_OFFSET_SEC_FIELD = 604800; //1 week in seconds (7 * 86400)
20
 
21
+ /**
22
+ * review colletion.
23
+ *
24
+ * @var array
25
+ */
26
+ protected $_review_items = array();
27
 
28
+ /**
29
+ * Option class.
30
+ *
31
+ * @var Ai1ec_Option
32
+ */
33
+ protected $_option;
34
 
35
+ public function __construct( Ai1ec_Registry_Object $registry ) {
36
+ parent::__construct( $registry );
37
+ $this->_option = $registry->get( 'model.option' );
38
+ $this->_review_items = $this->_get_array( self::OPTION_KEY );
39
+ }
40
 
41
+ private function _get_array( $option_key ) {
42
+ $items = $this->_option->get( $option_key );
43
+ if ( ! is_array( $items ) ) {
44
+ $items = array();
45
+ }
46
+ return $items;
47
+ }
48
 
49
+ private function _get_field( $field_name, $default_value ) {
50
+ if ( isset( $this->_review_items[$field_name] ) ) {
51
+ $value = $this->_review_items[$field_name];
52
+ if ( ai1ec_is_blank( $value ) ) {
53
+ return $default_value;
54
+ } else {
55
+ return $value;
56
+ }
57
+ } else {
58
+ return $default_value;
59
+ }
60
+ }
61
 
62
+ protected function _save( array $values ) {
63
+ foreach ($values as $key => $value) {
64
+ $this->_review_items[$key] = $value;
65
+ }
66
+ $this->_option->set( self::OPTION_KEY, $this->_review_items );
67
+ return true;
68
+ }
69
 
70
+ protected function _is_show_box_review() {
71
+ //only show for admins
72
+ if ( false === is_admin() ) {
73
+ return false;
74
+ }
75
+ $user_id = get_current_user_id();
76
+ if ( empty( $user_id ) ) {
77
+ return false;
78
+ }
79
+ //if the user already gave his feedback does not ask him again
80
+ if ( $this->_has_feedback( $user_id ) ) {
81
+ return false;
82
+ }
83
+ $release_date_str = $this->_get_field( self::RELEASE_DATE_FIELD, '' );
84
+ if ( ai1ec_is_blank( $release_date_str ) ) {
85
+ //the first time this page is loaded is save the moment as the release date
86
+ //to just ask the user a review after 2 weeks
87
+ $this->_save( array(
88
+ self::RELEASE_DATE_FIELD => $this->_registry->get( 'date.time' )->format()
89
+ ) );
90
+ return false;
91
+ } else {
92
+ $current_time = $this->_registry->get( 'date.time' );
93
+ $release_date = $this->_registry->get( 'date.time', $release_date_str );
94
+ $diff_sec = $release_date->diff_sec( $current_time );
95
+ //verify is passed 2 weeks after we release this feature
96
+ if ( $diff_sec < self::WEEK_OFFSET_SEC_FIELD ) {
97
+ return false;
98
+ }
99
+ }
100
+ //count the published events
101
+ $event_count = count_user_posts( $user_id, AI1EC_POST_TYPE, true );
102
+ if ( $event_count < self::PUBLISHED_THRESHOLD ) {
103
+ return false;
104
+ }
105
+ //count the future events
106
+ $count_future_events = apply_filters( 'ai1ec_count_future_events', $user_id );
107
+ if ( $count_future_events < self::FUTURE_EVENTS_THRESHOLD ) {
108
+ return false;
109
+ }
110
+ return true;
111
+ }
112
 
113
+ public function get_content( $theme_loader ) {
114
+ if ( $this->_is_show_box_review() ) {
115
+ $current_user = wp_get_current_user();
116
  $review_args = array();
117
  if ( $current_user instanceof WP_User ) {
118
  $review_args['contact_name'] = $current_user->display_name;
119
  $review_args['contact_email'] = $current_user->user_email;
120
  } else {
121
+ $review_args['contact_name'] = '';
122
+ $review_args['contact_email'] = '';
123
  }
124
  $review_args['site_url'] = get_option( 'siteurl' );
125
+ $theme_loader = $this->_registry->get( 'theme.loader' );
126
+ return $theme_loader->get_file( 'box_ask_customer_review.php', $review_args, true )->get_content();
127
+ } else {
128
+ return null;
129
+ }
130
+ }
131
 
132
+ public function save_feedback_review() {
133
+ $user_id = get_current_user_id();
134
+ if ( empty( $user_id ) ) {
135
+ throw new Exception( 'User not identified' );
136
+ }
137
+ if ( ai1ec_is_blank( $_POST['feedback'] ) ||
138
+ !in_array( $_POST['feedback'], array( 'y', 'n' ) ) ) {
139
+ throw new Exception( 'The field is not filled or invalid' );
140
+ }
141
+ $values = $this->_get_field( self::FEEDBACK_FIELD, null );
142
+ if ( null === $values ) {
143
+ $values = array();
144
+ }
145
+ $values[ $user_id ] = $_POST['feedback'];
146
+ $this->_save( array(
147
+ self::FEEDBACK_FIELD => $values
148
+ ) );
149
+ }
150
 
151
+ protected function _has_feedback( $user_id ) {
152
+ $values = $this->_get_field( self::FEEDBACK_FIELD, null );
153
+ if ( null === $values ) {
154
+ return false;
155
+ }
156
+ $user = (string) $user_id;
157
+ $value = isset( $values[$user] ) ? $values[$user] : '';
158
+ return 0 === strcasecmp( 'y', $value ) || 0 === strcasecmp( 'n', $value );
159
+ }
160
 
161
+ public function send_feedback_message() {
162
+ if ( ai1ec_is_blank( $_POST['name'] ) ||
163
+ ai1ec_is_blank( $_POST['email'] ) ||
164
+ ai1ec_is_blank( $_POST['site'] ) ||
165
+ ai1ec_is_blank( $_POST['message'] )
166
+ ) {
167
+ throw new Exception( 'All fields are required' );
168
+ }
169
+ $subject = __( 'Feedback provided by user', AI1EC_PLUGIN_NAME );
170
+ $content = sprintf( '<b>%s:</b><br/>%s<br/><br/><b>%s:</b><br/>%s<br/><br/><b>%s:</b><br/>%s<br/><br/><b>%s:</b><br/>%s',
171
+ __( 'Name', AI1EC_PLUGIN_NAME ),
172
+ $_POST['name'],
173
+ __( 'E-mail', AI1EC_PLUGIN_NAME ),
174
+ $_POST['email'],
175
+ __( 'Site URL', AI1EC_PLUGIN_NAME ),
176
+ $_POST['site'],
177
+ __( 'Message', AI1EC_PLUGIN_NAME ),
178
+ nl2br( $_POST['message'] )
179
+ );
180
+ $dispatcher = $this->_registry->get(
181
+ 'notification.email',
182
+ $content,
183
+ explode( ',', self::EMAIL_FEEDBACK_DESTINATION ),
184
+ $subject
185
+ );
186
+ $headers = array(
187
+ 'Content-type: text/html',
188
+ sprintf( 'From: %s <%s>', $_POST['name'], $_POST['email'])
189
+ );
190
+ if ( $dispatcher->send( $headers ) ) {
191
+ $_POST['feedback'] = 'n';
192
+ $this->save_feedback_review();
193
+ }
194
+ }
195
  }
app/model/search.php CHANGED
@@ -11,868 +11,868 @@
11
  */
12
  class Ai1ec_Event_Search extends Ai1ec_Base {
13
 
14
- /**
15
- * @var Ai1ec_Dbi instance
16
- */
17
- private $_dbi = null;
18
-
19
- /**
20
- * Caches the ids of the last 'between' query
21
- *
22
- * @var array
23
- */
24
- protected $_ids_between_cache = array();
25
-
26
- /**
27
- * Creates local DBI instance.
28
- */
29
- public function __construct( Ai1ec_Registry_Object $registry ){
30
- parent::__construct( $registry );
31
- $this->_dbi = $this->_registry->get( 'dbi.dbi' );
32
- }
33
-
34
- /**
35
- * @return array
36
- */
37
- public function get_cached_between_ids() {
38
- return $this->_ids_between_cache;
39
- }
40
-
41
- /**
42
- * Fetches the event object with the given post ID.
43
- *
44
- * Uses the WP cache to make this more efficient if possible.
45
- *
46
- * @param int $post_id The ID of the post associated.
47
- * @param bool|int $instance_id Instance ID, to fetch post details for.
48
- *
49
- * @return Ai1ec_Event The associated event object.
50
- */
51
- public function get_event( $post_id, $instance_id = false ) {
52
- $post_id = (int)$post_id;
53
- $instance_id = (int)$instance_id;
54
- if ( $instance_id < 1 ) {
55
- $instance_id = false;
56
- }
57
- return $this->_registry->get( 'model.event', $post_id, $instance_id );
58
- }
59
-
60
- /**
61
- * Return events falling within some time range.
62
- *
63
- * Return all events starting after the given start time and before the
64
- * given end time that the currently logged in user has permission to view.
65
- * If $spanning is true, then also include events that span this
66
- * period. All-day events are returned first.
67
- *
68
- * @param Ai1ec_Date_Time $start Limit to events starting after this.
69
- * @param Ai1ec_Date_Time $end Limit to events starting before this.
70
- * @param array $filter Array of filters for the events returned:
71
- * ['cat_ids'] => list of category IDs;
72
- * ['tag_ids'] => list of tag IDs;
73
- * ['post_ids'] => list of post IDs;
74
- * ['auth_ids'] => list of author IDs;
75
- * ['instance_ids'] => list of events
76
- * instance ids;
77
- * @param bool $spanning Also include events that span this period.
78
- * @param bool $single_day This parameter is added for oneday view.
79
- * Query should find events lasting in
80
- * particular day instead of checking dates
81
- * range. If you need to call this method
82
- * with $single_day set to true consider
83
- * using method get_events_for_day. This
84
- * parameter matters only if $spanning is set
85
- * to false.
86
- *
87
- * @return array List of matching event objects.
88
- */
89
- public function get_events_between(
90
- Ai1ec_Date_Time $start,
91
- Ai1ec_Date_Time $end,
92
- array $filter = array(),
93
- $spanning = false,
94
- $single_day = false
95
- ) {
96
- // Query arguments
97
- $args = array(
98
- $start->format_to_gmt(),
99
- $end->format_to_gmt(),
100
- );
101
-
102
- // Get post status Where snippet and associated SQL arguments
103
- $where_parameters = $this->_get_post_status_sql();
104
- $post_status_where = $where_parameters['post_status_where'];
105
- $args = array_merge( $args, $where_parameters['args'] );
106
-
107
- // Get the Join (filter_join) and Where (filter_where) statements based
108
- // on $filter elements specified
109
- $filter = $this->_get_filter_sql( $filter );
110
-
111
- $ai1ec_localization_helper = $this->_registry->get( 'p28n.wpml' );
112
-
113
- $wpml_join_particle = $ai1ec_localization_helper
114
- ->get_wpml_table_join( 'p.ID' );
115
-
116
- $wpml_where_particle = $ai1ec_localization_helper
117
- ->get_wpml_table_where();
118
-
119
- if ( $spanning ) {
120
- $spanning_string = 'i.end > %d AND i.start < %d ';
121
- } elseif ( $single_day ) {
122
- $spanning_string = 'i.end >= %d AND i.start <= %d ';
123
- } else {
124
- $spanning_string = 'i.start BETWEEN %d AND %d ';
125
- }
126
-
127
- $sql = '
128
- SELECT
129
- `p`.*,
130
- `e`.`post_id`,
131
- `i`.`id` AS `instance_id`,
132
- `i`.`start` AS `start`,
133
- `i`.`end` AS `end`,
134
- `e`.`timezone_name` AS `timezone_name`,
135
- `e`.`allday` AS `event_allday`,
136
- `e`.`recurrence_rules`,
137
- `e`.`exception_rules`,
138
- `e`.`recurrence_dates`,
139
- `e`.`exception_dates`,
140
- `e`.`venue`,
141
- `e`.`country`,
142
- `e`.`address`,
143
- `e`.`city`,
144
- `e`.`province`,
145
- `e`.`postal_code`,
146
- `e`.`instant_event`,
147
- `e`.`show_map`,
148
- `e`.`contact_name`,
149
- `e`.`contact_phone`,
150
- `e`.`contact_email`,
151
- `e`.`contact_url`,
152
- `e`.`cost`,
153
- `e`.`ticket_url`,
154
- `e`.`ical_feed_url`,
155
- `e`.`ical_source_url`,
156
- `e`.`ical_organizer`,
157
- `e`.`ical_contact`,
158
- `e`.`ical_uid`,
159
- `e`.`longitude`,
160
- `e`.`latitude`
161
- FROM
162
- ' . $this->_dbi->get_table_name( 'ai1ec_events' ) . ' e
163
- INNER JOIN
164
- ' . $this->_dbi->get_table_name( 'posts' ) . ' p
165
- ON ( `p`.`ID` = `e`.`post_id` )
166
- ' . $wpml_join_particle . '
167
- INNER JOIN
168
- ' . $this->_dbi->get_table_name( 'ai1ec_event_instances' ) . ' i
169
- ON ( `e`.`post_id` = `i`.`post_id` )
170
- ' . $filter['filter_join'] . '
171
- WHERE
172
- post_type = \'' . AI1EC_POST_TYPE . '\'
173
- ' . $wpml_where_particle . '
174
- AND
175
- ' . $spanning_string . '
176
- ' . $filter['filter_where'] . '
177
- ' . $post_status_where . '
178
- GROUP BY
179
- `i`.`id`
180
- ORDER BY
181
- `e` . `allday` DESC,
182
- `i` . `start` ASC,
183
- `p` . `post_title` ASC';
184
-
185
- $query = $this->_dbi->prepare( $sql, $args );
186
- $events = $this->_dbi->get_results( $query, ARRAY_A );
187
-
188
- $id_list = array();
189
- $id_instance_list = array();
190
- foreach ( $events as $event ) {
191
-
192
- if ( ! in_array( $event['post_id'], $id_list, true ) ) {
193
- $id_list[] = $event['post_id'];
194
- }
195
-
196
- $id_instance_list[] = array(
197
- 'id' => $event['post_id'],
198
- 'instance_id' => $event['instance_id'],
199
- );
200
- }
201
-
202
- if ( ! empty( $id_list ) ) {
203
- update_meta_cache( 'post', $id_list );
204
- $this->_ids_between_cache = $id_instance_list;
205
- }
206
-
207
- foreach ( $events as &$event ) {
208
- $event['allday'] = $this->_is_all_day( $event );
209
- $event = $this->_registry->get( 'model.event', $event );
210
- }
211
-
212
- return $events;
213
- }
214
-
215
- /**
216
- * get_events_relative_to function
217
- *
218
- * Return all events starting after the given reference time, limiting the
219
- * result set to a maximum of $limit items, offset by $page_offset. A
220
- * negative $page_offset can be provided, which will return events *before*
221
- * the reference time, as expected.
222
- *
223
- * @param int $time limit to events starting after this (local) UNIX time
224
- * @param int $limit return a maximum of this number of items
225
- * @param int $page_offset offset the result set by $limit times this number
226
- * @param array $filter Array of filters for the events returned.
227
- * ['cat_ids'] => non-associatative array of category IDs
228
- * ['tag_ids'] => non-associatative array of tag IDs
229
- * ['post_ids'] => non-associatative array of post IDs
230
- * ['auth_ids'] => non-associatative array of author IDs
231
- * ['instance_ids'] => non-associatative array of author IDs
232
- * @param int $last_day Last day (time), that was displayed.
233
- * NOTE FROM NICOLA: be careful, if you want a query with events
234
- * that have a start date which is greater than today, pass 0 as
235
- * this parameter. If you pass false ( or pass nothing ) you end up with a query
236
- * with events that finish before today. I don't know the rationale
237
- * behind this but that's how it works
238
- * @param bool $unique Whether display only unique events and don't
239
- * duplicate results with other instances or not.
240
- *
241
- * @return array five-element array:
242
- * ['events'] an array of matching event objects
243
- * ['prev'] true if more previous events
244
- * ['next'] true if more next events
245
- * ['date_first'] UNIX timestamp (date part) of first event
246
- * ['date_last'] UNIX timestamp (date part) of last event
247
- */
248
- public function get_events_relative_to(
249
- $time,
250
- $limit = 0,
251
- $page_offset = 0,
252
- $filter = array(),
253
- $last_day = false,
254
- $unique = false
255
- ) {
256
- $localization_helper = $this->_registry->get( 'p28n.wpml' );
257
- $settings = $this->_registry->get( 'model.settings' );
258
-
259
-
260
- // Even if there ARE more than 5 times the limit results - we shall not
261
- // try to fetch and display these, as it would crash system
262
- $limit = preg_replace('/\D/', '', $limit);
263
- $upper_boundary = $limit;
264
- if (
265
- $settings->get( 'agenda_include_entire_last_day' ) &&
266
- ( false !== $last_day )
267
- ) {
268
- $upper_boundary *= 5;
269
- }
270
-
271
- // Convert timestamp to GMT time
272
- $time = $this->_registry->get(
273
- 'date.system'
274
- )->get_current_rounded_time();
275
- // Get post status Where snippet and associated SQL arguments
276
- $where_parameters = $this->_get_post_status_sql();
277
- $post_status_where = $where_parameters['post_status_where'];
278
-
279
- // Get the Join (filter_join) and Where (filter_where) statements based
280
- // on $filter elements specified
281
- $filter = $this->_get_filter_sql( $filter );
282
-
283
- // Query arguments
284
- $args = array( $time );
285
- $args = array_merge( $args, $where_parameters['args'] );
286
-
287
- if( $page_offset >= 0 ) {
288
- $first_record = $page_offset * $limit;
289
- } else {
290
- $first_record = ( -$page_offset - 1 ) * $limit;
291
- }
292
-
293
-
294
- $wpml_join_particle = $localization_helper
295
- ->get_wpml_table_join( 'p.ID' );
296
-
297
- $wpml_where_particle = $localization_helper
298
- ->get_wpml_table_where();
299
-
300
- $filter_date_clause = ( $page_offset >= 0 )
301
- ? 'i.end >= %d '
302
- : 'i.start < %d ';
303
- $order_direction = ( $page_offset >= 0 ) ? 'ASC' : 'DESC';
304
- if ( false !== $last_day ) {
305
- if ( 0 == $last_day ) {
306
- $last_day = $time;
307
- }
308
- $filter_date_clause = ' i.end ';
309
- if ( $page_offset < 0 ) {
310
- $filter_date_clause .= '<';
311
- $order_direction = 'DESC';
312
- } else {
313
- $filter_date_clause .= '>';
314
- $order_direction = 'ASC';
315
- }
316
- $filter_date_clause .= ' %d ';
317
- $args[0] = $last_day;
318
- $first_record = 0;
319
- }
320
- $query = $this->_dbi->prepare(
321
- 'SELECT DISTINCT p.*, e.post_id, i.id AS instance_id, ' .
322
- 'i.start AS start, ' .
323
- 'i.end AS end, ' .
324
- 'e.allday AS event_allday, ' .
325
- 'e.recurrence_rules, e.exception_rules, e.ticket_url, e.instant_event, e.recurrence_dates, e.exception_dates, ' .
326
- 'e.venue, e.country, e.address, e.city, e.province, e.postal_code, ' .
327
- 'e.show_map, e.contact_name, e.contact_phone, e.contact_email, e.cost, ' .
328
- 'e.ical_feed_url, e.ical_source_url, e.ical_organizer, e.ical_contact, e.ical_uid, e.timezone_name, e.longitude, e.latitude ' .
329
- 'FROM ' . $this->_dbi->get_table_name( 'ai1ec_events' ) . ' e ' .
330
- 'INNER JOIN ' . $this->_dbi->get_table_name( 'posts' ) . ' p ON e.post_id = p.ID ' .
331
- $wpml_join_particle .
332
- ' INNER JOIN ' . $this->_dbi->get_table_name( 'ai1ec_event_instances' ) . ' i ON e.post_id = i.post_id ' .
333
- $filter['filter_join'] .
334
- " WHERE post_type = '" . AI1EC_POST_TYPE . "' " .
335
- ' AND ' . $filter_date_clause .
336
- $wpml_where_particle .
337
- $filter['filter_where'] .
338
- $post_status_where .
339
- ( $unique ? ' GROUP BY e.post_id' : '' ) .
340
- // Reverse order when viewing negative pages, to get correct set of
341
- // records. Then reverse results later to order them properly.
342
- ' ORDER BY i.start ' . $order_direction .
343
- ', post_title ' . $order_direction .
344
- ' LIMIT ' . $first_record . ', ' . $upper_boundary,
345
- $args
346
- );
347
-
348
- $events = $this->_dbi->get_results( $query, ARRAY_A );
349
-
350
- // Limit the number of records to convert to data-object
351
- $events = $this->_limit_result_set(
352
- $events,
353
- $limit,
354
- ( false !== $last_day )
355
- );
356
-
357
- // Reorder records if in negative page offset
358
- if( $page_offset < 0 ) {
359
- $events = array_reverse( $events );
360
- }
361
-
362
- $date_first = $date_last = NULL;
363
-
364
- foreach ( $events as &$event ) {
365
- $event['allday'] = $this->_is_all_day( $event );
366
- $event = $this->_registry->get( 'model.event', $event );
367
- if ( null === $date_first ) {
368
- $date_first = $event->get( 'start' );
369
- }
370
- $date_last = $event->get( 'start' );
371
- }
372
- $date_first = $this->_registry->get( 'date.time', $date_first );
373
- $date_last = $this->_registry->get( 'date.time', $date_last );
374
- // jus show next/prev links, in case no event found is shown.
375
- $next = true;
376
- $prev = true;
377
-
378
- return array(
379
- 'events' => $events,
380
- 'prev' => $prev,
381
- 'next' => $next,
382
- 'date_first' => $date_first,
383
- 'date_last' => $date_last,
384
- );
385
- }
386
-
387
- /**
388
- * get_events_relative_to_reference function
389
- *
390
- * Return all events starting after the given date reference, limiting the
391
- * result set to a maximum of $limit items, offset by $page_offset. A
392
- * negative $page_offset can be provided, which will return events *before*
393
- * the reference time, as expected.
394
- *
395
- * @param int $date_reference if page_offset is greater than or equal to zero, events with start date greater than the date_reference will be returned
396
- * otherwise events with start date less than the date_reference will be returned.
397
- * @param int $limit return a maximum of this number of items
398
- * @param int $page_offset offset the result set by $limit times this number
399
- * @param array $filter Array of filters for the events returned.
400
- * ['cat_ids'] => non-associatative array of category IDs
401
- * ['tag_ids'] => non-associatative array of tag IDs
402
- * ['post_ids'] => non-associatative array of post IDs
403
- * ['auth_ids'] => non-associatative array of author IDs
404
- * ['instance_ids'] => non-associatative array of author IDs
405
- * @param bool $unique Whether display only unique events and don't
406
- * duplicate results with other instances or not.
407
- *
408
- * @return array five-element array:
409
- * ['events'] an array of matching event objects
410
- * ['prev'] true if more previous events
411
- * ['next'] true if more next events
412
- * ['date_first'] UNIX timestamp (date part) of first event
413
- * ['date_last'] UNIX timestamp (date part) of last event
414
- */
415
- public function get_events_relative_to_reference( $date_reference, $limit = 0, $page_offset = 0, $filter = array(), $unique = false ) {
416
- $localization_helper = $this->_registry->get( 'p28n.wpml' );
417
- $settings = $this->_registry->get( 'model.settings' );
418
-
419
- // Even if there ARE more than 5 times the limit results - we shall not
420
- // try to fetch and display these, as it would crash system
421
- $limit = preg_replace( '/\D/', '', $limit );
422
-
423
- // Convert timestamp to GMT time
424
- if ( 0 == $date_reference ) {
425
- $timezone = $this->_registry->get( 'date.timezone' )->get( $settings->get( 'timezone_string' ) );
426
- $current_time = new DateTime( 'now' );
427
- $current_time->setTimezone( $timezone );
428
- $time = $current_time->format( 'U' );
429
- } else {
430
- $time = $date_reference;
431
- }
432
-
433
- // Get post status Where snippet and associated SQL arguments
434
- $where_parameters = $this->_get_post_status_sql();
435
- $post_status_where = $where_parameters['post_status_where'];
436
-
437
- // Get the Join (filter_join) and Where (filter_where) statements based
438
- // on $filter elements specified
439
- $filter = $this->_get_filter_sql( $filter );
440
-
441
- // Query arguments
442
- $args = array( $time );
443
- $args = array_merge( $args, $where_parameters['args'] );
444
-
445
- if ( 0 == $date_reference ) {
446
- if ( $page_offset >= 0 ) {
447
- $filter_date_clause = 'i.end >= %d ';
448
- $order_direction = 'ASC';
449
- } else {
450
- $filter_date_clause = 'i.start < %d ';
451
- $order_direction = 'DESC';
452
- }
453
- } else {
454
- if ( $page_offset < 0 ) {
455
- $filter_date_clause = 'i.end < %d ';
456
- $order_direction = 'DESC';
457
- } else {
458
- $filter_date_clause = 'i.end >= %d ';
459
- $order_direction = 'ASC';
460
- }
461
- }
462
- if ( $page_offset >= 0 ) {
463
- $first_record = $page_offset * $limit;
464
- } else {
465
- $first_record = ( - $page_offset - 1 ) * $limit;
466
- }
467
- $wpml_join_particle = $localization_helper->get_wpml_table_join( 'p.ID' );
468
- $wpml_where_particle = $localization_helper->get_wpml_table_where();
469
-
470
- $query = $this->_dbi->prepare(
471
- 'SELECT DISTINCT p.*, e.post_id, i.id AS instance_id, ' . 'i.start AS start, ' . 'i.end AS end, ' .
472
- 'e.allday AS event_allday, ' .
473
- 'e.recurrence_rules, e.exception_rules, e.ticket_url, e.instant_event, e.recurrence_dates, e.exception_dates, ' .
474
- 'e.venue, e.country, e.address, e.city, e.province, e.postal_code, ' .
475
- 'e.show_map, e.contact_name, e.contact_phone, e.contact_email, e.cost, ' .
476
- 'e.ical_feed_url, e.ical_source_url, e.ical_organizer, e.ical_contact, e.ical_uid, e.timezone_name, e.longitude, e.latitude ' .
477
- 'FROM ' . $this->_dbi->get_table_name( 'ai1ec_events' ) . ' e ' . 'INNER JOIN ' .
478
- $this->_dbi->get_table_name( 'posts' ) . ' p ON e.post_id = p.ID ' . $wpml_join_particle .
479
- ' INNER JOIN ' . $this->_dbi->get_table_name( 'ai1ec_event_instances' ) . ' i ON e.post_id = i.post_id ' .
480
- $filter['filter_join'] . " WHERE post_type = '" . AI1EC_POST_TYPE . "' " . ' AND ' . $filter_date_clause .
481
- $wpml_where_particle . $filter['filter_where'] . $post_status_where .
482
- ( $unique ? ' GROUP BY e.post_id' : '' ) .
483
- // Reverse order when viewing negative pages, to get correct set of
484
- // records. Then reverse results later to order them properly.
485
- ' ORDER BY i.start ' . $order_direction . ', post_title ' . $order_direction . ' LIMIT ' . $first_record .
486
- ', ' . ( $limit + 1 ),
487
- $args );
488
-
489
- $events = $this->_dbi->get_results( $query, ARRAY_A );
490
-
491
- if ( $page_offset >= 0 ) {
492
- $prev = true;
493
- $next = ( count( $events ) > $limit );
494
- if ( $next ) {
495
- array_pop( $events );
496
- }
497
- } else {
498
- $prev = ( count( $events ) > $limit );
499
- if ( $prev ) {
500
- array_pop( $events );
501
- }
502
- $next = true;
503
- }
504
-
505
- // Reorder records if in negative page offset
506
- if ( $page_offset < 0 ) {
507
- $events = array_reverse( $events );
508
- }
509
-
510
- $date_first = $date_last = NULL;
511
-
512
- foreach ( $events as &$event ) {
513
- $event['allday'] = $this->_is_all_day( $event );
514
- $event = $this->_registry->get( 'model.event', $event );
515
- if ( null === $date_first ) {
516
- $date_first = $event->get( 'start' );
517
- }
518
- $date_last = $event->get( 'start' );
519
- }
520
- $date_first = $this->_registry->get( 'date.time', $date_first );
521
- $date_last = $this->_registry->get( 'date.time', $date_last );
522
-
523
- return array(
524
- 'events' => $events,
525
- 'prev' => $prev,
526
- 'next' => $next,
527
- 'date_first' => $date_first,
528
- 'date_last' => $date_last );
529
- }
530
-
531
- /**
532
- * Returns events for given day. Event must start before end of day and must
533
- * ends after beginning of day.
534
- *
535
- * @param Ai1ec_Date_Time $day Date object.
536
- * @param array $filter Search filters;
537
- *
538
- * @return array List of events.
539
- */
540
- public function get_events_for_day(
541
- Ai1ec_Date_Time $day,
542
- array $filter = array()
543
- ) {
544
- $end_of_day = $this->_registry->get( 'date.time', $day )
545
- ->set_time( 23, 59, 59 );
546
- $start_of_day = $this->_registry->get( 'date.time', $day )
547
- ->set_time( 0, 0, 0 );
548
- return $this->get_events_between(
549
- $start_of_day,
550
- $end_of_day,
551
- $filter,
552
- false,
553
- true
554
- );
555
- }
556
-
557
- /**
558
- * Get ID of event in database, matching imported one.
559
- *
560
- * Return event ID by iCalendar UID, feed url, start time and whether the
561
- * event has recurrence rules (to differentiate between an event with a UID
562
- * defining the recurrence pattern, and other events with with the same UID,
563
- * which are just RECURRENCE-IDs).
564
- *
565
- * @param int $uid iCalendar UID property
566
- * @param string $feed Feed URL
567
- * @param int $start Start timestamp (GMT)
568
- * @param bool $has_recurrence Whether the event has recurrence rules
569
- * @param int|null $exclude_post_id Do not match against this post ID
570
- *
571
- * @return object|null ID of matching event post, or NULL if no match
572
- */
573
- public function get_matching_event_id(
574
- $uid,
575
- $feed,
576
- $start,
577
- $has_recurrence = false,
578
- $exclude_post_id = null
579
- ) {
580
- $dbi = $this->_registry->get( 'dbi.dbi' );
581
- $table_name = $dbi->get_table_name( 'ai1ec_events' );
582
- $query = 'SELECT `post_id` FROM ' . $table_name . '
583
- WHERE
584
- ical_feed_url = %s
585
- AND ical_uid = %s
586
- AND start = %d ' .
587
- ( $has_recurrence ? 'AND NOT ' : 'AND ' ) .
588
- ' ( recurrence_rules IS NULL OR recurrence_rules = \'\' )';
589
- $args = array( $feed, $uid );
590
- if ( $start instanceof Ai1ec_Date_Time ) {
591
- $args[] = $start->format();
592
- } else {
593
- $args[] = (int)$start;
594
- }
595
- if ( null !== $exclude_post_id ) {
596
- $query .= ' AND post_id <> %d';
597
- $args[] = $exclude_post_id;
598
- }
599
-
600
- return $dbi->get_var( $dbi->prepare( $query, $args ) );
601
- }
602
-
603
- /**
604
- * Get event by UID. UID must be unique.
605
- *
606
- * NOTICE: deletes events with that UID if they have different URLs.
607
- *
608
- * @param string $uid UID from feed.
609
- * @param string $uid Feed URL.
610
- *
611
- * @return int|null Matching Event ID or NULL if none found.
612
- */
613
- public function get_matching_event_by_uid_and_url( $uid, $url ) {
614
- if ( ! isset( $uid{1} ) ) {
615
- return null;
616
- }
617
- $dbi = $this->_registry->get( 'dbi.dbi' );
618
- $table_name = $dbi->get_table_name( 'ai1ec_events' );
619
- $argv = array( $url, $uid, $url );
620
- // fix issue where invalid feed URLs were assigned
621
- $update = 'UPDATE ' . $table_name . ' SET `ical_feed_url` = %s' .
622
- ' WHERE `ical_uid` = %s AND `ical_feed_url` != %s';
623
- $query = $dbi->prepare( $update, $argv);
624
- $success = $dbi->query( $query );
625
-
626
- // retrieve actual feed ID if any
627
- $select = 'SELECT `post_id` FROM `' . $table_name .
628
- '` WHERE `ical_uid` = %s';
629
- return $dbi->get_var( $dbi->prepare( $select, array( $uid ) ) );
630
- }
631
-
632
- /**
633
- * Get event ids for the passed feed url
634
- *
635
- * @param string $feed_url
636
- */
637
- public function get_event_ids_for_feed( $feed_url ) {
638
- $dbi = $this->_registry->get( 'dbi.dbi' );
639
- $table_name = $dbi->get_table_name( 'ai1ec_events' );
640
- $query = 'SELECT `post_id` FROM ' . $table_name .
641
- ' WHERE ical_feed_url = %s';
642
- return $dbi->get_col( $dbi->prepare( $query, array( $feed_url ) ) );
643
- }
644
-
645
- /**
646
- * Returns events instances closest to today.
647
- *
648
- * @param array $events_ids Events ids filter.
649
- *
650
- * @return array Events collection.
651
- * @throws Ai1ec_Bootstrap_Exception
652
- */
653
- public function get_instances_closest_to_today( array $events_ids = array() ) {
654
- $where_events_ids = '';
655
- if ( ! empty( $events_ids ) ) {
656
- $where_events_ids = 'i.post_id IN ('
657
- . implode( ',', $events_ids ) . ') AND ';
658
- }
659
- $query = 'SELECT i.id, i.post_id FROM ' .
660
- $this->_dbi->get_table_name( 'ai1ec_event_instances' ) .
661
- ' i WHERE ' .
662
- $where_events_ids .
663
- ' i.start > %d ' .
664
- ' GROUP BY i.post_id';
665
- /** @var $today Ai1ec_Date_Time */
666
- $today = $this->_registry->get( 'date.time', 'now', 'sys.default' );
667
- $today->set_time( 0, 0, 0 );
668
- $query = $this->_dbi->prepare( $query, $today->format( 'U' ) );
669
- $results = $this->_dbi->get_results( $query );
670
- $events = array();
671
- foreach ( $results as $result ) {
672
- $events[] = $this->get_event(
673
- $result->post_id,
674
- $result->id
675
- );
676
- }
677
-
678
- return $events;
679
- }
680
-
681
- /**
682
- * Check if given event must be treated as all-day event.
683
- *
684
- * Event instances that span 24 hours are treated as all-day.
685
- * NOTICE: event is passed in before being transformed into
686
- * Ai1ec_Event object, with Ai1ec_Date_Time fields.
687
- *
688
- * @param array $event Event data returned from database.
689
- *
690
- * @return bool True if event is all-day event.
691
- */
692
- protected function _is_all_day( array $event ) {
693
- if ( isset( $event['event_allday'] ) && $event['event_allday'] ) {
694
- return true;
695
- }
696
-
697
- if ( ! isset( $event['start'] ) || ! isset( $event['end'] ) ) {
698
- return false;
699
- }
700
-
701
- return ( 86400 === $event['end'] - $event['start'] );
702
- }
703
-
704
- /**
705
- * _limit_result_set function
706
- *
707
- * Slice given number of events from list, with exception when all
708
- * events from last day shall be included.
709
- *
710
- * @param array $events List of events to slice
711
- * @param int $limit Number of events to slice-off
712
- * @param bool $last_day Set to true to include all events from last day ignoring {$limit}
713
- *
714
- * @return array Sliced events list
715
- */
716
- protected function _limit_result_set(
717
- array $events,
718
- $limit,
719
- $last_day
720
- ) {
721
- $limited_events = array();
722
- $start_day_previous = 0;
723
- foreach ( $events as $event ) {
724
- $start_day = date(
725
- 'Y-m-d',
726
- $event['start']
727
- );
728
- --$limit; // $limit = $limit - 1;
729
- if ( $limit < 0 ) {
730
- if ( true === $last_day ) {
731
- if ( $start_day != $start_day_previous ) {
732
- break;
733
- }
734
- } else {
735
- break;
736
- }
737
- }
738
- $limited_events[] = $event;
739
- $start_day_previous = $start_day;
740
- }
741
- return $limited_events;
742
- }
743
-
744
- /**
745
- * _get_post_status_sql function
746
- *
747
- * Returns SQL snippet for properly matching event posts, as well as array
748
- * of arguments to pass to $this_dbi->prepare, in function argument
749
- * references.
750
- * Nothing is returned by the function.
751
- *
752
- * @return array An array containing post_status_where: the sql string,
753
- * args: the arguments for prepare()
754
- */
755
- protected function _get_post_status_sql() {
756
- $args = array();
757
-
758
- // Query the correct post status
759
- if (
760
- current_user_can( 'administrator' ) ||
761
- current_user_can( 'editor' )
762
- ) {
763
- // User has privilege of seeing all published and private
764
- $post_status_where = 'AND post_status IN ( %s, %s ) ';
765
- $args[] = 'publish';
766
- $args[] = 'private';
767
- } elseif ( is_user_logged_in() ) {
768
- // User has privilege of seeing all published and only their own
769
- // private posts.
770
-
771
- // Get user ID
772
- $user_id = 0;
773
- if ( is_callable( 'wp_get_current_user' ) ) {
774
- $user = wp_get_current_user();
775
- $user_id = (int)$user->ID;
776
- unset( $user );
777
- }
778
-
779
- // include post_status = published
780
- // OR
781
- // post_status = private AND post_author = userID
782
- $post_status_where =
783
- 'AND ( ' .
784
- 'post_status = %s ' .
785
- 'OR ( post_status = %s AND post_author = %d ) ' .
786
- ') ';
787
-
788
- $args[] = 'publish';
789
- $args[] = 'private';
790
- $args[] = $user_id;
791
- } else {
792
- // User can only see published posts.
793
- $post_status_where = 'AND post_status = %s ';
794
- $args[] = 'publish';
795
- }
796
-
797
- return array(
798
- 'post_status_where' => $post_status_where,
799
- 'args' => $args
800
- );
801
- }
802
-
803
- /**
804
- * Take filter and return SQL options.
805
- *
806
- * Takes an array of filtering options and turns it into JOIN and WHERE
807
- * statements for running an SQL query limited to the specified options.
808
- *
809
- * @param array $filter Array of filters for the events returned:
810
- * ['cat_ids'] => list of category IDs
811
- * ['tag_ids'] => list of tag IDs
812
- * ['post_ids'] => list of event post IDs
813
- * ['auth_ids'] => list of event author IDs
814
- * ['instance_ids'] => list of event instance IDs
815
- *
816
- * @return array The modified filter array to having:
817
- * ['filter_join'] the Join statements for the SQL
818
- * ['filter_where'] the Where statements for the SQL
819
- */
820
- protected function _get_filter_sql( $filter ) {
821
- $filter_join = $filter_where = array();
822
- foreach ( $filter as $filter_type => $filter_ids ) {
823
- $filter_object = null;
824
- try {
825
- if ( empty( $filter_ids ) ) {
826
- $filter_ids = array();
827
- }
828
- $filter_object = $this->_registry->get(
829
- 'model.filter.' . $filter_type,
830
- $filter_ids
831
- );
832
- if ( ! ( $filter_object instanceof Ai1ec_Filter_Interface ) ) {
833
- throw new Ai1ec_Bootstrap_Exception(
834
- 'Filter \'' . get_class( $filter_object ) .
835
- '\' is not instance of Ai1ec_Filter_Interface'
836
- );
837
- }
838
- } catch ( Ai1ec_Bootstrap_Exception $exception ) {
839
- continue;
840
- }
841
- $filter_join[] = $filter_object->get_join();
842
- $filter_where[] = $filter_object->get_where();
843
- }
844
-
845
- $filter_join = array_filter( $filter_join );
846
- $filter_where = array_filter( $filter_where );
847
- $filter_join = join( ' ', $filter_join );
848
- if ( count( $filter_where ) > 0 ) {
849
- $operator = $this->get_distinct_types_operator();
850
- $filter_where = $operator . '( ' .
851
- implode( ' ) ' . $operator . ' ( ', $filter_where ) .
852
- ' ) ';
853
- } else {
854
- $filter_where = '';
855
- }
856
-
857
- return $filter + compact( 'filter_where', 'filter_join' );
858
- }
859
-
860
- /**
861
- * Get operator for joining distinct filters in WHERE.
862
- *
863
- * @return string SQL operator.
864
- */
865
- public function get_distinct_types_operator() {
866
- static $operators = array( 'AND' => 1, 'OR' => 2 );
867
- $default = 'AND';
868
- $where_operator = strtoupper( trim( (string)apply_filters(
869
- 'ai1ec_filter_distinct_types_logic',
870
- $default
871
- ) ) );
872
- if ( ! isset( $operators[$where_operator] ) ) {
873
- $where_operator = $default;
874
- }
875
- return $where_operator;
876
- }
877
 
878
  }
11
  */
12
  class Ai1ec_Event_Search extends Ai1ec_Base {
13
 
14
+ /**
15
+ * @var Ai1ec_Dbi instance
16
+ */
17
+ private $_dbi = null;
18
+
19
+ /**
20
+ * Caches the ids of the last 'between' query
21
+ *
22
+ * @var array
23
+ */
24
+ protected $_ids_between_cache = array();
25
+
26
+ /**
27
+ * Creates local DBI instance.
28
+ */
29
+ public function __construct( Ai1ec_Registry_Object $registry ){
30
+ parent::__construct( $registry );
31
+ $this->_dbi = $this->_registry->get( 'dbi.dbi' );
32
+ }
33
+
34
+ /**
35
+ * @return array
36
+ */
37
+ public function get_cached_between_ids() {
38
+ return $this->_ids_between_cache;
39
+ }
40
+
41
+ /**
42
+ * Fetches the event object with the given post ID.
43
+ *
44
+ * Uses the WP cache to make this more efficient if possible.
45
+ *
46
+ * @param int $post_id The ID of the post associated.
47
+ * @param bool|int $instance_id Instance ID, to fetch post details for.
48
+ *
49
+ * @return Ai1ec_Event The associated event object.
50
+ */
51
+ public function get_event( $post_id, $instance_id = false ) {
52
+ $post_id = (int)$post_id;
53
+ $instance_id = (int)$instance_id;
54
+ if ( $instance_id < 1 ) {
55
+ $instance_id = false;
56
+ }
57
+ return $this->_registry->get( 'model.event', $post_id, $instance_id );
58
+ }
59
+
60
+ /**
61
+ * Return events falling within some time range.
62
+ *
63
+ * Return all events starting after the given start time and before the
64
+ * given end time that the currently logged in user has permission to view.
65
+ * If $spanning is true, then also include events that span this
66
+ * period. All-day events are returned first.
67
+ *
68
+ * @param Ai1ec_Date_Time $start Limit to events starting after this.
69
+ * @param Ai1ec_Date_Time $end Limit to events starting before this.
70
+ * @param array $filter Array of filters for the events returned:
71
+ * ['cat_ids'] => list of category IDs;
72
+ * ['tag_ids'] => list of tag IDs;
73
+ * ['post_ids'] => list of post IDs;
74
+ * ['auth_ids'] => list of author IDs;
75
+ * ['instance_ids'] => list of events
76
+ * instance ids;
77
+ * @param bool $spanning Also include events that span this period.
78
+ * @param bool $single_day This parameter is added for oneday view.
79
+ * Query should find events lasting in
80
+ * particular day instead of checking dates
81
+ * range. If you need to call this method
82
+ * with $single_day set to true consider
83
+ * using method get_events_for_day. This
84
+ * parameter matters only if $spanning is set
85
+ * to false.
86
+ *
87
+ * @return array List of matching event objects.
88
+ */
89
+ public function get_events_between(
90
+ Ai1ec_Date_Time $start,
91
+ Ai1ec_Date_Time $end,
92
+ array $filter = array(),
93
+ $spanning = false,
94
+ $single_day = false
95
+ ) {
96
+ // Query arguments
97
+ $args = array(
98
+ $start->format_to_gmt(),
99
+ $end->format_to_gmt(),
100
+ );
101
+
102
+ // Get post status Where snippet and associated SQL arguments
103
+ $where_parameters = $this->_get_post_status_sql();
104
+ $post_status_where = $where_parameters['post_status_where'];
105
+ $args = array_merge( $args, $where_parameters['args'] );
106
+
107
+ // Get the Join (filter_join) and Where (filter_where) statements based
108
+ // on $filter elements specified
109
+ $filter = $this->_get_filter_sql( $filter );
110
+
111
+ $ai1ec_localization_helper = $this->_registry->get( 'p28n.wpml' );
112
+
113
+ $wpml_join_particle = $ai1ec_localization_helper
114
+ ->get_wpml_table_join( 'p.ID' );
115
+
116
+ $wpml_where_particle = $ai1ec_localization_helper
117
+ ->get_wpml_table_where();
118
+
119
+ if ( $spanning ) {
120
+ $spanning_string = 'i.end > %d AND i.start < %d ';
121
+ } elseif ( $single_day ) {
122
+ $spanning_string = 'i.end >= %d AND i.start <= %d ';
123
+ } else {
124
+ $spanning_string = 'i.start BETWEEN %d AND %d ';
125
+ }
126
+
127
+ $sql = '
128
+ SELECT
129
+ `p`.*,
130
+ `e`.`post_id`,
131
+ `i`.`id` AS `instance_id`,
132
+ `i`.`start` AS `start`,
133
+ `i`.`end` AS `end`,
134
+ `e`.`timezone_name` AS `timezone_name`,
135
+ `e`.`allday` AS `event_allday`,
136
+ `e`.`recurrence_rules`,
137
+ `e`.`exception_rules`,
138
+ `e`.`recurrence_dates`,
139
+ `e`.`exception_dates`,
140
+ `e`.`venue`,
141
+ `e`.`country`,
142
+ `e`.`address`,
143
+ `e`.`city`,
144
+ `e`.`province`,
145
+ `e`.`postal_code`,
146
+ `e`.`instant_event`,
147
+ `e`.`show_map`,
148
+ `e`.`contact_name`,
149
+ `e`.`contact_phone`,
150
+ `e`.`contact_email`,
151
+ `e`.`contact_url`,
152
+ `e`.`cost`,
153
+ `e`.`ticket_url`,
154
+ `e`.`ical_feed_url`,
155
+ `e`.`ical_source_url`,
156
+ `e`.`ical_organizer`,
157
+ `e`.`ical_contact`,
158
+ `e`.`ical_uid`,
159
+ `e`.`longitude`,
160
+ `e`.`latitude`
161
+ FROM
162
+ ' . $this->_dbi->get_table_name( 'ai1ec_events' ) . ' e
163
+ INNER JOIN
164
+ ' . $this->_dbi->get_table_name( 'posts' ) . ' p
165
+ ON ( `p`.`ID` = `e`.`post_id` )
166
+ ' . $wpml_join_particle . '
167
+ INNER JOIN
168
+ ' . $this->_dbi->get_table_name( 'ai1ec_event_instances' ) . ' i
169
+ ON ( `e`.`post_id` = `i`.`post_id` )
170
+ ' . $filter['filter_join'] . '
171
+ WHERE
172
+ post_type = \'' . AI1EC_POST_TYPE . '\'
173
+ ' . $wpml_where_particle . '
174
+ AND
175
+ ' . $spanning_string . '
176
+ ' . $filter['filter_where'] . '
177
+ ' . $post_status_where . '
178
+ GROUP BY
179
+ `i`.`id`
180
+ ORDER BY
181
+ `e` . `allday` DESC,
182
+ `i` . `start` ASC,
183
+ `p` . `post_title` ASC';
184
+
185
+ $query = $this->_dbi->prepare( $sql, $args );
186
+ $events = $this->_dbi->get_results( $query, ARRAY_A );
187
+
188
+ $id_list = array();
189
+ $id_instance_list = array();
190
+ foreach ( $events as $event ) {
191
+
192
+ if ( ! in_array( $event['post_id'], $id_list, true ) ) {
193
+ $id_list[] = $event['post_id'];
194
+ }
195
+
196
+ $id_instance_list[] = array(
197
+ 'id' => $event['post_id'],
198
+ 'instance_id' => $event['instance_id'],
199
+ );
200
+ }
201
+
202
+ if ( ! empty( $id_list ) ) {
203
+ update_meta_cache( 'post', $id_list );
204
+ $this->_ids_between_cache = $id_instance_list;
205
+ }
206
+
207
+ foreach ( $events as &$event ) {
208
+ $event['allday'] = $this->_is_all_day( $event );
209
+ $event = $this->_registry->get( 'model.event', $event );
210
+ }
211
+
212
+ return $events;
213
+ }
214
+
215
+ /**
216
+ * get_events_relative_to function
217
+ *
218
+ * Return all events starting after the given reference time, limiting the
219
+ * result set to a maximum of $limit items, offset by $page_offset. A
220
+ * negative $page_offset can be provided, which will return events *before*
221
+ * the reference time, as expected.
222
+ *
223
+ * @param int $time limit to events starting after this (local) UNIX time
224
+ * @param int $limit return a maximum of this number of items
225
+ * @param int $page_offset offset the result set by $limit times this number
226
+ * @param array $filter Array of filters for the events returned.
227
+ * ['cat_ids'] => non-associatative array of category IDs
228
+ * ['tag_ids'] => non-associatative array of tag IDs
229
+ * ['post_ids'] => non-associatative array of post IDs
230
+ * ['auth_ids'] => non-associatative array of author IDs
231
+ * ['instance_ids'] => non-associatative array of author IDs
232
+ * @param int $last_day Last day (time), that was displayed.
233
+ * NOTE FROM NICOLA: be careful, if you want a query with events
234
+ * that have a start date which is greater than today, pass 0 as
235
+ * this parameter. If you pass false ( or pass nothing ) you end up with a query
236
+ * with events that finish before today. I don't know the rationale
237
+ * behind this but that's how it works
238
+ * @param bool $unique Whether display only unique events and don't
239
+ * duplicate results with other instances or not.
240
+ *
241
+ * @return array five-element array:
242
+ * ['events'] an array of matching event objects
243
+ * ['prev'] true if more previous events
244
+ * ['next'] true if more next events
245
+ * ['date_first'] UNIX timestamp (date part) of first event
246
+ * ['date_last'] UNIX timestamp (date part) of last event
247
+ */
248
+ public function get_events_relative_to(
249
+ $time,
250
+ $limit = 0,
251
+ $page_offset = 0,
252
+ $filter = array(),
253
+ $last_day = false,
254
+ $unique = false
255
+ ) {
256
+ $localization_helper = $this->_registry->get( 'p28n.wpml' );
257
+ $settings = $this->_registry->get( 'model.settings' );
258
+
259
+
260
+ // Even if there ARE more than 5 times the limit results - we shall not
261
+ // try to fetch and display these, as it would crash system
262
+ $limit = preg_replace('/\D/', '', $limit);
263
+ $upper_boundary = $limit;
264
+ if (
265
+ $settings->get( 'agenda_include_entire_last_day' ) &&
266
+ ( false !== $last_day )
267
+ ) {
268
+ $upper_boundary *= 5;
269
+ }
270
+
271
+ // Convert timestamp to GMT time
272
+ $time = $this->_registry->get(
273
+ 'date.system'
274
+ )->get_current_rounded_time();
275
+ // Get post status Where snippet and associated SQL arguments
276
+ $where_parameters = $this->_get_post_status_sql();
277
+ $post_status_where = $where_parameters['post_status_where'];
278
+
279
+ // Get the Join (filter_join) and Where (filter_where) statements based
280
+ // on $filter elements specified
281
+ $filter = $this->_get_filter_sql( $filter );
282
+
283
+ // Query arguments
284
+ $args = array( $time );
285
+ $args = array_merge( $args, $where_parameters['args'] );
286
+
287
+ if( $page_offset >= 0 ) {
288
+ $first_record = $page_offset * $limit;
289
+ } else {
290
+ $first_record = ( -$page_offset - 1 ) * $limit;
291
+ }
292
+
293
+
294
+ $wpml_join_particle = $localization_helper
295
+ ->get_wpml_table_join( 'p.ID' );
296
+
297
+ $wpml_where_particle = $localization_helper
298
+ ->get_wpml_table_where();
299
+
300
+ $filter_date_clause = ( $page_offset >= 0 )
301
+ ? 'i.end >= %d '
302
+ : 'i.start < %d ';
303
+ $order_direction = ( $page_offset >= 0 ) ? 'ASC' : 'DESC';
304
+ if ( false !== $last_day ) {
305
+ if ( 0 == $last_day ) {
306
+ $last_day = $time;
307
+ }
308
+ $filter_date_clause = ' i.end ';
309
+ if ( $page_offset < 0 ) {
310
+ $filter_date_clause .= '<';
311
+ $order_direction = 'DESC';
312
+ } else {
313
+ $filter_date_clause .= '>';
314
+ $order_direction = 'ASC';
315
+ }
316
+ $filter_date_clause .= ' %d ';
317
+ $args[0] = $last_day;
318
+ $first_record = 0;
319
+ }
320
+ $query = $this->_dbi->prepare(
321
+ 'SELECT DISTINCT p.*, e.post_id, i.id AS instance_id, ' .
322
+ 'i.start AS start, ' .
323
+ 'i.end AS end, ' .
324
+ 'e.allday AS event_allday, ' .
325
+ 'e.recurrence_rules, e.exception_rules, e.ticket_url, e.instant_event, e.recurrence_dates, e.exception_dates, ' .
326
+ 'e.venue, e.country, e.address, e.city, e.province, e.postal_code, ' .
327
+ 'e.show_map, e.contact_name, e.contact_phone, e.contact_email, e.cost, ' .
328
+ 'e.ical_feed_url, e.ical_source_url, e.ical_organizer, e.ical_contact, e.ical_uid, e.timezone_name, e.longitude, e.latitude ' .
329
+ 'FROM ' . $this->_dbi->get_table_name( 'ai1ec_events' ) . ' e ' .
330
+ 'INNER JOIN ' . $this->_dbi->get_table_name( 'posts' ) . ' p ON e.post_id = p.ID ' .
331
+ $wpml_join_particle .
332
+ ' INNER JOIN ' . $this->_dbi->get_table_name( 'ai1ec_event_instances' ) . ' i ON e.post_id = i.post_id ' .
333
+ $filter['filter_join'] .
334
+ " WHERE post_type = '" . AI1EC_POST_TYPE . "' " .
335
+ ' AND ' . $filter_date_clause .
336
+ $wpml_where_particle .
337
+ $filter['filter_where'] .
338
+ $post_status_where .
339
+ ( $unique ? ' GROUP BY e.post_id' : '' ) .
340
+ // Reverse order when viewing negative pages, to get correct set of
341
+ // records. Then reverse results later to order them properly.
342
+ ' ORDER BY i.start ' . $order_direction .
343
+ ', post_title ' . $order_direction .
344
+ ' LIMIT ' . $first_record . ', ' . $upper_boundary,
345
+ $args
346
+ );
347
+
348
+ $events = $this->_dbi->get_results( $query, ARRAY_A );
349
+
350
+ // Limit the number of records to convert to data-object
351
+ $events = $this->_limit_result_set(
352
+ $events,
353
+ $limit,
354
+ ( false !== $last_day )
355
+ );
356
+
357
+ // Reorder records if in negative page offset
358
+ if( $page_offset < 0 ) {
359
+ $events = array_reverse( $events );
360
+ }
361
+
362
+ $date_first = $date_last = NULL;
363
+
364
+ foreach ( $events as &$event ) {
365
+ $event['allday'] = $this->_is_all_day( $event );
366
+ $event = $this->_registry->get( 'model.event', $event );
367
+ if ( null === $date_first ) {
368
+ $date_first = $event->get( 'start' );
369
+ }
370
+ $date_last = $event->get( 'start' );
371
+ }
372
+ $date_first = $this->_registry->get( 'date.time', $date_first );
373
+ $date_last = $this->_registry->get( 'date.time', $date_last );
374
+ // jus show next/prev links, in case no event found is shown.
375
+ $next = true;
376
+ $prev = true;
377
+
378
+ return array(
379
+ 'events' => $events,
380
+ 'prev' => $prev,
381
+ 'next' => $next,
382
+ 'date_first' => $date_first,
383
+ 'date_last' => $date_last,
384
+ );
385
+ }
386
+
387
+ /**
388
+ * get_events_relative_to_reference function
389
+ *
390
+ * Return all events starting after the given date reference, limiting the
391
+ * result set to a maximum of $limit items, offset by $page_offset. A
392
+ * negative $page_offset can be provided, which will return events *before*
393
+ * the reference time, as expected.
394
+ *
395
+ * @param int $date_reference if page_offset is greater than or equal to zero, events with start date greater than the date_reference will be returned
396
+ * otherwise events with start date less than the date_reference will be returned.
397
+ * @param int $limit return a maximum of this number of items
398
+ * @param int $page_offset offset the result set by $limit times this number
399
+ * @param array $filter Array of filters for the events returned.
400
+ * ['cat_ids'] => non-associatative array of category IDs
401
+ * ['tag_ids'] => non-associatative array of tag IDs
402
+ * ['post_ids'] => non-associatative array of post IDs
403
+ * ['auth_ids'] => non-associatative array of author IDs
404
+ * ['instance_ids'] => non-associatative array of author IDs
405
+ * @param bool $unique Whether display only unique events and don't
406
+ * duplicate results with other instances or not.
407
+ *
408
+ * @return array five-element array:
409
+ * ['events'] an array of matching event objects
410
+ * ['prev'] true if more previous events
411
+ * ['next'] true if more next events
412
+ * ['date_first'] UNIX timestamp (date part) of first event
413
+ * ['date_last'] UNIX timestamp (date part) of last event
414
+ */
415
+ public function get_events_relative_to_reference( $date_reference, $limit = 0, $page_offset = 0, $filter = array(), $unique = false ) {
416
+ $localization_helper = $this->_registry->get( 'p28n.wpml' );
417
+ $settings = $this->_registry->get( 'model.settings' );
418
+
419
+ // Even if there ARE more than 5 times the limit results - we shall not
420
+ // try to fetch and display these, as it would crash system
421
+ $limit = preg_replace( '/\D/', '', $limit );
422
+
423
+ // Convert timestamp to GMT time
424
+ if ( 0 == $date_reference ) {
425
+ $timezone = $this->_registry->get( 'date.timezone' )->get( $settings->get( 'timezone_string' ) );
426
+ $current_time = new DateTime( 'now' );
427
+ $current_time->setTimezone( $timezone );
428
+ $time = $current_time->format( 'U' );
429
+ } else {
430
+ $time = $date_reference;
431
+ }
432
+
433
+ // Get post status Where snippet and associated SQL arguments
434
+ $where_parameters = $this->_get_post_status_sql();
435
+ $post_status_where = $where_parameters['post_status_where'];
436
+
437
+ // Get the Join (filter_join) and Where (filter_where) statements based
438
+ // on $filter elements specified
439
+ $filter = $this->_get_filter_sql( $filter );
440
+
441
+ // Query arguments
442
+ $args = array( $time );
443
+ $args = array_merge( $args, $where_parameters['args'] );
444
+
445
+ if ( 0 == $date_reference ) {
446
+ if ( $page_offset >= 0 ) {
447
+ $filter_date_clause = 'i.end >= %d ';
448
+ $order_direction = 'ASC';
449
+ } else {
450
+ $filter_date_clause = 'i.start < %d ';
451
+ $order_direction = 'DESC';
452
+ }
453
+ } else {
454
+ if ( $page_offset < 0 ) {
455
+ $filter_date_clause = 'i.end < %d ';
456
+ $order_direction = 'DESC';
457
+ } else {
458
+ $filter_date_clause = 'i.end >= %d ';
459
+ $order_direction = 'ASC';
460
+ }
461
+ }
462
+ if ( $page_offset >= 0 ) {
463
+ $first_record = $page_offset * $limit;
464
+ } else {
465
+ $first_record = ( - $page_offset - 1 ) * $limit;
466
+ }
467
+ $wpml_join_particle = $localization_helper->get_wpml_table_join( 'p.ID' );
468
+ $wpml_where_particle = $localization_helper->get_wpml_table_where();
469
+
470
+ $query = $this->_dbi->prepare(
471
+ 'SELECT DISTINCT p.*, e.post_id, i.id AS instance_id, ' . 'i.start AS start, ' . 'i.end AS end, ' .
472
+ 'e.allday AS event_allday, ' .
473
+ 'e.recurrence_rules, e.exception_rules, e.ticket_url, e.instant_event, e.recurrence_dates, e.exception_dates, ' .
474
+ 'e.venue, e.country, e.address, e.city, e.province, e.postal_code, ' .
475
+ 'e.show_map, e.contact_name, e.contact_phone, e.contact_email, e.cost, ' .
476
+ 'e.ical_feed_url, e.ical_source_url, e.ical_organizer, e.ical_contact, e.ical_uid, e.timezone_name, e.longitude, e.latitude ' .
477
+ 'FROM ' . $this->_dbi->get_table_name( 'ai1ec_events' ) . ' e ' . 'INNER JOIN ' .
478
+ $this->_dbi->get_table_name( 'posts' ) . ' p ON e.post_id = p.ID ' . $wpml_join_particle .
479
+ ' INNER JOIN ' . $this->_dbi->get_table_name( 'ai1ec_event_instances' ) . ' i ON e.post_id = i.post_id ' .
480
+ $filter['filter_join'] . " WHERE post_type = '" . AI1EC_POST_TYPE . "' " . ' AND ' . $filter_date_clause .
481
+ $wpml_where_particle . $filter['filter_where'] . $post_status_where .
482
+ ( $unique ? ' GROUP BY e.post_id' : '' ) .
483
+ // Reverse order when viewing negative pages, to get correct set of
484
+ // records. Then reverse results later to order them properly.
485
+ ' ORDER BY i.start ' . $order_direction . ', post_title ' . $order_direction . ' LIMIT ' . $first_record .
486
+ ', ' . ( $limit + 1 ),
487
+ $args );
488
+
489
+ $events = $this->_dbi->get_results( $query, ARRAY_A );
490
+
491
+ if ( $page_offset >= 0 ) {
492
+ $prev = true;
493
+ $next = ( count( $events ) > $limit );
494
+ if ( $next ) {
495
+ array_pop( $events );
496
+ }
497
+ } else {
498
+ $prev = ( count( $events ) > $limit );
499
+ if ( $prev ) {
500
+ array_pop( $events );
501
+ }
502
+ $next = true;
503
+ }
504
+
505
+ // Reorder records if in negative page offset
506
+ if ( $page_offset < 0 ) {
507
+ $events = array_reverse( $events );
508
+ }
509
+
510
+ $date_first = $date_last = NULL;
511
+
512
+ foreach ( $events as &$event ) {
513
+ $event['allday'] = $this->_is_all_day( $event );
514
+ $event = $this->_registry->get( 'model.event', $event );
515
+ if ( null === $date_first ) {
516
+ $date_first = $event->get( 'start' );
517
+ }
518
+ $date_last = $event->get( 'start' );
519
+ }
520
+ $date_first = $this->_registry->get( 'date.time', $date_first );
521
+ $date_last = $this->_registry->get( 'date.time', $date_last );
522
+
523
+ return array(
524
+ 'events' => $events,
525
+ 'prev' => $prev,
526
+ 'next' => $next,
527
+ 'date_first' => $date_first,
528
+ 'date_last' => $date_last );
529
+ }
530
+
531
+ /**
532
+ * Returns events for given day. Event must start before end of day and must
533
+ * ends after beginning of day.
534
+ *
535
+ * @param Ai1ec_Date_Time $day Date object.
536
+ * @param array $filter Search filters;
537
+ *
538
+ * @return array List of events.
539
+ */
540
+ public function get_events_for_day(
541
+ Ai1ec_Date_Time $day,
542
+ array $filter = array()
543
+ ) {
544
+ $end_of_day = $this->_registry->get( 'date.time', $day )
545
+ ->set_time( 23, 59, 59 );
546
+ $start_of_day = $this->_registry->get( 'date.time', $day )
547
+ ->set_time( 0, 0, 0 );
548
+ return $this->get_events_between(
549
+ $start_of_day,
550
+ $end_of_day,
551
+ $filter,
552
+ false,
553
+ true
554
+ );
555
+ }
556
+
557
+ /**
558
+ * Get ID of event in database, matching imported one.
559
+ *
560
+ * Return event ID by iCalendar UID, feed url, start time and whether the
561
+ * event has recurrence rules (to differentiate between an event with a UID
562
+ * defining the recurrence pattern, and other events with with the same UID,
563
+ * which are just RECURRENCE-IDs).
564
+ *
565
+ * @param int $uid iCalendar UID property
566
+ * @param string $feed Feed URL
567
+ * @param int $start Start timestamp (GMT)
568
+ * @param bool $has_recurrence Whether the event has recurrence rules
569
+ * @param int|null $exclude_post_id Do not match against this post ID
570
+ *
571
+ * @return object|null ID of matching event post, or NULL if no match
572
+ */
573
+ public function get_matching_event_id(
574
+ $uid,
575
+ $feed,
576
+ $start,
577
+ $has_recurrence = false,
578
+ $exclude_post_id = null
579
+ ) {
580
+ $dbi = $this->_registry->get( 'dbi.dbi' );
581
+ $table_name = $dbi->get_table_name( 'ai1ec_events' );
582
+ $query = 'SELECT `post_id` FROM ' . $table_name . '
583
+ WHERE
584
+ ical_feed_url = %s
585
+ AND ical_uid = %s
586
+ AND start = %d ' .
587
+ ( $has_recurrence ? 'AND NOT ' : 'AND ' ) .
588
+ ' ( recurrence_rules IS NULL OR recurrence_rules = \'\' )';
589
+ $args = array( $feed, $uid );
590
+ if ( $start instanceof Ai1ec_Date_Time ) {
591
+ $args[] = $start->format();
592
+ } else {
593
+ $args[] = (int)$start;
594
+ }
595
+ if ( null !== $exclude_post_id ) {
596
+ $query .= ' AND post_id <> %d';
597
+ $args[] = $exclude_post_id;
598
+ }
599
+
600
+ return $dbi->get_var( $dbi->prepare( $query, $args ) );
601
+ }
602
+
603
+ /**
604
+ * Get event by UID. UID must be unique.
605
+ *
606
+ * NOTICE: deletes events with that UID if they have different URLs.
607
+ *
608
+ * @param string $uid UID from feed.
609
+ * @param string $uid Feed URL.
610
+ *
611
+ * @return int|null Matching Event ID or NULL if none found.
612
+ */
613
+ public function get_matching_event_by_uid_and_url( $uid, $url ) {
614
+ if ( ! isset( $uid{1} ) ) {
615
+ return null;
616
+ }
617
+ $dbi = $this->_registry->get( 'dbi.dbi' );
618
+ $table_name = $dbi->get_table_name( 'ai1ec_events' );
619
+ $argv = array( $url, $uid, $url );
620
+ // fix issue where invalid feed URLs were assigned
621
+ $update = 'UPDATE ' . $table_name . ' SET `ical_feed_url` = %s' .
622
+ ' WHERE `ical_uid` = %s AND `ical_feed_url` != %s';
623
+ $query = $dbi->prepare( $update, $argv);
624
+ $success = $dbi->query( $query );
625
+
626
+ // retrieve actual feed ID if any
627
+ $select = 'SELECT `post_id` FROM `' . $table_name .
628
+ '` WHERE `ical_uid` = %s';
629
+ return $dbi->get_var( $dbi->prepare( $select, array( $uid ) ) );
630
+ }
631
+
632
+ /**
633
+ * Get event ids for the passed feed url
634
+ *
635
+ * @param string $feed_url
636
+ */
637
+ public function get_event_ids_for_feed( $feed_url ) {
638
+ $dbi = $this->_registry->get( 'dbi.dbi' );
639
+ $table_name = $dbi->get_table_name( 'ai1ec_events' );
640
+ $query = 'SELECT `post_id` FROM ' . $table_name .
641
+ ' WHERE ical_feed_url = %s';
642
+ return $dbi->get_col( $dbi->prepare( $query, array( $feed_url ) ) );
643
+ }
644
+
645
+ /**
646
+ * Returns events instances closest to today.
647
+ *
648
+ * @param array $events_ids Events ids filter.
649
+ *
650
+ * @return array Events collection.
651
+ * @throws Ai1ec_Bootstrap_Exception
652
+ */
653
+ public function get_instances_closest_to_today( array $events_ids = array() ) {
654
+ $where_events_ids = '';
655
+ if ( ! empty( $events_ids ) ) {
656
+ $where_events_ids = 'i.post_id IN ('
657
+ . implode( ',', $events_ids ) . ') AND ';
658
+ }
659
+ $query = 'SELECT i.id, i.post_id FROM ' .
660
+ $this->_dbi->get_table_name( 'ai1ec_event_instances' ) .
661
+ ' i WHERE ' .
662
+ $where_events_ids .
663
+ ' i.start > %d ' .
664
+ ' GROUP BY i.post_id';
665
+ /** @var $today Ai1ec_Date_Time */
666
+ $today = $this->_registry->get( 'date.time', 'now', 'sys.default' );
667
+ $today->set_time( 0, 0, 0 );
668
+ $query = $this->_dbi->prepare( $query, $today->format( 'U' ) );
669
+ $results = $this->_dbi->get_results( $query );
670
+ $events = array();
671
+ foreach ( $results as $result ) {
672
+ $events[] = $this->get_event(
673
+ $result->post_id,
674
+ $result->id
675
+ );
676
+ }
677
+
678
+ return $events;
679
+ }
680
+
681
+ /**
682
+ * Check if given event must be treated as all-day event.
683
+ *
684
+ * Event instances that span 24 hours are treated as all-day.
685
+ * NOTICE: event is passed in before being transformed into
686
+ * Ai1ec_Event object, with Ai1ec_Date_Time fields.
687
+ *
688
+ * @param array $event Event data returned from database.
689
+ *
690
+ * @return bool True if event is all-day event.
691
+ */
692
+ protected function _is_all_day( array $event ) {
693
+ if ( isset( $event['event_allday'] ) && $event['event_allday'] ) {
694
+ return true;
695
+ }
696
+
697
+ if ( ! isset( $event['start'] ) || ! isset( $event['end'] ) ) {
698
+ return false;
699
+ }
700
+
701
+ return ( 86400 === $event['end'] - $event['start'] );
702
+ }
703
+
704
+ /**
705
+ * _limit_result_set function
706
+ *
707
+ * Slice given number of events from list, with exception when all
708
+ * events from last day shall be included.
709
+ *
710
+ * @param array $events List of events to slice
711
+ * @param int $limit Number of events to slice-off
712
+ * @param bool $last_day Set to true to include all events from last day ignoring {$limit}
713
+ *
714
+ * @return array Sliced events list
715
+ */
716
+ protected function _limit_result_set(
717
+ array $events,
718
+ $limit,
719
+ $last_day
720
+ ) {
721
+ $limited_events = array();
722
+ $start_day_previous = 0;
723
+ foreach ( $events as $event ) {
724
+ $start_day = date(
725
+ 'Y-m-d',
726
+ $event['start']
727
+ );
728
+ --$limit; // $limit = $limit - 1;
729
+ if ( $limit < 0 ) {
730
+ if ( true === $last_day ) {
731
+ if ( $start_day != $start_day_previous ) {
732
+ break;
733
+ }
734
+ } else {
735
+ break;
736
+ }
737
+ }
738
+ $limited_events[] = $event;
739
+ $start_day_previous = $start_day;
740
+ }
741
+ return $limited_events;
742
+ }
743
+
744
+ /**
745
+ * _get_post_status_sql function
746
+ *
747
+ * Returns SQL snippet for properly matching event posts, as well as array
748
+ * of arguments to pass to $this_dbi->prepare, in function argument
749
+ * references.
750
+ * Nothing is returned by the function.
751
+ *
752
+ * @return array An array containing post_status_where: the sql string,
753
+ * args: the arguments for prepare()
754
+ */
755
+ protected function _get_post_status_sql() {
756
+ $args = array();
757
+
758
+ // Query the correct post status
759
+ if (
760
+ current_user_can( 'administrator' ) ||
761
+ current_user_can( 'editor' )
762
+ ) {
763
+ // User has privilege of seeing all published and private
764
+ $post_status_where = 'AND post_status IN ( %s, %s ) ';
765
+ $args[] = 'publish';
766
+ $args[] = 'private';
767
+ } elseif ( is_user_logged_in() ) {
768
+ // User has privilege of seeing all published and only their own
769
+ // private posts.
770
+
771
+ // Get user ID
772
+ $user_id = 0;
773
+ if ( is_callable( 'wp_get_current_user' ) ) {
774
+ $user = wp_get_current_user();
775
+ $user_id = (int)$user->ID;
776
+ unset( $user );
777
+ }
778
+
779
+ // include post_status = published
780
+ // OR
781
+ // post_status = private AND post_author = userID
782
+ $post_status_where =
783
+ 'AND ( ' .
784
+ 'post_status = %s ' .
785
+ 'OR ( post_status = %s AND post_author = %d ) ' .
786
+ ') ';
787
+
788
+ $args[] = 'publish';
789
+ $args[] = 'private';
790
+ $args[] = $user_id;
791
+ } else {
792
+ // User can only see published posts.
793
+ $post_status_where = 'AND post_status = %s ';
794
+ $args[] = 'publish';
795
+ }
796
+
797
+ return array(
798
+ 'post_status_where' => $post_status_where,
799
+ 'args' => $args
800
+ );
801
+ }
802
+
803
+ /**
804
+ * Take filter and return SQL options.
805
+ *
806
+ * Takes an array of filtering options and turns it into JOIN and WHERE
807
+ * statements for running an SQL query limited to the specified options.
808
+ *
809
+ * @param array $filter Array of filters for the events returned:
810
+ * ['cat_ids'] => list of category IDs
811
+ * ['tag_ids'] => list of tag IDs
812
+ * ['post_ids'] => list of event post IDs
813
+ * ['auth_ids'] => list of event author IDs
814
+ * ['instance_ids'] => list of event instance IDs
815
+ *
816
+ * @return array The modified filter array to having:
817
+ * ['filter_join'] the Join statements for the SQL
818
+ * ['filter_where'] the Where statements for the SQL
819
+ */
820
+ protected function _get_filter_sql( $filter ) {
821
+ $filter_join = $filter_where = array();
822
+ foreach ( $filter as $filter_type => $filter_ids ) {
823
+ $filter_object = null;
824
+ try {
825
+ if ( empty( $filter_ids ) ) {
826
+ $filter_ids = array();
827
+ }
828
+ $filter_object = $this->_registry->get(
829
+ 'model.filter.' . $filter_type,
830
+ $filter_ids
831
+ );
832
+ if ( ! ( $filter_object instanceof Ai1ec_Filter_Interface ) ) {
833
+ throw new Ai1ec_Bootstrap_Exception(
834
+ 'Filter \'' . get_class( $filter_object ) .
835
+ '\' is not instance of Ai1ec_Filter_Interface'
836
+ );
837
+ }
838
+ } catch ( Ai1ec_Bootstrap_Exception $exception ) {
839
+ continue;
840
+ }
841
+ $filter_join[] = $filter_object->get_join();
842
+ $filter_where[] = $filter_object->get_where();
843
+ }
844
+
845
+ $filter_join = array_filter( $filter_join );
846
+ $filter_where = array_filter( $filter_where );
847
+ $filter_join = join( ' ', $filter_join );
848
+ if ( count( $filter_where ) > 0 ) {
849
+ $operator = $this->get_distinct_types_operator();
850
+ $filter_where = $operator . '( ' .
851
+ implode( ' ) ' . $operator . ' ( ', $filter_where ) .
852
+ ' ) ';
853
+ } else {
854
+ $filter_where = '';
855
+ }
856
+
857
+ return $filter + compact( 'filter_where', 'filter_join' );
858
+ }
859
+
860
+ /**
861
+ * Get operator for joining distinct filters in WHERE.
862
+ *
863
+ * @return string SQL operator.
864
+ */
865
+ public function get_distinct_types_operator() {
866
+ static $operators = array( 'AND' => 1, 'OR' => 2 );
867
+ $default = 'AND';
868
+ $where_operator = strtoupper( trim( (string)apply_filters(
869
+ 'ai1ec_filter_distinct_types_logic',
870
+ $default
871
+ ) ) );
872
+ if ( ! isset( $operators[$where_operator] ) ) {
873
+ $where_operator = $default;
874
+ }
875
+ return $where_operator;
876
+ }
877
 
878
  }
app/model/settings-view.php CHANGED
@@ -10,158 +10,158 @@
10
  */
11
  class Ai1ec_Settings_View extends Ai1ec_App {
12
 
13
- /**
14
- * @var string Name of settings option to use for views map.
15
- */
16
- const SETTING_VIEWS_MAP = 'enabled_views';
17
 
18
- /**
19
- * @var Ai1ec_Settings Instance
20
- */
21
- protected $_settings = null;
22
 
23
- /**
24
- * Acquire Settings model instance for future reference.
25
- *
26
- * @return void
27
- */
28
- protected function _initialize() {
29
- $this->_settings = $this->_registry->get( 'model.settings' );
30
- }
31
 
32
- /**
33
- * Add a view if not set.
34
- *
35
- * @param array $view
36
- */
37
- public function add( array $view ) {
38
- $enabled_views = $this->_get();
39
- if ( isset( $enabled_views[$view['name']] ) ) {
40
- if ( $enabled_views[$view['name']]['longname'] === $view['longname'] ) {
41
- return;
42
- }
43
- $enabled_views[$view['name']]['longname'] = $view['longname'];
44
- } else {
45
- // Copy relevant settings to local view array; account for possible missing
46
- // mobile settings during upgrade (assign defaults).
47
- $enabled_views[$view['name']] = array(
48
- 'enabled' => $view['enabled'],
49
- 'default' => $view['default'],
50
- 'enabled_mobile' => isset( $view['enabled_mobile'] ) ?
51
- $view['enabled_mobile'] : $view['enabled'],
52
- 'default_mobile' => isset( $view['default_mobile'] ) ?
53
- $view['default_mobile'] : $view['default'],
54
- 'longname' => $view['longname'],
55
- );
56
- }
57
- $this->_set( $enabled_views );
58
- }
59
 
60
- /**
61
- * Remove a view.
62
- *
63
- * @param string $view
64
- */
65
- public function remove( $view ) {
66
- $enabled_views = $this->_get();
67
- if ( isset( $enabled_views[$view] ) ) {
68
- unset( $enabled_views[$view] );
69
- $this->_set( $enabled_views );
70
- }
71
- }
72
 
73
- /**
74
- * Retrieve all configured views.
75
- *
76
- * @return array Map of configured view aliases and their details.
77
- */
78
- public function get_all() {
79
- return $this->_get();
80
- }
81
 
82
- /**
83
- * Get name of view to be rendered for requested alias.
84
- *
85
- * @param string $view Name of view requested.
86
- *
87
- * @return string Name of view to be rendered.
88
- *
89
- * @throws Ai1ec_Settings_Exception If no views are configured.
90
- */
91
- public function get_configured( $view ) {
92
- $enabled_views = $this->_get();
93
- if ( empty( $enabled_views ) ) {
94
- throw new Ai1ec_Settings_Exception( 'No view is enabled' );
95
- }
96
- if (
97
- isset( $enabled_views[$view] ) &&
98
- $enabled_views[$view]['enabled' . ( wp_is_mobile() ? '_mobile' : '' ) ]
99
- ) {
100
- return $view;
101
- }
102
- return $this->get_default();
103
- }
104
 
105
- /**
106
- * Get default view to render.
107
- *
108
- *
109
- * @return
110
- */
111
- public function get_default() {
112
- $enabled_views = $this->_get();
113
- $default = null;
114
- // Check mobile settings first, if in mobile mode.
115
- if (
116
- ! $this->_registry->get( 'compatibility.cli' )->is_cli() &&
117
- wp_is_mobile()
118
- ) {
119
- foreach ( $enabled_views as $view => $details ) {
120
- if (
121
- isset( $details['default_mobile'] ) &&
122
- $details['default_mobile'] &&
123
- $details['enabled_mobile']
124
- ) {
125
- $default = $view;
126
- break;
127
- }
128
- }
129
- }
130
- // Either not in mobile mode or no mobile settings available; look up
131
- // desktop settings.
132
- if ( null === $default ) {
133
- foreach ( $enabled_views as $view => $details ) {
134
- if ( $details['default'] && $details['enabled'] ) {
135
- $default = $view;
136
- break;
137
- }
138
- }
139
- }
140
- // No enabled view found, but we need to pick one, so pick the first view.
141
- if ( null === $default ) {
142
- $default = (string)current( array_keys( $enabled_views ) );
143
- }
144
- return $default;
145
- }
146
 
147
- /**
148
- * Retrieve views maps from storage.
149
- *
150
- * @return array Current views map.
151
- */
152
- protected function _get() {
153
- return (array)$this->_settings->get( self::SETTING_VIEWS_MAP, array() );
154
- }
155
 
156
- /**
157
- * Update views map.
158
- *
159
- * @param array $enabled_views Map of enabled views.
160
- *
161
- * @return bool Success.
162
- */
163
- protected function _set( array $enabled_views ) {
164
- return $this->_settings->set( self::SETTING_VIEWS_MAP, $enabled_views );
165
- }
166
 
167
  }
10
  */
11
  class Ai1ec_Settings_View extends Ai1ec_App {
12
 
13
+ /**
14
+ * @var string Name of settings option to use for views map.
15
+ */
16
+ const SETTING_VIEWS_MAP = 'enabled_views';
17
 
18
+ /**
19
+ * @var Ai1ec_Settings Instance
20
+ */
21
+ protected $_settings = null;
22
 
23
+ /**
24
+ * Acquire Settings model instance for future reference.
25
+ *
26
+ * @return void
27
+ */
28
+ protected function _initialize() {
29
+ $this->_settings = $this->_registry->get( 'model.settings' );
30
+ }
31
 
32
+ /**
33
+ * Add a view if not set.
34
+ *
35
+ * @param array $view
36
+ */
37
+ public function add( array $view ) {
38
+ $enabled_views = $this->_get();
39
+ if ( isset( $enabled_views[$view['name']] ) ) {
40
+ if ( $enabled_views[$view['name']]['longname'] === $view['longname'] ) {
41
+ return;
42
+ }
43
+ $enabled_views[$view['name']]['longname'] = $view['longname'];
44
+ } else {
45
+ // Copy relevant settings to local view array; account for possible missing
46
+ // mobile settings during upgrade (assign defaults).
47
+ $enabled_views[$view['name']] = array(
48
+ 'enabled' => $view['enabled'],
49
+ 'default' => $view['default'],
50
+ 'enabled_mobile' => isset( $view['enabled_mobile'] ) ?
51
+ $view['enabled_mobile'] : $view['enabled'],
52
+ 'default_mobile' => isset( $view['default_mobile'] ) ?
53
+ $view['default_mobile'] : $view['default'],
54
+ 'longname' => $view['longname'],
55
+ );
56
+ }
57
+ $this->_set( $enabled_views );
58
+ }
59
 
60
+ /**
61
+ * Remove a view.
62
+ *
63
+ * @param string $view
64
+ */
65
+ public function remove( $view ) {
66
+ $enabled_views = $this->_get();
67
+ if ( isset( $enabled_views[$view] ) ) {
68
+ unset( $enabled_views[$view] );
69
+ $this->_set( $enabled_views );
70
+ }
71
+ }
72
 
73
+ /**
74
+ * Retrieve all configured views.
75
+ *
76
+ * @return array Map of configured view aliases and their details.
77
+ */
78
+ public function get_all() {
79
+ return $this->_get();
80
+ }
81
 
82
+ /**
83
+ * Get name of view to be rendered for requested alias.
84
+ *
85
+ * @param string $view Name of view requested.
86
+ *
87
+ * @return string Name of view to be rendered.
88
+ *
89
+ * @throws Ai1ec_Settings_Exception If no views are configured.
90
+ */
91
+ public function get_configured( $view ) {
92
+ $enabled_views = $this->_get();
93
+ if ( empty( $enabled_views ) ) {
94
+ throw new Ai1ec_Settings_Exception( 'No view is enabled' );
95
+ }
96
+ if (
97
+ isset( $enabled_views[$view] ) &&
98
+ $enabled_views[$view]['enabled' . ( wp_is_mobile() ? '_mobile' : '' ) ]
99
+ ) {
100
+ return $view;
101
+ }
102
+ return $this->get_default();
103
+ }
104
 
105
+ /**
106
+ * Get default view to render.
107
+ *
108
+ *
109
+ * @return
110
+ */
111
+ public function get_default() {
112
+ $enabled_views = $this->_get();
113
+ $default = null;
114
+ // Check mobile settings first, if in mobile mode.
115
+ if (
116
+ ! $this->_registry->get( 'compatibility.cli' )->is_cli() &&
117
+ wp_is_mobile()
118
+ ) {
119
+ foreach ( $enabled_views as $view => $details ) {
120
+ if (
121
+ isset( $details['default_mobile'] ) &&
122
+ $details['default_mobile'] &&
123
+ $details['enabled_mobile']
124
+ ) {
125
+ $default = $view;
126
+ break;
127
+ }
128
+ }
129
+ }
130
+ // Either not in mobile mode or no mobile settings available; look up
131
+ // desktop settings.
132
+ if ( null === $default ) {
133
+ foreach ( $enabled_views as $view => $details ) {
134
+ if ( $details['default'] && $details['enabled'] ) {
135
+ $default = $view;
136
+ break;
137
+ }
138
+ }
139
+ }
140
+ // No enabled view found, but we need to pick one, so pick the first view.
141
+ if ( null === $default ) {
142
+ $default = (string)current( array_keys( $enabled_views ) );
143
+ }
144
+ return $default;
145
+ }
146
 
147
+ /**
148
+ * Retrieve views maps from storage.
149
+ *
150
+ * @return array Current views map.
151
+ */
152
+ protected function _get() {
153
+ return (array)$this->_settings->get( self::SETTING_VIEWS_MAP, array() );
154
+ }
155
 
156
+ /**
157
+ * Update views map.
158
+ *
159
+ * @param array $enabled_views Map of enabled views.
160
+ *
161
+ * @return bool Success.
162
+ */
163
+ protected function _set( array $enabled_views ) {
164
+ return $this->_settings->set( self::SETTING_VIEWS_MAP, $enabled_views );
165
+ }
166
 
167
  }
app/model/settings.php CHANGED
@@ -10,335 +10,335 @@
10
  */
11
  class Ai1ec_Settings extends Ai1ec_App {
12
 
13
- /**
14
- * @constant string Name of WordPress options key used to store settings.
15
- */
16
- const WP_OPTION_KEY = 'ai1ec_settings';
17
 
18
- /**
19
- * @var array Map of value names and their representations.
20
- */
21
- protected $_options = array();
22
 
23
- /**
24
- * @var bool Indicator for modified object state.
25
- */
26
- protected $_updated = false;
27
 
28
- /**
29
- * @var array The core options of the plugin.
30
- */
31
- protected $_standard_options;
32
 
33
- /**
34
- * Register new option to be used.
35
- *
36
- * @param string $option Name of option.
37
- * @param mixed $value The value.
38
- * @param string $type Option type to be used for validation.
39
- * @param string $renderer Name of class to render the option.
40
- *
41
- * @return Ai1ec_Settings Instance of self for chaining.
42
- */
43
- public function register(
44
- $option,
45
- $value,
46
- $type,
47
- $renderer,
48
- $version = '2.0.0'
49
- ) {
50
 
51
- if ( 'deprecated' === $type ) {
52
- unset( $this->_options[$option] );
53
- } else if (
54
- ! isset( $this->_options[$option] ) ||
55
- ! isset( $this->_options[$option]['version'] ) ||
56
- (string)$this->_options[$option]['version'] !== (string)$version ||
57
- (
58
- isset( $renderer['label'] ) &&
59
- isset( $this->_options[$option]['renderer'] ) &&
60
- (string)$this->_options[$option]['renderer']['label'] !== (string)$renderer['label']
61
- ) ||
62
- (
63
- isset( $renderer['help'] ) &&
64
- ( ! isset( $this->_options[$option]['renderer']['help'] ) || // handle the case when you are adding help
65
- (string)$this->_options[$option]['renderer']['help'] !== (string)$renderer['help'] )
66
- )
67
- ) {
68
- $this->_options[$option] = array(
69
- 'value' => ( isset( $this->_options[$option] ) )
70
- ? $this->_options[$option]['value']
71
- : $value,
72
- 'type' => $type,
73
- 'legacy' => false,
74
- 'version' => $version,
75
- );
76
- if ( null !== $renderer ) {
77
- $this->_options[$option]['renderer'] = $renderer;
78
- }
79
- }
80
- return $this;
81
- }
82
 
83
- /**
84
- * Gets the options.
85
- *
86
- * @return array:
87
- */
88
- public function get_options() {
89
- return $this->_options;
90
- }
91
 
92
- /**
93
- * Get field options as registered.
94
- *
95
- * @param string $option Name of option field to describe.
96
- *
97
- * @return array|null Description or null if nothing is found.
98
- */
99
- public function describe( $option ) {
100
- if ( ! isset( $this->_options[$option] ) ) {
101
- return null;
102
- }
103
- return $this->_options[$option];
104
- }
105
 
106
- /**
107
- * Get value for option.
108
- *
109
- * @param string $option Name of option to get value for.
110
- * @param mixed $default Value to return if option is not found.
111
- *
112
- * @return mixed Value or $default if none is found.
113
- */
114
- public function get( $option, $default = null ) {
115
- // notice, that `null` is not treated as a value
116
- if ( ! isset( $this->_options[$option] ) ) {
117
- return $default;
118
- }
119
- return $this->_options[$option]['value'];
120
- }
121
 
122
- /**
123
- * Set new value for previously initialized option.
124
- *
125
- * @param string $option Name of option to update.
126
- * @param mixed $value Actual value to be used for option.
127
- *
128
- * @throws Ai1ec_Settings_Exception
129
- *
130
- * @return Ai1ec_Settings Instance of self for chaining.
131
- */
132
- public function set( $option, $value ) {
133
- if ( ! isset( $this->_options[$option] ) ) {
134
- throw new Ai1ec_Settings_Exception(
135
- 'Option "' . $option . '" was not registered'
136
- );
137
- }
138
- if ( 'array' === $this->_options[$option]['type'] ) {
139
- if (
140
- ! is_array( $this->_options[$option]['value'] ) ||
141
- ! is_array( $value ) ||
142
- $value != $this->_options[$option]['value']
143
- ) {
144
- $this->_options[$option]['value'] = $value;
145
- $this->_change_update_status ( true );
146
- }
147
- } else if (
148
- (string)$value !== (string)$this->_options[$option]['value']
149
- ) {
150
- $this->_options[$option]['value'] = $value;
151
- $this->_change_update_status ( true );
152
- }
153
- return $this;
154
- }
155
 
156
- /**
157
- * Parse legacy values into new structure.
158
- *
159
- * @param mixed $values Expected legacy representation.
160
- *
161
- * @return array Parsed values representation, or input cast as array.
162
- */
163
- protected function _parse_legacy( Ai1ec_Settings $values ) {
164
- $variables = get_object_vars( $values );
165
- $default_tags_cat = array();
166
- $legacy = array();
167
- foreach ( $variables as $key => $value ) {
168
- if ( 'default_categories' === $key ) {
169
- $default_tags_cat['categories'] = $value;
170
- continue;
171
- }
172
- if ( 'default_tags' === $key ) {
173
- $default_tags_cat['tags'] = $value;
174
- continue;
175
- }
176
- $type = 'string';
177
- if ( is_array( $value ) ) {
178
- $type = 'array';
179
- } elseif ( is_bool( $value ) ) {
180
- $type = 'bool';
181
- } elseif ( is_int( $value ) ) {
182
- $type = 'int';
183
- }
184
- if ( isset( $this->_options[$key] ) ) {
185
- $this->_options[$key]['value'] = $value;
186
- } else {
187
- $legacy[$key] = array(
188
- 'value' => $value,
189
- 'type' => $type,
190
- 'legacy' => true,
191
- 'version' => AI1EC_VERSION
192
- );
193
- }
194
- }
195
- $this->_options['default_tags_categories']['value'] = $default_tags_cat;
196
- $this->_options['legacy_options'] = $legacy;
197
- }
198
 
199
- /**
200
- * Write object representation to persistence layer.
201
- *
202
- * Upon successful write to persistence layer the objects internal
203
- * state {@see self::$_updated} is updated respectively.
204
- *
205
- * @return bool Success.
206
- */
207
- public function persist() {
208
- $success = $this->_registry->get( 'model.option' )
209
- ->set( self::WP_OPTION_KEY, $this->_options );
210
- if ( $success ) {
211
- $this->_change_update_status( false );
212
- }
213
- return $success;
214
- }
215
 
216
- /**
217
- * Remove an option if is set.
218
- *
219
- * @param string $option
220
- */
221
- public function remove_option( $option ) {
222
- if ( isset( $this->_options[$option] ) ) {
223
- unset( $this->_options[$option] );
224
- $this->_change_update_status( true );
225
- }
226
- }
227
-
228
- /**
229
- * Do things needed on every plugin upgrade.
230
- */
231
- public function perform_upgrade_actions() {
232
- update_option( 'ai1ec_force_flush_rewrite_rules', true );
233
- update_option( 'ai1ec_invalidate_css_cache', true );
234
- update_option( Ai1ec_Theme_Loader::OPTION_FORCE_CLEAN, true );
235
- }
236
 
237
- /**
238
- * Hide an option by unsetting it's renderer
239
- *
240
- * @param string $option
241
- */
242
- public function hide_option( $option ) {
243
- if ( isset( $this->_options[$option] ) ) {
244
- unset( $this->_options[$option]['renderer'] );
245
- $this->_change_update_status( true );
246
- }
247
- }
248
 
249
- /**
250
- * Show an option by setting it's renderer
251
- *
252
- * @param string $option
253
- */
254
- public function show_option( $option, array $renderer ) {
255
- if ( isset( $this->_options[$option] ) ) {
256
- $this->_options[$option]['renderer'] = $renderer;
257
- $this->_change_update_status( true );
258
- }
259
- }
260
 
261
- /**
262
- * Check object state and update it's database representation as needed.
263
- *
264
- * @return void Destructor does not return.
265
- */
266
- public function shutdown() {
267
- if ( $this->_updated ) {
268
- $this->persist();
269
- }
270
- }
 
271
 
272
- /**
273
- * Observes wp_options changes. If any matches related setting then
274
- * updates that setting.
275
- *
276
- * @param string $option Name of the updated option.
277
- * @param mixed $old_value The old option value.
278
- * @param mixed $value The new option value.
279
- *
280
- * @return void Method does not return.
281
- */
282
- public function wp_options_observer( $option, $old_value, $value ) {
283
- $options = $this->get_options();
284
- if (
285
- self::WP_OPTION_KEY === $option ||
286
- empty( $options )
287
- ) {
288
- return;
289
- }
290
 
291
- if (
292
- isset( $options[$option] ) &&
293
- 'wp_option' === $options[$option]['type'] &&
294
- $this->get( $option ) !== $value
295
- ) {
296
- $this->set( $option, $value );
297
- }
298
- }
 
 
 
 
 
 
 
 
 
 
299
 
300
- /**
301
- * Initiate options map from storage.
302
- *
303
- * @return void Return from this method is ignored.
304
- */
305
- protected function _initialize() {
306
- $this->_set_standard_values();
307
- $values = $this->_registry->get( 'model.option' )
308
- ->get( self::WP_OPTION_KEY, array() );
309
- $this->_change_update_status( false );
310
- $test_version = false;
311
- if ( is_array( $values ) ) { // always assign existing values, if any
312
- $this->_options = $values;
313
- if ( isset( $values['calendar_page_id'] ) ) {
314
- $test_version = $values['calendar_page_id']['version'];
315
- }
316
- }
317
- // check for updated translations
318
- $this->_register_standard_values();
319
- if ( // process meta updates changes
320
- empty( $values ) || (
321
- false !== $test_version &&
322
- AI1EC_VERSION !== $test_version
323
- )
324
- ) {
325
- $this->_register_standard_values();
326
- $this->_update_name_translations();
327
- $this->_change_update_status( true );
328
- } else if ( $values instanceof Ai1ec_Settings ) { // process legacy
329
- $this->_parse_legacy( $values );
330
- $this->_change_update_status( true );
331
- }
332
- $this->_registry->get( 'controller.shutdown' )->register(
333
- array( $this, 'shutdown' )
334
- );
335
- }
336
 
337
- /**
338
- * Set the standard values for the options of the core plugin.
339
- *
340
- */
341
- protected function _set_standard_values() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  $this->_standard_options = array(
343
  'enabling_ticket_invitation_page' => array(
344
  'type' => 'string',
@@ -348,747 +348,747 @@ class Ai1ec_Settings extends Ai1ec_App {
348
  'type' => 'boolean',
349
  'default' => false,
350
  ),
351
- 'ai1ec_db_version' => array(
352
- 'type' => 'int',
353
- 'default' => false,
354
- ),
355
- 'feeds_page' => array(
356
- 'type' => 'string',
357
- 'default' => false,
358
- ),
359
- 'settings_page' => array(
360
- 'type' => 'string',
361
- 'default' => false,
362
- ),
363
- 'less_variables_page' => array(
364
- 'type' => 'string',
365
- 'default' => false,
366
- ),
367
- 'input_date_format' => array(
368
- 'type' => 'string',
369
- 'default' => 'd/m/yyyy',
370
- ),
371
- 'plugins_options' => array(
372
- 'type' => 'array',
373
- 'default' => array(),
374
- ),
375
- 'show_tracking_popup' => array(
376
- 'type' => 'deprecated',
377
- 'default' => true,
378
- ),
379
- 'ticketing_message' => array(
380
- 'type' => 'string',
381
- 'default' => false,
382
- ),
383
- 'ticketing_token' => array(
384
- 'type' => 'string',
385
- 'default' => '',
386
- ),
387
- 'ticketing_enabled' => array(
388
- 'type' => 'boolean',
389
- 'default' => false,
390
- ),
391
- 'ticketing_calendar_id' => array(
392
- 'type' => 'int',
393
- 'default' => 0,
394
- ),
395
- 'calendar_page_id' => array(
396
- 'type' => 'mixed',
397
- 'renderer' => array(
398
- 'class' => 'calendar-page-selector',
399
- 'tab' => 'viewing-events',
400
- 'item' => 'viewing-events',
401
- 'label' => Ai1ec_I18n::__( 'Calendar page' )
402
- ),
403
- 'default' => false,
404
- ),
405
- 'week_start_day' => array(
406
- 'type' => 'int',
407
- 'renderer' => array(
408
- 'class' => 'select',
409
- 'tab' => 'viewing-events',
410
- 'item' => 'viewing-events',
411
- 'label' => Ai1ec_I18n::__( 'Week starts on' ),
412
- 'options' => 'get_weekdays',
413
- ),
414
- 'default' => $this->_registry->get( 'model.option' )->get(
415
- 'start_of_week'
416
- ),
417
- ),
418
- 'enabled_views' => array(
419
- 'type' => 'array',
420
- 'renderer' => array(
421
- 'class' => 'enabled-views',
422
- 'tab' => 'viewing-events',
423
- 'item' => 'viewing-events',
424
- 'label' => Ai1ec_I18n::__( 'Available views' ),
425
- ),
426
- 'default' => array(
427
- 'agenda' => array(
428
- 'enabled' => true,
429
- 'default' => true,
430
- 'enabled_mobile' => true,
431
- 'default_mobile' => true,
432
- 'longname' => _n_noop(
433
- 'Agenda',
434
- 'Agenda',
435
- AI1EC_PLUGIN_NAME
436
- ),
437
- ),
438
- 'oneday' => array(
439
- 'enabled' => true,
440
- 'default' => false,
441
- 'enabled_mobile' => true,
442
- 'default_mobile' => false,
443
- 'longname' => _n_noop(
444
- 'Day',
445
- 'Day',
446
- AI1EC_PLUGIN_NAME
447
- ),
448
- ),
449
- 'month' => array(
450
- 'enabled' => true,
451
- 'default' => false,
452
- 'enabled_mobile' => true,
453
- 'default_mobile' => false,
454
- 'longname' => _n_noop(
455
- 'Month',
456
- 'Month',
457
- AI1EC_PLUGIN_NAME
458
- ),
459
- ),
460
- 'week' => array(
461
- 'enabled' => true,
462
- 'default' => false,
463
- 'enabled_mobile' => true,
464
- 'default_mobile' => false,
465
- 'longname' => _n_noop(
466
- 'Week',
467
- 'Week',
468
- AI1EC_PLUGIN_NAME
469
- ),
470
- ),
471
- ),
472
- ),
473
- 'timezone_string' => array(
474
- 'type' => 'wp_option',
475
- 'renderer' => array(
476
- 'class' => 'select',
477
- 'tab' => 'viewing-events',
478
- 'item' => 'viewing-events',
479
- 'label' => Ai1ec_I18n::__( 'Timezone' ),
480
- 'options' => 'Ai1ec_Date_Timezone:get_timezones',
481
- ),
482
- 'default' => $this->_registry->get( 'model.option' )->get(
483
- 'timezone_string'
484
- ),
485
- ),
486
- 'default_tags_categories' => array(
487
- 'type' => 'array',
488
- 'renderer' => array(
489
- 'class' => 'tags-categories',
490
- 'tab' => 'viewing-events',
491
- 'item' => 'viewing-events',
492
- 'label' => Ai1ec_I18n::__( 'Preselected calendar filters' ),
493
- 'help' => Ai1ec_I18n::__(
494
- 'To clear, hold &#8984;/<abbr class="initialism">CTRL</abbr> and click selection.'
495
- )
496
- ),
497
- 'default' => array(
498
- 'categories' => array(),
499
- 'tags' => array(),
500
- ),
501
- ),
502
- 'exact_date' => array(
503
- 'type' => 'string',
504
- 'renderer' => array(
505
- 'class' => 'input',
506
- 'tab' => 'viewing-events',
507
- 'item' => 'viewing-events',
508
- 'label' => Ai1ec_I18n::__( 'Default calendar start date (optional)' ),
509
- 'type' => 'date',
510
- ),
511
- 'default' => '',
512
- ),
513
- 'agenda_events_per_page' => array(
514
- 'type' => 'int',
515
- 'renderer' => array(
516
- 'class' => 'input',
517
- 'tab' => 'viewing-events',
518
- 'item' => 'viewing-events',
519
- 'label' => Ai1ec_I18n::__( 'Agenda pages show at most' ),
520
- 'type' => 'append',
521
- 'append' => 'events',
522
- 'validator' => 'numeric',
523
- ),
524
- 'default' => 10,
525
- ),
526
- 'week_view_starts_at' => array(
527
- 'type' => 'int',
528
- 'renderer' => array(
529
- 'class' => 'input',
530
- 'tab' => 'viewing-events',
531
- 'item' => 'viewing-events',
532
- 'label' => Ai1ec_I18n::__( 'Week/Day view starts at' ),
533
- 'type' => 'append',
534
- 'append' => 'hrs',
535
- 'validator' => 'numeric',
536
- ),
537
- 'default' => 8,
538
- ),
539
- 'week_view_ends_at' => array(
540
- 'type' => 'int',
541
- 'renderer' => array(
542
- 'class' => 'input',
543
- 'tab' => 'viewing-events',
544
- 'item' => 'viewing-events',
545
- 'label' => Ai1ec_I18n::__( 'Week/Day view ends at' ),
546
- 'type' => 'append',
547
- 'append' => 'hrs',
548
- 'validator' => 'numeric',
549
- ),
550
- 'default' => 24,
551
- ),
552
- 'google_maps_api_key' => array(
553
- 'type' => 'string',
554
- 'renderer' => array(
555
- 'class' => 'input',
556
- 'tab' => 'viewing-events',
557
- 'item' => 'viewing-events',
558
- 'label' => Ai1ec_I18n::__(
559
- '<span class="ai1ec-tooltip-toggle"
560
- data-original-title="Google may request for an API key in order to show the map">
561
- Google Maps API Key</span> (<a target="_blank" href="https://developers.google.com/maps/documentation/javascript/get-api-key#get-an-api-key">Get an API key</a>)'
562
- ),
563
- 'type' => 'normal'
564
- ),
565
- 'default' => '',
566
- ),
567
- 'month_word_wrap' => array(
568
- 'type' => 'bool',
569
- 'renderer' => array(
570
- 'class' => 'checkbox',
571
- 'tab' => 'viewing-events',
572
- 'item' => 'viewing-events',
573
- 'label' => Ai1ec_I18n::__(
574
- '<strong>Word-wrap event stubs</strong> in Month view'
575
- ),
576
- 'help' => Ai1ec_I18n::__(
577
- 'Only applies to events that span a single day.'
578
- ),
579
- ),
580
- 'default' => false,
581
- ),
582
- 'agenda_include_entire_last_day' => array(
583
- 'type' => 'bool',
584
- 'renderer' => array(
585
- 'class' => 'checkbox',
586
- 'tab' => 'viewing-events',
587
- 'item' => 'viewing-events',
588
- 'label' => Ai1ec_I18n::__(
589
- 'In <span class="ai1ec-tooltip-toggle"
590
- data-original-title="These include Agenda view,
591
- the Upcoming Events widget, and some extended views.">
592
- Agenda-like views</span>, <strong>include all events
593
- from last day shown</strong>'
594
- )
595
- ),
596
- 'default' => false,
597
- ),
598
- 'agenda_events_expanded' => array(
599
- 'type' => 'bool',
600
- 'renderer' => array(
601
- 'class' => 'checkbox',
602
- 'tab' => 'viewing-events',
603
- 'item' => 'viewing-events',
604
- 'label' => Ai1ec_I18n::__(
605
- 'Keep all events <strong>expanded</strong> in Agenda view'
606
- )
607
- ),
608
- 'default' => false,
609
- ),
610
- 'show_year_in_agenda_dates' => array(
611
- 'type' => 'bool',
612
- 'renderer' => array(
613
- 'class' => 'checkbox',
614
- 'tab' => 'viewing-events',
615
- 'item' => 'viewing-events',
616
- 'label' => Ai1ec_I18n::__(
617
- '<strong>Show year</strong> in calendar date labels'
618
- )
619
- ),
620
- 'default' => false,
621
- ),
622
- 'show_location_in_title' => array(
623
- 'type' => 'bool',
624
- 'renderer' => array(
625
- 'class' => 'checkbox',
626
- 'tab' => 'viewing-events',
627
- 'item' => 'viewing-events',
628
- 'label' => Ai1ec_I18n::__(
629
- '<strong>Show location in event titles</strong> in calendar views'
630
- )
631
- ),
632
- 'default' => true,
633
- ),
634
- 'exclude_from_search' => array(
635
- 'type' => 'bool',
636
- 'renderer' => array(
637
- 'class' => 'checkbox',
638
- 'tab' => 'viewing-events',
639
- 'item' => 'viewing-events',
640
- 'label' => Ai1ec_I18n::__(
641
- '<strong>Exclude</strong> events from search results'
642
- )
643
- ),
644
- 'default' => false,
645
- ),
646
- 'turn_off_subscription_buttons' => array(
647
- 'type' => 'bool',
648
- 'renderer' => array(
649
- 'class' => 'checkbox',
650
- 'tab' => 'viewing-events',
651
- 'item' => 'viewing-events',
652
- 'label' => Ai1ec_I18n::__(
653
- 'Hide <strong>Subscribe</strong>/<strong>Add to Calendar</strong> buttons in calendar and single event views '
654
- )
655
- ),
656
- 'default' => false,
657
- ),
658
- 'disable_get_calendar_button' => array(
659
- 'type' => 'bool',
660
- 'renderer' => array(
661
- 'class' => 'checkbox',
662
- 'tab' => 'viewing-events',
663
- 'item' => 'viewing-events',
664
- 'label' => Ai1ec_I18n::__(
665
- 'Hide <strong>Get a Timely Calendar</strong> button'
666
- )
667
- ),
668
- 'default' => true,
669
- ),
670
- 'hide_maps_until_clicked' => array(
671
- 'type' => 'bool',
672
- 'renderer' => array(
673
- 'class' => 'checkbox',
674
- 'tab' => 'viewing-events',
675
- 'item' => 'viewing-events',
676
- 'label' => Ai1ec_I18n::__(
677
- ' Hide <strong>Google Maps</strong> until clicked'
678
- )
679
- ),
680
- 'default' => false,
681
- ),
682
- 'affix_filter_menu' => array(
683
- 'type' => 'bool',
684
- 'renderer' => array(
685
- 'class' => 'checkbox',
686
- 'tab' => 'viewing-events',
687
- 'item' => 'viewing-events',
688
- 'label' => Ai1ec_I18n::__(
689
- ' <strong>Affix filter menu</strong> to top of window when it scrolls out of view'
690
- ),
691
- 'help' => Ai1ec_I18n::__(
692
- 'Only applies to first visible calendar found on the page.'
693
- ),
694
- ),
695
- 'default' => false,
696
- ),
697
- 'affix_vertical_offset_md' => array(
698
- 'type' => 'int',
699
- 'renderer' => array(
700
- 'class' => 'input',
701
- 'tab' => 'viewing-events',
702
- 'item' => 'viewing-events',
703
- 'label' => Ai1ec_I18n::__( 'Offset affixed filter bar vertically by' ),
704
- 'type' => 'append',
705
- 'append' => 'pixels',
706
- 'validator' => 'numeric',
707
- ),
708
- 'default' => 0,
709
- ),
710
- 'affix_vertical_offset_lg' => array(
711
- 'type' => 'int',
712
- 'renderer' => array(
713
- 'class' => 'input',
714
- 'tab' => 'viewing-events',
715
- 'item' => 'viewing-events',
716
- 'label' =>
717
- '<i class="ai1ec-fa ai1ec-fa-lg ai1ec-fa-fw ai1ec-fa-desktop"></i> ' .
718
- Ai1ec_I18n::__( 'Wide screens only (&#8805; 1200px)' ),
719
- 'type' => 'append',
720
- 'append' => 'pixels',
721
- 'validator' => 'numeric',
722
- ),
723
- 'default' => 0,
724
- ),
725
- 'affix_vertical_offset_sm' => array(
726
- 'type' => 'int',
727
- 'renderer' => array(
728
- 'class' => 'input',
729
- 'tab' => 'viewing-events',
730
- 'item' => 'viewing-events',
731
- 'label' =>
732
- '<i class="ai1ec-fa ai1ec-fa-lg ai1ec-fa-fw ai1ec-fa-tablet"></i> ' .
733
- Ai1ec_I18n::__( 'Tablets only (< 980px)' ),
734
- 'type' => 'append',
735
- 'append' => 'pixels',
736
- 'validator' => 'numeric',
737
- ),
738
- 'default' => 0,
739
- ),
740
- 'affix_vertical_offset_xs' => array(
741
- 'type' => 'int',
742
- 'renderer' => array(
743
- 'class' => 'input',
744
- 'tab' => 'viewing-events',
745
- 'item' => 'viewing-events',
746
- 'label' =>
747
- '<i class="ai1ec-fa ai1ec-fa-lg ai1ec-fa-fw ai1ec-fa-mobile"></i> ' .
748
- Ai1ec_I18n::__( 'Phones only (< 768px)' ),
749
- 'type' => 'append',
750
- 'append' => 'pixels',
751
- 'validator' => 'numeric',
752
- ),
753
- 'default' => 0,
754
- ),
755
- 'strict_compatibility_content_filtering' => array(
756
- 'type' => 'bool',
757
- 'renderer' => array(
758
- 'class' => 'checkbox',
759
- 'tab' => 'viewing-events',
760
- 'item' => 'viewing-events',
761
- 'label' => Ai1ec_I18n::__(
762
- 'Strict compatibility content filtering'
763
- ),
764
- ),
765
- 'default' => false,
766
- ),
767
- 'hide_featured_image' => array(
768
- 'type' => 'bool',
769
- 'renderer' => array(
770
- 'class' => 'checkbox',
771
- 'tab' => 'viewing-events',
772
- 'item' => 'viewing-events',
773
- 'label' => Ai1ec_I18n::__(
774
- ' <strong>Hide featured image</strong> from event details page'
775
- ),
776
- 'help' => Ai1ec_I18n::__(
777
- "Select this option if your theme already displays each post's featured image."
778
- ),
779
- ),
780
- 'default' => false,
781
- ),
782
- 'input_date_format' => array(
783
- 'type' => 'string',
784
- 'renderer' => array(
785
- 'class' => 'select',
786
- 'tab' => 'editing-events',
787
- 'label' => Ai1ec_I18n::__(
788
- 'Input dates in this format'
789
- ),
790
- 'options' => array(
791
- array(
792
- 'text' => Ai1ec_I18n::__( 'Default (d/m/yyyy)' ),
793
- 'value' => 'def'
794
- ),
795
- array(
796
- 'text' => Ai1ec_I18n::__( 'US (m/d/yyyy)' ),
797
- 'value' => 'us'
798
- ),
799
- array(
800
- 'text' => Ai1ec_I18n::__( 'ISO 8601 (yyyy-m-d)' ),
801
- 'value' => 'iso'
802
- ),
803
- array(
804
- 'text' => Ai1ec_I18n::__( 'Dotted (m.d.yyyy)' ),
805
- 'value' => 'dot'
806
- ),
807
- ),
808
- ),
809
- 'default' => 'def',
810
- ),
811
- 'input_24h_time' => array(
812
- 'type' => 'bool',
813
- 'renderer' => array(
814
- 'class' => 'checkbox',
815
- 'tab' => 'editing-events',
816
- 'label' => Ai1ec_I18n::__(
817
- ' Use <strong>24h time</strong> in time pickers'
818
- )
819
- ),
820
- 'default' => false,
821
- ),
822
- 'disable_autocompletion' => array(
823
- 'type' => 'bool',
824
- 'renderer' => array(
825
- 'class' => 'checkbox',
826
- 'tab' => 'editing-events',
827
- 'label' => Ai1ec_I18n::__(
828
- '<strong>Disable address autocomplete</strong> function'
829
- )
830
- ),
831
- 'default' => false,
832
- ),
833
- 'geo_region_biasing' => array(
834
- 'type' => 'bool',
835
- 'renderer' => array(
836
- 'class' => 'checkbox',
837
- 'tab' => 'editing-events',
838
- 'label' => Ai1ec_I18n::__(
839
- 'Use the configured <strong>region</strong> (WordPress locale) to bias the address autocomplete function '
840
- )
841
- ),
842
- 'default' => false,
843
- ),
844
- 'show_publish_button' => array(
845
- 'type' => 'deprecated',
846
- 'renderer' => null,
847
- 'default' => false,
848
- ),
849
- 'show_create_event_button' => array(
850
- 'type' => 'bool',
851
- 'renderer' => array(
852
- 'class' => 'checkbox',
853
- 'tab' => 'extensions',
854
- 'label' => Ai1ec_I18n::__(
855
- ' Show the old <strong>Post Your Event</strong> button above the calendar to privileged users'
856
- ),
857
- 'help' => Ai1ec_I18n::__(
858
- 'Install the <a target="_blank" href="https://time.ly/">Interactive Frontend Extension</a> for the <strong>frontend Post Your Event form</strong>.'
859
- ),
860
- ),
861
- 'default' => true,
862
- ),
863
- 'embedding' => array(
864
- 'type' => 'html',
865
- 'renderer' => array(
866
- 'class' => 'html',
867
- 'tab' => 'advanced',
868
- 'item' => 'embedded-views',
869
- ),
870
- 'default' => null,
871
- ),
872
- 'calendar_css_selector' => array(
873
- 'type' => 'string',
874
- 'renderer' => array(
875
- 'class' => 'input',
876
- 'tab' => 'advanced',
877
- 'item' => 'advanced',
878
- 'label' => Ai1ec_I18n::__( 'Move calendar into this DOM element' ),
879
- 'type' => 'normal',
880
- 'help' => Ai1ec_I18n::__(
881
- 'Optional. Use this JavaScript-based shortcut to place the
882
- calendar a DOM element other than the usual page content container
883
- if you are unable to create an appropriate page template
884
- for the calendar page. To use, enter a
885
- <a target="_blank" href="https://api.jquery.com/category/selectors/">
886
- jQuery selector</a> that evaluates to a single DOM element.
887
- Any existing markup found within the target will be replaced
888
- by the calendar.'
889
- ),
890
- ),
891
- 'default' => '',
892
- ),
893
- 'skip_in_the_loop_check' => array(
894
- 'type' => 'bool',
895
- 'renderer' => array(
896
- 'class' => 'checkbox',
897
- 'tab' => 'advanced',
898
- 'item' => 'advanced',
899
- 'label' => Ai1ec_I18n::__(
900
- '<strong>Skip <tt>in_the_loop()</tt> check </strong> that protects against multiple calendar output'
901
- ),
902
- 'help' => Ai1ec_I18n::__(
903
- 'Try enabling this option if your calendar does not appear on the calendar page. It is needed for compatibility with a small number of themes that call <tt>the_content()</tt> from outside of The Loop. Leave disabled otherwise.'
904
- ),
905
- ),
906
- 'default' => false,
907
- ),
908
- 'disable_gzip_compression' => array(
909
- 'type' => 'bool',
910
- 'renderer' => array(
911
- 'class' => 'checkbox',
912
- 'tab' => 'advanced',
913
- 'item' => 'advanced',
914
- 'label' => Ai1ec_I18n::__(
915
- 'Disable <strong>gzip</strong> compression.'
916
- ),
917
- 'help' => Ai1ec_I18n::__(
918
- 'Use this option if calendar is unresponsive. <a target="_blank" href="https://time.ly/document/user-guide/troubleshooting/disable-gzip-compression/">Read more</a> about the issue. (From version 2.1 onwards, gzip is disabled by default for maximum compatibility.)'
919
- ),
920
- ),
921
- 'default' => true,
922
- ),
923
- 'ai1ec_use_frontend_rendering' => array(
924
- 'type' => 'bool',
925
- 'renderer' => array(
926
- 'class' => 'checkbox',
927
- 'tab' => 'advanced',
928
- 'item' => 'advanced',
929
- 'label' => Ai1ec_I18n::__(
930
- 'Use frontend rendering.'
931
- ),
932
- 'help' => Ai1ec_I18n::__(
933
- 'Renders calendar views on the client rather than the server; can improve performance.'
934
- ),
935
- ),
936
- 'default' => true,
937
- ),
938
- 'cache_dynamic_js' => array(
939
- 'type' => 'bool',
940
- 'renderer' => array(
941
- 'class' => 'checkbox',
942
- 'tab' => 'advanced',
943
- 'item' => 'advanced',
944
- 'label' => Ai1ec_I18n::__(
945
- 'Use advanced JS cache.'
946
- ),
947
- 'help' => Ai1ec_I18n::__(
948
- 'Cache dynamically generated JS files. Improves performance.'
949
- ),
950
- ),
951
- 'default' => true,
952
- ),
953
- 'render_css_as_link' => array(
954
- 'type' => 'bool',
955
- 'renderer' => array(
956
- 'class' => 'checkbox',
957
- 'tab' => 'advanced',
958
- 'item' => 'advanced',
959
- 'label' => Ai1ec_I18n::__(
960
- '<strong>Link CSS</strong> in <code>&lt;head&gt;</code> section when file cache is unavailable.'
961
- ),
962
- 'help' => Ai1ec_I18n::__(
963
- 'Use this option if file cache is unavailable and you would prefer to serve CSS as a link rather than have it output inline.'
964
- ),
965
- ),
966
- 'default' => false,
967
- ),
968
- 'edit_robots_txt' => array(
969
- 'type' => 'string',
970
- 'renderer' => array(
971
- 'class' => 'textarea',
972
- 'tab' => 'advanced',
973
- 'item' => 'advanced',
974
- 'label' => Ai1ec_I18n::__( 'Current <strong>robots.txt</strong> on this site' ),
975
- 'type' => 'normal',
976
- 'rows' => 6,
977
- 'readonly' => 'readonly',
978
- 'help' => Ai1ec_I18n::__(
979
- 'The Robot Exclusion Standard, also known as the Robots Exclusion Protocol or
980
- <code><a href="https://en.wikipedia.org/wiki/Robots.txt" target="_blank">robots.txt</a></code>
981
- protocol, is a convention for cooperating web crawlers and other web robots
982
- about accessing all or part of a website that is otherwise publicly viewable.
983
- You can change it manually by editing <code>robots.txt</code> in your root WordPress directory.'
984
- ),
985
- ),
986
- 'default' => '',
987
- ),
988
- 'allow_statistics' => array(
989
- 'type' => 'bool',
990
- 'renderer' => array(
991
- 'class' => 'checkbox',
992
- 'tab' => 'advanced',
993
- 'item' => 'advanced',
994
- 'label' => sprintf(
995
- Ai1ec_I18n::__(
996
- '<strong>Publicize, promote, and share my events</strong> marked as public on the Timely network. (<a href="%s" target="_blank">Learn more &#187;</a>)'
997
- ),
998
- 'https://time.ly/event-search-calendar'
999
- ),
1000
- ),
1001
- 'default' => false,
1002
- ),
1003
- 'legacy_options' => array(
1004
- 'type' => 'legacy_options',
1005
- 'default' => null,
1006
- ),
1007
- 'ics_cron_freq' => array(
1008
- 'type' => 'string',
1009
- 'default' => 'hourly',
1010
- ),
1011
- 'twig_cache' => array(
1012
- 'type' => 'string',
1013
- 'renderer' => array(
1014
- 'class' => 'cache',
1015
- 'tab' => 'advanced',
1016
- 'item' => 'cache',
1017
- 'label' => sprintf(
1018
- Ai1ec_I18n::__(
1019
- 'Templates cache improves site performance'
1020
- )
1021
- ),
1022
- ),
1023
- 'default' => '',
1024
- ),
1025
- 'always_use_calendar_timezone' => array(
1026
- 'type' => 'bool',
1027
- 'renderer' => array(
1028
- 'class' => 'checkbox',
1029
- 'tab' => 'viewing-events',
1030
- 'item' => 'viewing-events',
1031
- 'label' => Ai1ec_I18n::__(
1032
- 'Display events in <strong>calendar time zone</strong>'
1033
- ),
1034
- 'help' => Ai1ec_I18n::__(
1035
- 'If this box is checked events will appear in the calendar time zone with time zone information displayed on the event details page.'
1036
- ),
1037
- ),
1038
- 'default' => false,
1039
- ),
1040
- );
1041
- }
1042
 
1043
- /**
1044
- * Register the standard setting values.
1045
- *
1046
- * @return void Method doesn't return.
1047
- */
1048
- protected function _register_standard_values() {
1049
- foreach ( $this->_standard_options as $key => $option ) {
1050
- $renderer = null;
1051
- $value = $option['default'];
1052
- if ( isset( $option['renderer'] ) ) {
1053
- $renderer = $option['renderer'];
1054
- }
1055
- $this->register(
1056
- $key,
1057
- $value,
1058
- $option['type'],
1059
- $renderer,
1060
- AI1EC_VERSION
1061
- );
1062
- }
1063
- }
1064
 
1065
- /**
1066
- * Update translated strings, after introduction of `_noop` functions.
1067
- *
1068
- * @return void
1069
- */
1070
- protected function _update_name_translations() {
1071
- $translations = $this->_standard_options['enabled_views']['default'];
1072
- $current = $this->get( 'enabled_views' );
1073
- foreach ( $current as $key => $view ) {
1074
- if ( isset( $translations[$key] ) ) {
1075
- $current[$key]['longname'] = $translations[$key]['longname'];
1076
- }
1077
- }
1078
- $this->set( 'enabled_views', $current );
1079
- }
1080
 
1081
- /**
1082
- * Change `updated` flag value.
1083
- *
1084
- * @param bool $new_status Status to change to.
1085
- *
1086
- * @return bool Previous status flag value.
1087
- */
1088
- protected function _change_update_status( $new_status ) {
1089
- $previous = $this->_updated;
1090
- $this->_updated = (bool)$new_status;
1091
- return $previous;
1092
- }
1093
 
1094
  }
10
  */
11
  class Ai1ec_Settings extends Ai1ec_App {
12
 
13
+ /**
14
+ * @constant string Name of WordPress options key used to store settings.
15
+ */
16
+ const WP_OPTION_KEY = 'ai1ec_settings';
17
 
18
+ /**
19
+ * @var array Map of value names and their representations.
20
+ */
21
+ protected $_options = array();
22
 
23
+ /**
24
+ * @var bool Indicator for modified object state.
25
+ */
26
+ protected $_updated = false;
27
 
28
+ /**
29
+ * @var array The core options of the plugin.
30
+ */
31
+ protected $_standard_options;
32
 
33
+ /**
34
+ * Register new option to be used.
35
+ *
36
+ * @param string $option Name of option.
37
+ * @param mixed $value The value.
38
+ * @param string $type Option type to be used for validation.
39
+ * @param string $renderer Name of class to render the option.
40
+ *
41
+ * @return Ai1ec_Settings Instance of self for chaining.
42
+ */
43
+ public function register(
44
+ $option,
45
+ $value,
46
+ $type,
47
+ $renderer,
48
+ $version = '2.0.0'
49
+ ) {
50
 
51
+ if ( 'deprecated' === $type ) {
52
+ unset( $this->_options[$option] );
53
+ } else if (
54
+ ! isset( $this->_options[$option] ) ||
55
+ ! isset( $this->_options[$option]['version'] ) ||
56
+ (string)$this->_options[$option]['version'] !== (string)$version ||
57
+ (
58
+ isset( $renderer['label'] ) &&
59
+ isset( $this->_options[$option]['renderer'] ) &&
60
+ (string)$this->_options[$option]['renderer']['label'] !== (string)$renderer['label']
61
+ ) ||
62
+ (
63
+ isset( $renderer['help'] ) &&
64
+ ( ! isset( $this->_options[$option]['renderer']['help'] ) || // handle the case when you are adding help
65
+ (string)$this->_options[$option]['renderer']['help'] !== (string)$renderer['help'] )
66
+ )
67
+ ) {
68
+ $this->_options[$option] = array(
69
+ 'value' => ( isset( $this->_options[$option] ) )
70
+ ? $this->_options[$option]['value']
71
+ : $value,
72
+ 'type' => $type,
73
+ 'legacy' => false,
74
+ 'version' => $version,
75
+ );
76
+ if ( null !== $renderer ) {
77
+ $this->_options[$option]['renderer'] = $renderer;
78
+ }
79
+ }
80
+ return $this;
81
+ }
82
 
83
+ /**
84
+ * Gets the options.
85
+ *
86
+ * @return array:
87
+ */
88
+ public function get_options() {
89
+ return $this->_options;
90
+ }
91
 
92
+ /**
93
+ * Get field options as registered.
94
+ *
95
+ * @param string $option Name of option field to describe.
96
+ *
97
+ * @return array|null Description or null if nothing is found.
98
+ */
99
+ public function describe( $option ) {
100
+ if ( ! isset( $this->_options[$option] ) ) {
101
+ return null;
102
+ }
103
+ return $this->_options[$option];
104
+ }
105
 
106
+ /**
107
+ * Get value for option.
108
+ *
109
+ * @param string $option Name of option to get value for.
110
+ * @param mixed $default Value to return if option is not found.
111
+ *
112
+ * @return mixed Value or $default if none is found.
113
+ */
114
+ public function get( $option, $default = null ) {
115
+ // notice, that `null` is not treated as a value
116
+ if ( ! isset( $this->_options[$option] ) ) {
117
+ return $default;
118
+ }
119
+ return $this->_options[$option]['value'];
120
+ }
121
 
122
+ /**
123
+ * Set new value for previously initialized option.
124
+ *
125
+ * @param string $option Name of option to update.
126
+ * @param mixed $value Actual value to be used for option.
127
+ *
128
+ * @throws Ai1ec_Settings_Exception
129
+ *
130
+ * @return Ai1ec_Settings Instance of self for chaining.
131
+ */
132
+ public function set( $option, $value ) {
133
+ if ( ! isset( $this->_options[$option] ) ) {
134
+ throw new Ai1ec_Settings_Exception(
135
+ 'Option "' . $option . '" was not registered'
136
+ );
137
+ }
138
+ if ( 'array' === $this->_options[$option]['type'] ) {
139
+ if (
140
+ ! is_array( $this->_options[$option]['value'] ) ||
141
+ ! is_array( $value ) ||
142
+ $value != $this->_options[$option]['value']
143
+ ) {
144
+ $this->_options[$option]['value'] = $value;
145
+ $this->_change_update_status ( true );
146
+ }
147
+ } else if (
148
+ (string)$value !== (string)$this->_options[$option]['value']
149
+ ) {
150
+ $this->_options[$option]['value'] = $value;
151
+ $this->_change_update_status ( true );
152
+ }
153
+ return $this;
154
+ }
155
 
156
+ /**
157
+ * Parse legacy values into new structure.
158
+ *
159
+ * @param mixed $values Expected legacy representation.
160
+ *
161
+ * @return array Parsed values representation, or input cast as array.
162
+ */
163
+ protected function _parse_legacy( Ai1ec_Settings $values ) {
164
+ $variables = get_object_vars( $values );
165
+ $default_tags_cat = array();
166
+ $legacy = array();
167
+ foreach ( $variables as $key => $value ) {
168
+ if ( 'default_categories' === $key ) {
169
+ $default_tags_cat['categories'] = $value;
170
+ continue;
171
+ }
172
+ if ( 'default_tags' === $key ) {
173
+ $default_tags_cat['tags'] = $value;
174
+ continue;
175
+ }
176
+ $type = 'string';
177
+ if ( is_array( $value ) ) {
178
+ $type = 'array';
179
+ } elseif ( is_bool( $value ) ) {
180
+ $type = 'bool';
181
+ } elseif ( is_int( $value ) ) {
182
+ $type = 'int';
183
+ }
184
+ if ( isset( $this->_options[$key] ) ) {
185
+ $this->_options[$key]['value'] = $value;
186
+ } else {
187
+ $legacy[$key] = array(
188
+ 'value' => $value,
189
+ 'type' => $type,
190
+ 'legacy' => true,
191
+ 'version' => AI1EC_VERSION
192
+ );
193
+ }
194
+ }
195
+ $this->_options['default_tags_categories']['value'] = $default_tags_cat;
196
+ $this->_options['legacy_options'] = $legacy;
197
+ }
198
 
199
+ /**
200
+ * Write object representation to persistence layer.
201
+ *
202
+ * Upon successful write to persistence layer the objects internal
203
+ * state {@see self::$_updated} is updated respectively.
204
+ *
205
+ * @return bool Success.
206
+ */
207
+ public function persist() {
208
+ $success = $this->_registry->get( 'model.option' )
209
+ ->set( self::WP_OPTION_KEY, $this->_options );
210
+ if ( $success ) {
211
+ $this->_change_update_status( false );
212
+ }
213
+ return $success;
214
+ }
215
 
216
+ /**
217
+ * Remove an option if is set.
218
+ *
219
+ * @param string $option
220
+ */
221
+ public function remove_option( $option ) {
222
+ if ( isset( $this->_options[$option] ) ) {
223
+ unset( $this->_options[$option] );
224
+ $this->_change_update_status( true );
225
+ }
226
+ }
 
 
 
 
 
 
 
 
 
227
 
228
+ /**
229
+ * Do things needed on every plugin upgrade.
230
+ */
231
+ public function perform_upgrade_actions() {
232
+ update_option( 'ai1ec_force_flush_rewrite_rules', true );
233
+ update_option( 'ai1ec_invalidate_css_cache', true );
234
+ update_option( Ai1ec_Theme_Loader::OPTION_FORCE_CLEAN, true );
235
+ }
 
 
 
236
 
237
+ /**
238
+ * Hide an option by unsetting it's renderer
239
+ *
240
+ * @param string $option
241
+ */
242
+ public function hide_option( $option ) {
243
+ if ( isset( $this->_options[$option] ) ) {
244
+ unset( $this->_options[$option]['renderer'] );
245
+ $this->_change_update_status( true );
246
+ }
247
+ }
248
 
249
+ /**
250
+ * Show an option by setting it's renderer
251
+ *
252
+ * @param string $option
253
+ */
254
+ public function show_option( $option, array $renderer ) {
255
+ if ( isset( $this->_options[$option] ) ) {
256
+ $this->_options[$option]['renderer'] = $renderer;
257
+ $this->_change_update_status( true );
258
+ }
259
+ }
260
 
261
+ /**
262
+ * Check object state and update it's database representation as needed.
263
+ *
264
+ * @return void Destructor does not return.
265
+ */
266
+ public function shutdown() {
267
+ if ( $this->_updated ) {
268
+ $this->persist();
269
+ }
270
+ }
 
 
 
 
 
 
 
 
271
 
272
+ /**
273
+ * Observes wp_options changes. If any matches related setting then
274
+ * updates that setting.
275
+ *
276
+ * @param string $option Name of the updated option.
277
+ * @param mixed $old_value The old option value.
278
+ * @param mixed $value The new option value.
279
+ *
280
+ * @return void Method does not return.
281
+ */
282
+ public function wp_options_observer( $option, $old_value, $value ) {
283
+ $options = $this->get_options();
284
+ if (
285
+ self::WP_OPTION_KEY === $option ||
286
+ empty( $options )
287
+ ) {
288
+ return;
289
+ }
290
 
291
+ if (
292
+ isset( $options[$option] ) &&
293
+ 'wp_option' === $options[$option]['type'] &&
294
+ $this->get( $option ) !== $value
295
+ ) {
296
+ $this->set( $option, $value );
297
+ }
298
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
+ /**
301
+ * Initiate options map from storage.
302
+ *
303
+ * @return void Return from this method is ignored.
304
+ */
305
+ protected function _initialize() {
306
+ $this->_set_standard_values();
307
+ $values = $this->_registry->get( 'model.option' )
308
+ ->get( self::WP_OPTION_KEY, array() );
309
+ $this->_change_update_status( false );
310
+ $test_version = false;
311
+ if ( is_array( $values ) ) { // always assign existing values, if any
312
+ $this->_options = $values;
313
+ if ( isset( $values['calendar_page_id'] ) ) {
314
+ $test_version = $values['calendar_page_id']['version'];
315
+ }
316
+ }
317
+ // check for updated translations
318
+ $this->_register_standard_values();
319
+ if ( // process meta updates changes
320
+ empty( $values ) || (
321
+ false !== $test_version &&
322
+ AI1EC_VERSION !== $test_version
323
+ )
324
+ ) {
325
+ $this->_register_standard_values();
326
+ $this->_update_name_translations();
327
+ $this->_change_update_status( true );
328
+ } else if ( $values instanceof Ai1ec_Settings ) { // process legacy
329
+ $this->_parse_legacy( $values );
330
+ $this->_change_update_status( true );
331
+ }
332
+ $this->_registry->get( 'controller.shutdown' )->register(
333
+ array( $this, 'shutdown' )
334
+ );
335
+ }
336
+
337
+ /**
338
+ * Set the standard values for the options of the core plugin.
339
+ *
340
+ */
341
+ protected function _set_standard_values() {
342
  $this->_standard_options = array(
343
  'enabling_ticket_invitation_page' => array(
344
  'type' => 'string',
348
  'type' => 'boolean',
349
  'default' => false,
350
  ),
351
+ 'ai1ec_db_version' => array(
352
+ 'type' => 'int',
353
+ 'default' => false,
354
+ ),
355
+ 'feeds_page' => array(
356
+ 'type' => 'string',
357
+ 'default' => false,
358
+ ),
359
+ 'settings_page' => array(
360
+ 'type' => 'string',
361
+ 'default' => false,
362
+ ),
363
+ 'less_variables_page' => array(
364
+ 'type' => 'string',
365
+ 'default' => false,
366
+ ),
367
+ 'input_date_format' => array(
368
+ 'type' => 'string',
369
+ 'default' => 'd/m/yyyy',
370
+ ),
371
+ 'plugins_options' => array(
372
+ 'type' => 'array',
373
+ 'default' => array(),
374
+ ),
375
+ 'show_tracking_popup' => array(
376
+ 'type' => 'deprecated',
377
+ 'default' => true,
378
+ ),
379
+ 'ticketing_message' => array(
380
+ 'type' => 'string',
381
+ 'default' => false,
382
+ ),
383
+ 'ticketing_token' => array(
384
+ 'type' => 'string',
385
+ 'default' => '',
386
+ ),
387
+ 'ticketing_enabled' => array(
388
+ 'type' => 'boolean',
389
+ 'default' => false,
390
+ ),
391
+ 'ticketing_calendar_id' => array(
392
+ 'type' => 'int',
393
+ 'default' => 0,
394
+ ),
395
+ 'calendar_page_id' => array(
396
+ 'type' => 'mixed',
397
+ 'renderer' => array(
398
+ 'class' => 'calendar-page-selector',
399
+ 'tab' => 'viewing-events',
400
+ 'item' => 'viewing-events',
401
+ 'label' => Ai1ec_I18n::__( 'Calendar page' )
402
+ ),
403
+ 'default' => false,
404
+ ),
405
+ 'week_start_day' => array(
406
+ 'type' => 'int',
407
+ 'renderer' => array(
408
+ 'class' => 'select',
409
+ 'tab' => 'viewing-events',
410
+ 'item' => 'viewing-events',
411
+ 'label' => Ai1ec_I18n::__( 'Week starts on' ),
412
+ 'options' => 'get_weekdays',
413
+ ),
414
+ 'default' => $this->_registry->get( 'model.option' )->get(
415
+ 'start_of_week'
416
+ ),
417
+ ),
418
+ 'enabled_views' => array(
419
+ 'type' => 'array',
420
+ 'renderer' => array(
421
+ 'class' => 'enabled-views',
422
+ 'tab' => 'viewing-events',
423
+ 'item' => 'viewing-events',
424
+ 'label' => Ai1ec_I18n::__( 'Available views' ),
425
+ ),
426
+ 'default' => array(
427
+ 'agenda' => array(
428
+ 'enabled' => true,
429
+ 'default' => true,
430
+ 'enabled_mobile' => true,
431
+ 'default_mobile' => true,
432
+ 'longname' => _n_noop(
433
+ 'Agenda',
434
+ 'Agenda',
435
+ AI1EC_PLUGIN_NAME
436
+ ),
437
+ ),
438
+ 'oneday' => array(
439
+ 'enabled' => true,
440
+ 'default' => false,
441
+ 'enabled_mobile' => true,
442
+ 'default_mobile' => false,
443
+ 'longname' => _n_noop(
444
+ 'Day',
445
+ 'Day',
446
+ AI1EC_PLUGIN_NAME
447
+ ),
448
+ ),
449
+ 'month' => array(
450
+ 'enabled' => true,
451
+ 'default' => false,
452
+ 'enabled_mobile' => true,
453
+ 'default_mobile' => false,
454
+ 'longname' => _n_noop(
455
+ 'Month',
456
+ 'Month',
457
+ AI1EC_PLUGIN_NAME
458
+ ),
459
+ ),
460
+ 'week' => array(
461
+ 'enabled' => true,
462
+ 'default' => false,
463
+ 'enabled_mobile' => true,
464
+ 'default_mobile' => false,
465
+ 'longname' => _n_noop(
466
+ 'Week',
467
+ 'Week',
468
+ AI1EC_PLUGIN_NAME
469
+ ),
470
+ ),
471
+ ),
472
+ ),
473
+ 'timezone_string' => array(
474
+ 'type' => 'wp_option',
475
+ 'renderer' => array(
476
+ 'class' => 'select',
477
+ 'tab' => 'viewing-events',
478
+ 'item' => 'viewing-events',
479
+ 'label' => Ai1ec_I18n::__( 'Timezone' ),
480
+ 'options' => 'Ai1ec_Date_Timezone:get_timezones',
481
+ ),
482
+ 'default' => $this->_registry->get( 'model.option' )->get(
483
+ 'timezone_string'
484
+ ),
485
+ ),
486
+ 'default_tags_categories' => array(
487
+ 'type' => 'array',
488
+ 'renderer' => array(
489
+ 'class' => 'tags-categories',
490
+ 'tab' => 'viewing-events',
491
+ 'item' => 'viewing-events',
492
+ 'label' => Ai1ec_I18n::__( 'Preselected calendar filters' ),
493
+ 'help' => Ai1ec_I18n::__(
494
+ 'To clear, hold &#8984;/<abbr class="initialism">CTRL</abbr> and click selection.'
495
+ )
496
+ ),
497
+ 'default' => array(
498
+ 'categories' => array(),
499
+ 'tags' => array(),
500
+ ),
501
+ ),
502
+ 'exact_date' => array(
503
+ 'type' => 'string',
504
+ 'renderer' => array(
505
+ 'class' => 'input',
506
+ 'tab' => 'viewing-events',
507
+ 'item' => 'viewing-events',
508
+ 'label' => Ai1ec_I18n::__( 'Default calendar start date (optional)' ),
509
+ 'type' => 'date',
510
+ ),
511
+ 'default' => '',
512
+ ),
513
+ 'agenda_events_per_page' => array(
514
+ 'type' => 'int',
515
+ 'renderer' => array(
516
+ 'class' => 'input',
517
+ 'tab' => 'viewing-events',
518
+ 'item' => 'viewing-events',
519
+ 'label' => Ai1ec_I18n::__( 'Agenda pages show at most' ),
520
+ 'type' => 'append',
521
+ 'append' => 'events',
522
+ 'validator' => 'numeric',
523
+ ),
524
+ 'default' => 10,
525
+ ),
526
+ 'week_view_starts_at' => array(
527
+ 'type' => 'int',
528
+ 'renderer' => array(
529
+ 'class' => 'input',
530
+ 'tab' => 'viewing-events',
531
+ 'item' => 'viewing-events',
532
+ 'label' => Ai1ec_I18n::__( 'Week/Day view starts at' ),
533
+ 'type' => 'append',
534
+ 'append' => 'hrs',
535
+ 'validator' => 'numeric',
536
+ ),
537
+ 'default' => 8,
538
+ ),
539
+ 'week_view_ends_at' => array(
540
+ 'type' => 'int',
541
+ 'renderer' => array(
542
+ 'class' => 'input',
543
+ 'tab' => 'viewing-events',
544
+ 'item' => 'viewing-events',
545
+ 'label' => Ai1ec_I18n::__( 'Week/Day view ends at' ),
546
+ 'type' => 'append',
547
+ 'append' => 'hrs',
548
+ 'validator' => 'numeric',
549
+ ),
550
+ 'default' => 24,
551
+ ),
552
+ 'google_maps_api_key' => array(
553
+ 'type' => 'string',
554
+ 'renderer' => array(
555
+ 'class' => 'input',
556
+ 'tab' => 'viewing-events',
557
+ 'item' => 'viewing-events',
558
+ 'label' => Ai1ec_I18n::__(
559
+ '<span class="ai1ec-tooltip-toggle"
560
+ data-original-title="Google may request for an API key in order to show the map">
561
+ Google Maps API Key</span> (<a target="_blank" href="https://developers.google.com/maps/documentation/javascript/get-api-key#get-an-api-key">Get an API key</a>)'
562
+ ),
563
+ 'type' => 'normal'
564
+ ),
565
+ 'default' => '',
566
+ ),
567
+ 'month_word_wrap' => array(
568
+ 'type' => 'bool',
569
+ 'renderer' => array(
570
+ 'class' => 'checkbox',
571
+ 'tab' => 'viewing-events',
572
+ 'item' => 'viewing-events',
573
+ 'label' => Ai1ec_I18n::__(
574
+ '<strong>Word-wrap event stubs</strong> in Month view'
575
+ ),
576
+ 'help' => Ai1ec_I18n::__(
577
+ 'Only applies to events that span a single day.'
578
+ ),
579
+ ),
580
+ 'default' => false,
581
+ ),
582
+ 'agenda_include_entire_last_day' => array(
583
+ 'type' => 'bool',
584
+ 'renderer' => array(
585
+ 'class' => 'checkbox',
586
+ 'tab' => 'viewing-events',
587
+ 'item' => 'viewing-events',
588
+ 'label' => Ai1ec_I18n::__(
589
+ 'In <span class="ai1ec-tooltip-toggle"
590
+ data-original-title="These include Agenda view,
591
+ the Upcoming Events widget, and some extended views.">
592
+ Agenda-like views</span>, <strong>include all events
593
+ from last day shown</strong>'
594
+ )
595
+ ),
596
+ 'default' => false,
597
+ ),
598
+ 'agenda_events_expanded' => array(
599
+ 'type' => 'bool',
600
+ 'renderer' => array(
601
+ 'class' => 'checkbox',
602
+ 'tab' => 'viewing-events',
603
+ 'item' => 'viewing-events',
604
+ 'label' => Ai1ec_I18n::__(
605
+ 'Keep all events <strong>expanded</strong> in Agenda view'
606
+ )
607
+ ),
608
+ 'default' => false,
609
+ ),
610
+ 'show_year_in_agenda_dates' => array(
611
+ 'type' => 'bool',
612
+ 'renderer' => array(
613
+ 'class' => 'checkbox',
614
+ 'tab' => 'viewing-events',
615
+ 'item' => 'viewing-events',
616
+ 'label' => Ai1ec_I18n::__(
617
+ '<strong>Show year</strong> in calendar date labels'
618
+ )
619
+ ),
620
+ 'default' => false,
621
+ ),
622
+ 'show_location_in_title' => array(
623
+ 'type' => 'bool',
624
+ 'renderer' => array(
625
+ 'class' => 'checkbox',
626
+ 'tab' => 'viewing-events',
627
+ 'item' => 'viewing-events',
628
+ 'label' => Ai1ec_I18n::__(
629
+ '<strong>Show location in event titles</strong> in calendar views'
630
+ )
631
+ ),
632
+ 'default' => true,
633
+ ),
634
+ 'exclude_from_search' => array(
635
+ 'type' => 'bool',
636
+ 'renderer' => array(
637
+ 'class' => 'checkbox',
638
+ 'tab' => 'viewing-events',
639
+ 'item' => 'viewing-events',
640
+ 'label' => Ai1ec_I18n::__(
641
+ '<strong>Exclude</strong> events from search results'
642
+ )
643
+ ),
644
+ 'default' => false,
645
+ ),
646
+ 'turn_off_subscription_buttons' => array(
647
+ 'type' => 'bool',
648
+ 'renderer' => array(
649
+ 'class' => 'checkbox',
650
+ 'tab' => 'viewing-events',
651
+ 'item' => 'viewing-events',
652
+ 'label' => Ai1ec_I18n::__(
653
+ 'Hide <strong>Subscribe</strong>/<strong>Add to Calendar</strong> buttons in calendar and single event views '
654
+ )
655
+ ),
656
+ 'default' => false,
657
+ ),
658
+ 'disable_get_calendar_button' => array(
659
+ 'type' => 'bool',
660
+ 'renderer' => array(
661
+ 'class' => 'checkbox',
662
+ 'tab' => 'viewing-events',
663
+ 'item' => 'viewing-events',
664
+ 'label' => Ai1ec_I18n::__(
665
+ 'Hide <strong>Get a Timely Calendar</strong> button'
666
+ )
667
+ ),
668
+ 'default' => true,
669
+ ),
670
+ 'hide_maps_until_clicked' => array(
671
+ 'type' => 'bool',
672
+ 'renderer' => array(
673
+ 'class' => 'checkbox',
674
+ 'tab' => 'viewing-events',
675
+ 'item' => 'viewing-events',
676
+ 'label' => Ai1ec_I18n::__(
677
+ ' Hide <strong>Google Maps</strong> until clicked'
678
+ )
679
+ ),
680
+ 'default' => false,
681
+ ),
682
+ 'affix_filter_menu' => array(
683
+ 'type' => 'bool',
684
+ 'renderer' => array(
685
+ 'class' => 'checkbox',
686
+ 'tab' => 'viewing-events',
687
+ 'item' => 'viewing-events',
688
+ 'label' => Ai1ec_I18n::__(
689
+ ' <strong>Affix filter menu</strong> to top of window when it scrolls out of view'
690
+ ),
691
+ 'help' => Ai1ec_I18n::__(
692
+ 'Only applies to first visible calendar found on the page.'
693
+ ),
694
+ ),
695
+ 'default' => false,
696
+ ),
697
+ 'affix_vertical_offset_md' => array(
698
+ 'type' => 'int',
699
+ 'renderer' => array(
700
+ 'class' => 'input',
701
+ 'tab' => 'viewing-events',
702
+ 'item' => 'viewing-events',
703
+ 'label' => Ai1ec_I18n::__( 'Offset affixed filter bar vertically by' ),
704
+ 'type' => 'append',
705
+ 'append' => 'pixels',
706
+ 'validator' => 'numeric',
707
+ ),
708
+ 'default' => 0,
709
+ ),
710
+ 'affix_vertical_offset_lg' => array(
711
+ 'type' => 'int',
712
+ 'renderer' => array(
713
+ 'class' => 'input',
714
+ 'tab' => 'viewing-events',
715
+ 'item' => 'viewing-events',
716
+ 'label' =>
717
+ '<i class="ai1ec-fa ai1ec-fa-lg ai1ec-fa-fw ai1ec-fa-desktop"></i> ' .
718
+ Ai1ec_I18n::__( 'Wide screens only (&#8805; 1200px)' ),
719
+ 'type' => 'append',
720
+ 'append' => 'pixels',
721
+ 'validator' => 'numeric',
722
+ ),
723
+ 'default' => 0,
724
+ ),
725
+ 'affix_vertical_offset_sm' => array(
726
+ 'type' => 'int',
727
+ 'renderer' => array(
728
+ 'class' => 'input',
729
+ 'tab' => 'viewing-events',
730
+ 'item' => 'viewing-events',
731
+ 'label' =>
732
+ '<i class="ai1ec-fa ai1ec-fa-lg ai1ec-fa-fw ai1ec-fa-tablet"></i> ' .
733
+ Ai1ec_I18n::__( 'Tablets only (< 980px)' ),
734
+ 'type' => 'append',
735
+ 'append' => 'pixels',
736
+ 'validator' => 'numeric',
737
+ ),
738
+ 'default' => 0,
739
+ ),
740
+ 'affix_vertical_offset_xs' => array(
741
+ 'type' => 'int',
742
+ 'renderer' => array(
743
+ 'class' => 'input',
744
+ 'tab' => 'viewing-events',
745
+ 'item' => 'viewing-events',
746
+ 'label' =>
747
+ '<i class="ai1ec-fa ai1ec-fa-lg ai1ec-fa-fw ai1ec-fa-mobile"></i> ' .
748
+ Ai1ec_I18n::__( 'Phones only (< 768px)' ),
749
+ 'type' => 'append',
750
+ 'append' => 'pixels',
751
+ 'validator' => 'numeric',
752
+ ),
753
+ 'default' => 0,
754
+ ),
755
+ 'strict_compatibility_content_filtering' => array(
756
+ 'type' => 'bool',
757
+ 'renderer' => array(
758
+ 'class' => 'checkbox',
759
+ 'tab' => 'viewing-events',
760
+ 'item' => 'viewing-events',
761
+ 'label' => Ai1ec_I18n::__(
762
+ 'Strict compatibility content filtering'
763
+ ),
764
+ ),
765
+ 'default' => false,
766
+ ),
767
+ 'hide_featured_image' => array(
768
+ 'type' => 'bool',
769
+ 'renderer' => array(
770
+ 'class' => 'checkbox',
771
+ 'tab' => 'viewing-events',
772
+ 'item' => 'viewing-events',
773
+ 'label' => Ai1ec_I18n::__(
774
+ ' <strong>Hide featured image</strong> from event details page'
775
+ ),
776
+ 'help' => Ai1ec_I18n::__(
777
+ "Select this option if your theme already displays each post's featured image."
778
+ ),
779
+ ),
780
+ 'default' => false,
781
+ ),
782
+ 'input_date_format' => array(
783
+ 'type' => 'string',
784
+ 'renderer' => array(
785
+ 'class' => 'select',
786
+ 'tab' => 'editing-events',
787
+ 'label' => Ai1ec_I18n::__(
788
+ 'Input dates in this format'
789
+ ),
790
+ 'options' => array(
791
+ array(
792
+ 'text' => Ai1ec_I18n::__( 'Default (d/m/yyyy)' ),
793
+ 'value' => 'def'
794
+ ),
795
+ array(
796
+ 'text' => Ai1ec_I18n::__( 'US (m/d/yyyy)' ),
797
+ 'value' => 'us'
798
+ ),
799
+ array(
800
+ 'text' => Ai1ec_I18n::__( 'ISO 8601 (yyyy-m-d)' ),
801
+ 'value' => 'iso'
802
+ ),
803
+ array(
804
+ 'text' => Ai1ec_I18n::__( 'Dotted (m.d.yyyy)' ),
805
+ 'value' => 'dot'
806
+ ),
807
+ ),
808
+ ),
809
+ 'default' => 'def',
810
+ ),
811
+ 'input_24h_time' => array(
812
+ 'type' => 'bool',
813
+ 'renderer' => array(
814
+ 'class' => 'checkbox',
815
+ 'tab' => 'editing-events',
816
+ 'label' => Ai1ec_I18n::__(
817
+ ' Use <strong>24h time</strong> in time pickers'
818
+ )
819
+ ),
820
+ 'default' => false,
821
+ ),
822
+ 'disable_autocompletion' => array(
823
+ 'type' => 'bool',
824
+ 'renderer' => array(
825
+ 'class' => 'checkbox',
826
+ 'tab' => 'editing-events',
827
+ 'label' => Ai1ec_I18n::__(
828
+ '<strong>Disable address autocomplete</strong> function'
829
+ )
830
+ ),
831
+ 'default' => false,
832
+ ),
833
+ 'geo_region_biasing' => array(
834
+ 'type' => 'bool',
835
+ 'renderer' => array(
836
+ 'class' => 'checkbox',
837
+ 'tab' => 'editing-events',
838
+ 'label' => Ai1ec_I18n::__(
839
+ 'Use the configured <strong>region</strong> (WordPress locale) to bias the address autocomplete function '
840
+ )
841
+ ),
842
+ 'default' => false,
843
+ ),
844
+ 'show_publish_button' => array(
845
+ 'type' => 'deprecated',
846
+ 'renderer' => null,
847
+ 'default' => false,
848
+ ),
849
+ 'show_create_event_button' => array(
850
+ 'type' => 'bool',
851
+ 'renderer' => array(
852
+ 'class' => 'checkbox',
853
+ 'tab' => 'extensions',
854
+ 'label' => Ai1ec_I18n::__(
855
+ ' Show the old <strong>Post Your Event</strong> button above the calendar to privileged users'
856
+ ),
857
+ 'help' => Ai1ec_I18n::__(
858
+ 'Install the <a target="_blank" href="https://time.ly/">Interactive Frontend Extension</a> for the <strong>frontend Post Your Event form</strong>.'
859
+ ),
860
+ ),
861
+ 'default' => true,
862
+ ),
863
+ 'embedding' => array(
864
+ 'type' => 'html',
865
+ 'renderer' => array(
866
+ 'class' => 'html',
867
+ 'tab' => 'advanced',
868
+ 'item' => 'embedded-views',
869
+ ),
870
+ 'default' => null,
871
+ ),
872
+ 'calendar_css_selector' => array(
873
+ 'type' => 'string',
874
+ 'renderer' => array(
875
+ 'class' => 'input',
876
+ 'tab' => 'advanced',
877
+ 'item' => 'advanced',
878
+ 'label' => Ai1ec_I18n::__( 'Move calendar into this DOM element' ),
879
+ 'type' => 'normal',
880
+ 'help' => Ai1ec_I18n::__(
881
+ 'Optional. Use this JavaScript-based shortcut to place the
882
+ calendar a DOM element other than the usual page content container
883
+ if you are unable to create an appropriate page template
884
+ for the calendar page. To use, enter a
885
+ <a target="_blank" href="https://api.jquery.com/category/selectors/">
886
+ jQuery selector</a> that evaluates to a single DOM element.
887
+ Any existing markup found within the target will be replaced
888
+ by the calendar.'
889
+ ),
890
+ ),
891
+ 'default' => '',
892
+ ),
893
+ 'skip_in_the_loop_check' => array(
894
+ 'type' => 'bool',
895
+ 'renderer' => array(
896
+ 'class' => 'checkbox',
897
+ 'tab' => 'advanced',
898
+ 'item' => 'advanced',
899
+ 'label' => Ai1ec_I18n::__(
900
+ '<strong>Skip <tt>in_the_loop()</tt> check </strong> that protects against multiple calendar output'
901
+ ),
902
+ 'help' => Ai1ec_I18n::__(
903
+ 'Try enabling this option if your calendar does not appear on the calendar page. It is needed for compatibility with a small number of themes that call <tt>the_content()</tt> from outside of The Loop. Leave disabled otherwise.'
904
+ ),
905
+ ),
906
+ 'default' => false,
907
+ ),
908
+ 'disable_gzip_compression' => array(
909
+ 'type' => 'bool',
910
+ 'renderer' => array(
911
+ 'class' => 'checkbox',
912
+ 'tab' => 'advanced',
913
+ 'item' => 'advanced',
914
+ 'label' => Ai1ec_I18n::__(
915
+ 'Disable <strong>gzip</strong> compression.'
916
+ ),
917
+ 'help' => Ai1ec_I18n::__(
918
+ 'Use this option if calendar is unresponsive. <a target="_blank" href="https://time.ly/document/user-guide/troubleshooting/disable-gzip-compression/">Read more</a> about the issue. (From version 2.1 onwards, gzip is disabled by default for maximum compatibility.)'
919
+ ),
920
+ ),
921
+ 'default' => true,
922
+ ),
923
+ 'ai1ec_use_frontend_rendering' => array(
924
+ 'type' => 'bool',
925
+ 'renderer' => array(
926
+ 'class' => 'checkbox',
927
+ 'tab' => 'advanced',
928
+ 'item' => 'advanced',
929
+ 'label' => Ai1ec_I18n::__(
930
+ 'Use frontend rendering.'
931
+ ),
932
+ 'help' => Ai1ec_I18n::__(
933
+ 'Renders calendar views on the client rather than the server; can improve performance.'
934
+ ),
935
+ ),
936
+ 'default' => true,
937
+ ),
938
+ 'cache_dynamic_js' => array(
939
+ 'type' => 'bool',
940
+ 'renderer' => array(
941
+ 'class' => 'checkbox',
942
+ 'tab' => 'advanced',
943
+ 'item' => 'advanced',
944
+ 'label' => Ai1ec_I18n::__(
945
+ 'Use advanced JS cache.'
946
+ ),
947
+ 'help' => Ai1ec_I18n::__(
948
+ 'Cache dynamically generated JS files. Improves performance.'
949
+ ),
950
+ ),
951
+ 'default' => true,
952
+ ),
953
+ 'render_css_as_link' => array(
954
+ 'type' => 'bool',
955
+ 'renderer' => array(
956
+ 'class' => 'checkbox',
957
+ 'tab' => 'advanced',
958
+ 'item' => 'advanced',
959
+ 'label' => Ai1ec_I18n::__(
960
+ '<strong>Link CSS</strong> in <code>&lt;head&gt;</code> section when file cache is unavailable.'
961
+ ),
962
+ 'help' => Ai1ec_I18n::__(
963
+ 'Use this option if file cache is unavailable and you would prefer to serve CSS as a link rather than have it output inline.'
964
+ ),
965
+ ),
966
+ 'default' => false,
967
+ ),
968
+ 'edit_robots_txt' => array(
969
+ 'type' => 'string',
970
+ 'renderer' => array(
971
+ 'class' => 'textarea',
972
+ 'tab' => 'advanced',
973
+ 'item' => 'advanced',
974
+ 'label' => Ai1ec_I18n::__( 'Current <strong>robots.txt</strong> on this site' ),
975
+ 'type' => 'normal',
976
+ 'rows' => 6,
977
+ 'readonly' => 'readonly',
978
+ 'help' => Ai1ec_I18n::__(
979
+ 'The Robot Exclusion Standard, also known as the Robots Exclusion Protocol or
980
+ <code><a href="https://en.wikipedia.org/wiki/Robots.txt" target="_blank">robots.txt</a></code>
981
+ protocol, is a convention for cooperating web crawlers and other web robots
982
+ about accessing all or part of a website that is otherwise publicly viewable.
983
+ You can change it manually by editing <code>robots.txt</code> in your root WordPress directory.'
984
+ ),
985
+ ),
986
+ 'default' => '',
987
+ ),
988
+ 'allow_statistics' => array(
989
+ 'type' => 'bool',
990
+ 'renderer' => array(
991
+ 'class' => 'checkbox',
992
+ 'tab' => 'advanced',
993
+ 'item' => 'advanced',
994
+ 'label' => sprintf(
995
+ Ai1ec_I18n::__(
996
+ '<strong>Publicize, promote, and share my events</strong> marked as public on the Timely network. (<a href="%s" target="_blank">Learn more &#187;</a>)'
997
+ ),
998
+ 'https://time.ly/event-search-calendar'
999
+ ),
1000
+ ),
1001
+ 'default' => false,
1002
+ ),
1003
+ 'legacy_options' => array(
1004
+ 'type' => 'legacy_options',
1005
+ 'default' => null,
1006
+ ),
1007
+ 'ics_cron_freq' => array(
1008
+ 'type' => 'string',
1009
+ 'default' => 'hourly',
1010
+ ),
1011
+ 'twig_cache' => array(
1012
+ 'type' => 'string',
1013
+ 'renderer' => array(
1014
+ 'class' => 'cache',
1015
+ 'tab' => 'advanced',
1016
+ 'item' => 'cache',
1017
+ 'label' => sprintf(
1018
+ Ai1ec_I18n::__(
1019
+ 'Templates cache improves site performance'
1020
+ )
1021
+ ),
1022
+ ),
1023
+ 'default' => '',
1024
+ ),
1025
+ 'always_use_calendar_timezone' => array(
1026
+ 'type' => 'bool',
1027
+ 'renderer' => array(
1028
+ 'class' => 'checkbox',
1029
+ 'tab' => 'viewing-events',
1030
+ 'item' => 'viewing-events',
1031
+ 'label' => Ai1ec_I18n::__(
1032
+ 'Display events in <strong>calendar time zone</strong>'
1033
+ ),
1034
+ 'help' => Ai1ec_I18n::__(
1035
+ 'If this box is checked events will appear in the calendar time zone with time zone information displayed on the event details page.'
1036
+ ),
1037
+ ),
1038
+ 'default' => false,
1039
+ ),
1040
+ );
1041
+ }
1042
 
1043
+ /**
1044
+ * Register the standard setting values.
1045
+ *
1046
+ * @return void Method doesn't return.
1047
+ */
1048
+ protected function _register_standard_values() {
1049
+ foreach ( $this->_standard_options as $key => $option ) {
1050
+ $renderer = null;
1051
+ $value = $option['default'];
1052
+ if ( isset( $option['renderer'] ) ) {
1053
+ $renderer = $option['renderer'];
1054
+ }
1055
+ $this->register(
1056
+ $key,
1057
+ $value,
1058
+ $option['type'],
1059
+ $renderer,
1060
+ AI1EC_VERSION
1061
+ );
1062
+ }
1063
+ }
1064
 
1065
+ /**
1066
+ * Update translated strings, after introduction of `_noop` functions.
1067
+ *
1068
+ * @return void
1069
+ */
1070
+ protected function _update_name_translations() {
1071
+ $translations = $this->_standard_options['enabled_views']['default'];
1072
+ $current = $this->get( 'enabled_views' );
1073
+ foreach ( $current as $key => $view ) {
1074
+ if ( isset( $translations[$key] ) ) {
1075
+ $current[$key]['longname'] = $translations[$key]['longname'];
1076
+ }
1077
+ }
1078
+ $this->set( 'enabled_views', $current );
1079
+ }
1080
 
1081
+ /**
1082
+ * Change `updated` flag value.
1083
+ *
1084
+ * @param bool $new_status Status to change to.
1085
+ *
1086
+ * @return bool Previous status flag value.
1087
+ */
1088
+ protected function _change_update_status( $new_status ) {
1089
+ $previous = $this->_updated;
1090
+ $this->_updated = (bool)$new_status;
1091
+ return $previous;
1092
+ }
1093
 
1094
  }
app/model/taxonomy.php CHANGED
@@ -10,187 +10,187 @@
10
  */
11
  class Ai1ec_Taxonomy extends Ai1ec_Base {
12
 
13
- /**
14
- * @var array Map of taxonomy values.
15
- */
16
- protected $_taxonomy_map = array(
17
- 'events_categories' => array(),
18
- 'events_tags' => array(),
19
- );
20
 
21
- /**
22
- * Callback to pre-populate taxonomies before exporting ics.
23
- * All taxonomies which are not tags are exported as event_categories
24
- *
25
- * @param array $post_ids List of Post IDs to inspect.
26
- *
27
- * @return void
28
- */
29
- public function prepare_meta_for_ics( array $post_ids ) {
30
- $taxonomies = get_object_taxonomies( AI1EC_POST_TYPE );
31
- $categories = array();
32
- $excluded_categories = array(
33
- 'events_tags' => true,
34
- 'events_feeds' => true
35
- );
36
- foreach ( $taxonomies as $taxonomy ) {
37
- if ( isset( $excluded_categories[$taxonomy] ) ) {
38
- continue;
39
- }
40
- $categories[] = $taxonomy;
41
- }
42
- foreach ( $post_ids as $post_id ) {
43
- $post_id = (int)$post_id;
44
- $this->_taxonomy_map['events_categories'][$post_id] = array();
45
- $this->_taxonomy_map['events_tags'][$post_id] = array();
46
- }
47
- $tags = wp_get_object_terms(
48
- $post_ids,
49
- array( 'events_tags' ),
50
- array( 'fields' => 'all_with_object_id' )
51
- );
52
- foreach ( $tags as $term ) {
53
- $this->_taxonomy_map[$term->taxonomy][$term->object_id][] = $term;
54
- }
55
- $category_terms = wp_get_object_terms(
56
- $post_ids,
57
- $categories,
58
- array( 'fields' => 'all_with_object_id' )
59
- );
60
- foreach ( $category_terms as $term ) {
61
- $this->_taxonomy_map['events_categories'][$term->object_id][] = $term;
62
- }
63
- }
64
 
65
- /**
66
- * Callback to pre-populate taxonomies before processing.
67
- *
68
- * @param array $post_ids List of Post IDs to inspect.
69
- *
70
- * @return void
71
- */
72
- public function update_meta( array $post_ids ) {
73
- foreach ( $post_ids as $post_id ) {
74
- $post_id = (int)$post_id;
75
- $this->_taxonomy_map['events_categories'][$post_id] = array();
76
- $this->_taxonomy_map['events_tags'][$post_id] = array();
77
- }
78
- $terms = wp_get_object_terms(
79
- $post_ids,
80
- array( 'events_categories', 'events_tags' ),
81
- array( 'fields' => 'all_with_object_id' )
82
- );
83
- foreach ( $terms as $term ) {
84
- $this->_taxonomy_map[$term->taxonomy][$term->object_id][] = $term;
85
- }
86
- }
87
 
88
- /**
89
- * Re-fetch category entries map from database.
90
- *
91
- * @return array Map of category entries.
92
- */
93
- public function fetch_category_map() {
94
- $category_map = array();
95
- $records = (array)$this->_registry->get( 'dbi.dbi' )->select(
96
- 'ai1ec_event_category_meta',
97
- array( 'term_id', 'term_image', 'term_color' )
98
- );
99
- foreach ( $records as $row ) {
100
- $image = $color = null;
101
- if ( $row->term_image ) {
102
- $image = $row->term_image;
103
- }
104
- if ( $row->term_color ) {
105
- $color = $row->term_color;
106
- }
107
- $category_map[(int)$row->term_id] = compact( 'image', 'color' );
108
- }
109
- return $category_map;
110
- }
111
 
112
- /**
113
- * Get taxonomy values for specified post.
114
- *
115
- * @param int $post_id Actual Post ID to check.
116
- * @param string $taxonomy Name of taxonomy to retrieve values for.
117
- *
118
- * @return array List of terms (stdClass'es) associated with post.
119
- */
120
- public function get_post_taxonomy( $post_id, $taxonomy ) {
121
- $post_id = (int)$post_id;
122
- if ( ! isset( $this->_taxonomy_map[$taxonomy][$post_id] ) ) {
123
- $definition = wp_get_post_terms( $post_id, $taxonomy );
124
- if ( empty( $definition ) || is_wp_error( $definition ) ) {
125
- $definition = array();
126
- }
127
- $this->_taxonomy_map[$taxonomy][$post_id] = $definition;
128
- }
129
- return $this->_taxonomy_map[$taxonomy][$post_id];
130
- }
131
 
132
- /**
133
- * Get post (event) categories taxonomy.
134
- *
135
- * @param int $post_id Checked post ID.
136
- *
137
- * @return array List of categories (stdClass'es) associated with event.
138
- */
139
- public function get_post_categories( $post_id ) {
140
- return $this->get_post_taxonomy( $post_id, 'events_categories' );
141
- }
142
 
143
- /**
144
- * Get post (event) tags taxonomy.
145
- *
146
- * @param int $post_id Checked post ID.
147
- *
148
- * @return array List of tags (stdClass'es) associated with event.
149
- */
150
- public function get_post_tags( $post_id ) {
151
- return $this->get_post_taxonomy( $post_id, 'events_tags' );
152
- }
153
 
154
- /**
155
- * Get cached category description field.
156
- *
157
- * @param int $term_id Category ID.
158
- * @param string $field Name of field, one of 'image', 'color'.
159
- *
160
- * @return string|null Field value or null if entry is not found.
161
- */
162
- public function get_category_field( $term_id, $field ) {
163
- static $category_meta = null;
164
- if ( null === $category_meta ) {
165
- $category_meta = $this->fetch_category_map();
166
- }
167
- $term_id = (int)$term_id;
168
- if ( ! isset( $category_meta[$term_id] ) ) {
169
- return null;
170
- }
171
- return $category_meta[$term_id][$field];
172
- }
173
-
174
- /**
175
- * Returns the color of the Event Category having the given term ID.
176
- *
177
- * @param int $term_id The ID of the Event Category.
178
- *
179
- * @return string|null Color to use
180
- */
181
- public function get_category_color( $term_id ) {
182
- return $this->get_category_field( $term_id, 'color' );
183
- }
184
 
185
- /**
186
- * Returns the image of the Event Category having the given term ID.
187
- *
188
- * @param int $term_id The ID of the Event Category.
189
- *
190
- * @return string|null Image url to use.
191
- */
192
- public function get_category_image( $term_id ) {
193
- return $this->get_category_field( $term_id, 'image' );
194
- }
 
 
 
 
 
 
 
 
 
 
 
195
 
196
  }
10
  */
11
  class Ai1ec_Taxonomy extends Ai1ec_Base {
12
 
13
+ /**
14
+ * @var array Map of taxonomy values.
15
+ */
16
+ protected $_taxonomy_map = array(
17
+ 'events_categories' => array(),
18
+ 'events_tags' => array(),
19
+ );
20
 
21
+ /**
22
+ * Callback to pre-populate taxonomies before exporting ics.
23
+ * All taxonomies which are not tags are exported as event_categories
24
+ *
25
+ * @param array $post_ids List of Post IDs to inspect.
26
+ *
27
+ * @return void
28
+ */
29
+ public function prepare_meta_for_ics( array $post_ids ) {
30
+ $taxonomies = get_object_taxonomies( AI1EC_POST_TYPE );
31
+ $categories = array();
32
+ $excluded_categories = array(
33
+ 'events_tags' => true,
34
+ 'events_feeds' => true
35
+ );
36
+ foreach ( $taxonomies as $taxonomy ) {
37
+ if ( isset( $excluded_categories[$taxonomy] ) ) {
38
+ continue;
39
+ }
40
+ $categories[] = $taxonomy;
41
+ }
42
+ foreach ( $post_ids as $post_id ) {
43
+ $post_id = (int)$post_id;
44
+ $this->_taxonomy_map['events_categories'][$post_id] = array();
45
+ $this->_taxonomy_map['events_tags'][$post_id] = array();
46
+ }
47
+ $tags = wp_get_object_terms(
48
+ $post_ids,
49
+ array( 'events_tags' ),
50
+ array( 'fields' => 'all_with_object_id' )
51
+ );
52
+ foreach ( $tags as $term ) {
53
+ $this->_taxonomy_map[$term->taxonomy][$term->object_id][] = $term;
54
+ }
55
+ $category_terms = wp_get_object_terms(
56
+ $post_ids,
57
+ $categories,
58
+ array( 'fields' => 'all_with_object_id' )
59
+ );
60
+ foreach ( $category_terms as $term ) {
61
+ $this->_taxonomy_map['events_categories'][$term->object_id][] = $term;
62
+ }
63
+ }
64
 
65
+ /**
66
+ * Callback to pre-populate taxonomies before processing.
67
+ *
68
+ * @param array $post_ids List of Post IDs to inspect.
69
+ *
70
+ * @return void
71
+ */
72
+ public function update_meta( array $post_ids ) {
73
+ foreach ( $post_ids as $post_id ) {
74
+ $post_id = (int)$post_id;
75
+ $this->_taxonomy_map['events_categories'][$post_id] = array();
76
+ $this->_taxonomy_map['events_tags'][$post_id] = array();
77
+ }
78
+ $terms = wp_get_object_terms(
79
+ $post_ids,
80
+ array( 'events_categories', 'events_tags' ),
81
+ array( 'fields' => 'all_with_object_id' )
82
+ );
83
+ foreach ( $terms as $term ) {
84
+ $this->_taxonomy_map[$term->taxonomy][$term->object_id][] = $term;
85
+ }
86
+ }
87
 
88
+ /**
89
+ * Re-fetch category entries map from database.
90
+ *
91
+ * @return array Map of category entries.
92
+ */
93
+ public function fetch_category_map() {
94
+ $category_map = array();
95
+ $records = (array)$this->_registry->get( 'dbi.dbi' )->select(
96
+ 'ai1ec_event_category_meta',
97
+ array( 'term_id', 'term_image', 'term_color' )
98
+ );
99
+ foreach ( $records as $row ) {
100
+ $image = $color = null;
101
+ if ( $row->term_image ) {
102
+ $image = $row->term_image;
103
+ }
104
+ if ( $row->term_color ) {
105
+ $color = $row->term_color;
106
+ }
107
+ $category_map[(int)$row->term_id] = compact( 'image', 'color' );
108
+ }
109
+ return $category_map;
110
+ }
111
 
112
+ /**
113
+ * Get taxonomy values for specified post.
114
+ *
115
+ * @param int $post_id Actual Post ID to check.
116
+ * @param string $taxonomy Name of taxonomy to retrieve values for.
117
+ *
118
+ * @return array List of terms (stdClass'es) associated with post.
119
+ */
120
+ public function get_post_taxonomy( $post_id, $taxonomy ) {
121
+ $post_id = (int)$post_id;
122
+ if ( ! isset( $this->_taxonomy_map[$taxonomy][$post_id] ) ) {
123
+ $definition = wp_get_post_terms( $post_id, $taxonomy );
124
+ if ( empty( $definition ) || is_wp_error( $definition ) ) {
125
+ $definition = array();
126
+ }
127
+ $this->_taxonomy_map[$taxonomy][$post_id] = $definition;
128
+ }
129
+ return $this->_taxonomy_map[$taxonomy][$post_id];
130
+ }
131
 
132
+ /**
133
+ * Get post (event) categories taxonomy.
134
+ *
135
+ * @param int $post_id Checked post ID.
136
+ *
137
+ * @return array List of categories (stdClass'es) associated with event.
138
+ */
139
+ public function get_post_categories( $post_id ) {
140
+ return $this->get_post_taxonomy( $post_id, 'events_categories' );
141
+ }
142
 
143
+ /**
144
+ * Get post (event) tags taxonomy.
145
+ *
146
+ * @param int $post_id Checked post ID.
147
+ *
148
+ * @return array List of tags (stdClass'es) associated with event.
149
+ */
150
+ public function get_post_tags( $post_id ) {
151
+ return $this->get_post_taxonomy( $post_id, 'events_tags' );
152
+ }
153
 
154
+ /**
155
+ * Get cached category description field.
156
+ *
157
+ * @param int $term_id Category ID.
158
+ * @param string $field Name of field, one of 'image', 'color'.
159
+ *
160
+ * @return string|null Field value or null if entry is not found.
161
+ */
162
+ public function get_category_field( $term_id, $field ) {
163
+ static $category_meta = null;
164
+ if ( null === $category_meta ) {
165
+ $category_meta = $this->fetch_category_map();
166
+ }
167
+ $term_id = (int)$term_id;
168
+ if ( ! isset( $category_meta[$term_id] ) ) {
169
+ return null;
170
+ }
171
+ return $category_meta[$term_id][$field];
172
+ }
 
 
 
 
 
 
 
 
 
 
 
173
 
174
+ /**
175
+ * Returns the color of the Event Category having the given term ID.
176
+ *
177
+ * @param int $term_id The ID of the Event Category.
178
+ *
179
+ * @return string|null Color to use
180
+ */
181
+ public function get_category_color( $term_id ) {
182
+ return $this->get_category_field( $term_id, 'color' );
183
+ }
184
+
185
+ /**
186
+ * Returns the image of the Event Category having the given term ID.
187
+ *
188
+ * @param int $term_id The ID of the Event Category.
189
+ *
190
+ * @return string|null Image url to use.
191
+ */
192
+ public function get_category_image( $term_id ) {
193
+ return $this->get_category_field( $term_id, 'image' );
194
+ }
195
 
196
  }
app/view/admin/abstract.php CHANGED
@@ -11,66 +11,66 @@
11
  */
12
  abstract class Ai1ec_View_Admin_Abstract extends Ai1ec_Base {
13
 
14
- /**
15
- * @var string
16
- */
17
- protected $_page_id;
18
 
19
- /**
20
- * @var string
21
- */
22
- protected $_page_suffix;
23
 
24
- /**
25
- * @var string
26
- */
27
- protected $_api_registration;
28
 
29
- /**
30
- * Standard constructor
31
- *
32
- * @param Ai1ec_Registry_Object $registry
33
- */
34
- public function __construct( Ai1ec_Registry_Object $registry ) {
35
- parent::__construct( $registry );
36
- $exploded_class = explode( '_', get_class( $this ) );
37
- $this->_page_suffix = strtolower( end( $exploded_class ) );
38
- $this->_api_registration = $this->_registry->get( 'model.api.api-registration' );
39
- }
40
 
41
- /**
42
- * Get the url of the page
43
- *
44
- * @return string
45
- */
46
- public function get_url() {
47
- return add_query_arg(
48
- array(
49
- 'post_type' => AI1EC_POST_TYPE,
50
- 'page' => AI1EC_PLUGIN_NAME . '-' . $this->_page_suffix,
51
- ),
52
- ai1ec_admin_url( 'edit.php' )
53
- );
54
- }
55
 
56
- /**
57
- * Adds the page to the correct menu.
58
- */
59
- abstract public function add_page();
60
 
61
- /**
62
- * Adds the page to the correct menu.
63
- */
64
- abstract public function add_meta_box();
65
 
66
- /**
67
- * Display the page html
68
- */
69
- abstract public function display_page();
70
 
71
- /**
72
- * Handle post, likely to be deprecated to use commands.
73
- */
74
- abstract public function handle_post();
75
 
76
  }
11
  */
12
  abstract class Ai1ec_View_Admin_Abstract extends Ai1ec_Base {
13
 
14
+ /**
15
+ * @var string
16
+ */
17
+ protected $_page_id;
18
 
19
+ /**
20
+ * @var string
21
+ */
22
+ protected $_page_suffix;
23
 
24
+ /**
25
+ * @var string
26
+ */
27
+ protected $_api_registration;
28
 
29
+ /**
30
+ * Standard constructor
31
+ *
32
+ * @param Ai1ec_Registry_Object $registry
33
+ */
34
+ public function __construct( Ai1ec_Registry_Object $registry ) {
35
+ parent::__construct( $registry );
36
+ $exploded_class = explode( '_', get_class( $this ) );
37
+ $this->_page_suffix = strtolower( end( $exploded_class ) );
38
+ $this->_api_registration = $this->_registry->get( 'model.api.api-registration' );
39
+ }
40
 
41
+ /**
42
+ * Get the url of the page
43
+ *
44
+ * @return string
45
+ */
46
+ public function get_url() {
47
+ return add_query_arg(
48
+ array(
49
+ 'post_type' => AI1EC_POST_TYPE,
50
+ 'page' => AI1EC_PLUGIN_NAME . '-' . $this->_page_suffix,
51
+ ),
52
+ ai1ec_admin_url( 'edit.php' )
53
+ );
54
+ }
55
 
56
+ /**
57
+ * Adds the page to the correct menu.
58
+ */
59
+ abstract public function add_page();
60
 
61
+ /**
62
+ * Adds the page to the correct menu.
63
+ */
64
+ abstract public function add_meta_box();
65
 
66
+ /**
67
+ * Display the page html
68
+ */
69
+ abstract public function display_page();
70
 
71
+ /**
72
+ * Handle post, likely to be deprecated to use commands.
73
+ */
74
+ abstract public function handle_post();
75
 
76
  }
app/view/admin/add-new-event.php CHANGED
@@ -13,539 +13,564 @@
13
  */
14
  class Ai1ec_View_Add_New_Event extends Ai1ec_Base {
15
 
16
- /**
17
- * Create hook to display event meta box when creating or editing an event.
18
- *
19
- * @wp_hook add_meta_boxes
20
- *
21
- * @return void
22
- */
23
- public function event_meta_box_container() {
24
- add_meta_box(
25
- AI1EC_POST_TYPE,
26
- Ai1ec_I18n::__( 'Event Details' ),
27
- array( $this, 'meta_box_view' ),
28
- AI1EC_POST_TYPE,
29
- 'normal',
30
- 'high'
31
- );
32
- }
33
-
34
- /**
35
- * Add Event Details meta box to the Add/Edit Event screen in the dashboard.
36
- *
37
- * @return void
38
- */
39
- public function meta_box_view( $post ) {
40
-
41
- $theme_loader = $this->_registry->get( 'theme.loader' );
42
- $empty_event = $this->_registry->get( 'model.event' );
43
-
44
- // ==================
45
- // = Default values =
46
- // ==================
47
- // ATTENTION - When adding new fields to the event remember that you must
48
- // also set up the duplicate-controller.
49
- // TODO: Fix this duplication.
50
- $all_day_event = '';
51
- $instant_event = '';
52
- $start = $this->_registry->get( 'date.time' );
53
- $end = $this->_registry->get( 'date.time', '+1 hour' );
54
- $timezone_name = null;
55
- $timezones_list = $this->_registry->get( 'date.timezone' )->get_timezones( true );
56
- $show_map = false;
57
- $google_map = '';
58
- $venue = '';
59
- $country = '';
60
- $address = '';
61
- $city = '';
62
- $province = '';
63
- $postal_code = '';
64
- $contact_name = '';
65
- $contact_phone = '';
66
- $contact_email = '';
67
- $contact_url = '';
68
- $cost = '';
69
- $is_free = '';
70
- $cost_type = 'free';
71
- $rrule = '';
72
- $rrule_text = '';
73
- $repeating_event = false;
74
- $exrule = '';
75
- $exrule_text = '';
76
- $exclude_event = false;
77
- $exdate = '';
78
- $show_coordinates = false;
79
- $longitude = '';
80
- $latitude = '';
81
- $coordinates = '';
82
- $ticket_url = '';
83
-
84
- $instance_id = false;
85
- if ( isset( $_REQUEST['instance'] ) ) {
86
- $instance_id = absint( $_REQUEST['instance'] );
87
- }
88
- if ( $instance_id ) {
89
- add_filter(
90
- 'print_scripts_array',
91
- array( $this, 'disable_autosave' )
92
- );
93
- }
94
-
95
- try {
96
- // on some php version, nested try catch blocks fail and the exception would never be caught.
97
- // this is why we use this approach.
98
- $excpt = null;
99
- $event = null;
100
- try {
101
- $event = $this->_registry->get(
102
- 'model.event',
103
- get_the_ID(),
104
- $instance_id
105
- );
106
- } catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
107
- $ai1ec_localization_helper = $this->_registry
108
- ->get( 'p28n.wpml' );
109
- $translatable_id = $ai1ec_localization_helper
110
- ->get_translatable_id();
111
- if ( false !== $translatable_id ) {
112
- $event = $this->_registry->get(
113
- 'model.event',
114
- $translatable_id,
115
- $instance_id
116
- );
117
- }
118
- }
119
- if ( null !== $excpt ) {
120
- throw $excpt;
121
- }
122
-
123
- // Existing event was found. Initialize form values with values from
124
- // event object.
125
- $all_day_event = $event->is_allday() ? 'checked' : '';
126
- $instant_event = $event->is_instant() ? 'checked' : '';
127
-
128
- $start = $event->get( 'start' );
129
- $end = $event->get( 'end' );
130
- $timezone_name = $event->get( 'timezone_name' );
131
-
132
- $multi_day = $event->is_multiday();
133
-
134
- $show_map = $event->get( 'show_map' );
135
- $google_map = $show_map ? 'checked="checked"' : '';
136
-
137
- $show_coordinates = $event->get( 'show_coordinates' );
138
- $coordinates = $show_coordinates ? 'checked="checked"' : '';
139
- $longitude = (float)$event->get( 'longitude', 0 );
140
- $latitude = (float)$event->get( 'latitude', 0 );
141
- // There is a known bug in Wordpress (https://core.trac.wordpress.org/ticket/15158) that saves 0 to the DB instead of null.
142
- // We handle a special case here to avoid having the fields with a value of 0 when the user never inputted any coordinates
143
- if ( ! $show_coordinates ) {
144
- $longitude = '';
145
- $latitude = '';
146
- }
147
-
148
- $venue = $event->get( 'venue' );
149
- $country = $event->get( 'country' );
150
- $address = $event->get( 'address' );
151
- $city = $event->get( 'city' );
152
- $province = $event->get( 'province' );
153
- $postal_code = $event->get( 'postal_code' );
154
- $contact_name = $event->get( 'contact_name' );
155
- $contact_phone = $event->get( 'contact_phone' );
156
- $contact_email = $event->get( 'contact_email' );
157
- $contact_url = $event->get( 'contact_url' );
158
- $cost = $event->get( 'cost' );
159
- $ticket_url = $event->get( 'ticket_url' );
160
- $rrule = $event->get( 'recurrence_rules' );
161
- $exrule = $event->get( 'exception_rules' );
162
- $exdate = $event->get( 'exception_dates' );
163
- $repeating_event = ! empty( $rrule );
164
- $exclude_event = ! empty( $exrule );
165
-
166
- $is_free = '';
167
- $free = $event->is_free();
168
- if ( ! empty( $free ) ) {
169
- $is_free = 'checked="checked" ';
170
- $cost = '';
171
- }
172
-
173
- if ( $repeating_event ) {
174
- $rrule_text = ucfirst(
175
- $this->_registry->get( 'recurrence.rule' )
176
- ->rrule_to_text( $rrule )
177
- );
178
- }
179
-
180
- if ( $exclude_event ) {
181
- $exrule_text = ucfirst(
182
- $this->_registry->get( 'recurrence.rule' )
183
- ->rrule_to_text( $exrule )
184
- );
185
- }
186
- } catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
187
- // Event does not exist.
188
- // Leave form fields undefined (= zero-length strings)
189
- $event = null;
190
- }
191
-
192
- // Time zone; display if set.
193
- $timezone = '';
194
- $timezone_string = null;
195
- $date_timezone = $this->_registry->get( 'date.timezone' );
196
-
197
- if (
198
- ! empty( $timezone_name ) &&
199
- $local_name = $date_timezone->get_name( $timezone_name )
200
- ) {
201
- $timezone_string = $local_name;
202
- }
203
- if ( null === $timezone_string ) {
204
- $timezone_string = $date_timezone->get_default_timezone();
205
- }
206
-
207
- if ( $timezone_string ) {
208
- $timezone = $this->_registry->get( 'date.system' )
209
- ->get_gmt_offset_expr( $timezone_string );
210
- }
211
-
212
- if ( empty( $timezone_name ) ) {
213
- /**
214
- * Actual Olsen timezone name is used when value is to be directly
215
- * exposed to user in some mean. It's possible to use named const.
216
- * `'sys.default'` only when passing value to date.time library.
217
- */
218
- $timezone_name = $date_timezone->get_default_timezone();
219
- }
220
-
221
- // This will store each of the accordion tabs' markup, and passed as an
222
- // argument to the final view.
223
- $boxes = array();
224
- $parent_event_id = null;
225
- if ( $event ) {
226
- $parent_event_id = $this->_registry->get( 'model.event.parent' )
227
- ->event_parent( $event->get( 'post_id' ) );
228
- }
229
- // ===============================
230
- // = Display event time and date =
231
- // ===============================
232
- $args = array(
233
- 'all_day_event' => $all_day_event,
234
- 'instant_event' => $instant_event,
235
- 'start' => $start,
236
- 'end' => $end,
237
- 'repeating_event' => $repeating_event,
238
- 'rrule' => $rrule,
239
- 'rrule_text' => $rrule_text,
240
- 'exclude_event' => $exclude_event,
241
- 'exrule' => $exrule,
242
- 'exrule_text' => $exrule_text,
243
- 'timezone' => $timezone,
244
- 'timezone_string' => $timezone_string,
245
- 'timezone_name' => $timezone_name,
246
- 'exdate' => $exdate,
247
- 'parent_event_id' => $parent_event_id,
248
- 'instance_id' => $instance_id,
249
- 'timezones_list' => $timezones_list,
250
- );
251
-
252
- $boxes[] = $theme_loader
253
- ->get_file( 'box_time_and_date.php', $args, true )
254
- ->get_content();
255
-
256
- // =================================================
257
- // = Display event location details and Google map =
258
- // =================================================
259
- $args = array(
260
- 'select_venue' => apply_filters( 'ai1ec_admin_pre_venue_html', '' ),
261
- 'save_venue' => apply_filters( 'ai1ec_admin_post_venue_html', '' ),
262
- 'venue' => $venue,
263
- 'country' => $country,
264
- 'address' => $address,
265
- 'city' => $city,
266
- 'province' => $province,
267
- 'postal_code' => $postal_code,
268
- 'google_map' => $google_map,
269
- 'show_map' => $show_map,
270
- 'show_coordinates' => $show_coordinates,
271
- 'longitude' => $longitude,
272
- 'latitude' => $latitude,
273
- 'coordinates' => $coordinates,
274
- );
275
- $boxes[] = $theme_loader
276
- ->get_file( 'box_event_location.php', $args, true )
277
- ->get_content();
278
-
279
- // ===================================
280
- // = Display event ticketing options =
281
- // ===================================
282
- if ( $event ) {
283
- $cost_type = get_post_meta(
284
- $event->get( 'post_id' ),
285
- '_ai1ec_cost_type',
286
- true
287
- );
288
- if ( ! $cost_type ) {
289
- if ( $ticket_url || $cost ) {
290
- $cost_type = 'external';
291
- } else {
292
- $cost_type = 'free';
293
- }
294
- }
295
- }
296
-
297
- $api = $this->_registry->get( 'model.api.api-ticketing' );
298
- $api_reg = $this->_registry->get( 'model.api.api-registration' );
299
- $ticketing = $api_reg->is_signed() && $api_reg->is_ticket_available() && $api_reg->is_ticket_enabled();
300
- $message = $api->get_sign_message();
301
- $ticket_error = null;
302
- $ticket_event_imported = false;
303
- $tickets = array( null );
304
- $tax_options = null;
305
-
306
- if ( ! $api_reg->is_ticket_available() ) {
307
- $message = __(
308
- 'Ticketing is currently not available for this website. Please, try again later.',
309
- AI1EC_PLUGIN_NAME
310
- );
311
- } else if ( ! $api_reg->is_ticket_enabled() ) {
312
- $message = __(
313
- 'Timely Ticketing saves time & money. Create ticketing/registration right here and now. You do not pay any ticketing fees (other than regular PayPal transaction costs). Create as many ticketing/registration as you\'d like.<br /><br />Ticketing feature is not enabled for this website. Please sign up for Ticketing plan <a href="https://time.ly/tickets-existing-users/" target="_blank">here</a>.',
314
- AI1EC_PLUGIN_NAME
315
- );
316
- }
317
-
318
- if ( $event ) {
319
- $is_ticket_event = ! is_null( $api->get_api_event_id( $event->get( 'post_id' ) ) );
320
- $ticket_event_account = $api->get_api_event_account( $event->get( 'post_id' ) );
321
- $ticket_event_imported = $api->is_ticket_event_imported( $event->get( 'post_id' ) );
322
- if ( $ticketing || $ticket_event_imported ) {
323
- if ( 'tickets' === $cost_type ) {
324
- if ( $ticket_event_imported ) {
325
- $response = json_decode( $api->get_ticket_types( $event->get( 'post_id' ) ) );
326
- if ( isset( $response->data ) && 0 < count( $response->data ) ) {
327
- $tickets = array_merge( $tickets, $response->data );
328
- }
329
- if ( isset( $response->error ) ) {
330
- $ticket_error = $response->error;
331
- }
332
- } else {
333
- $response = $api->get_event( $event->get( 'post_id' ) );
334
- if ( isset( $response->data ) && 0 < count( $response->data ) ) {
335
- $tickets = array_merge( $tickets, $response->data->ticket_types );
336
- $tax_options = $response->data->tax_options;
337
- }
338
- if ( isset( $response->error ) ) {
339
- $ticket_error = $response->error;
340
- }
341
- }
342
- }
343
- $uid = $event->get_uid();
344
- } else {
345
- $uid = $empty_event->get_uid();
346
- }
347
- $uid = $event->get_uid();
348
- } else {
349
- $is_ticket_event = false;
350
- $ticket_event_account = '';
351
- $uid = $empty_event->get_uid();
352
- }
353
-
354
- if ( $ticketing ) {
355
- if ( $event ) {
356
- $ticket_currency = $api->get_api_event_currency( $event->get( 'post_id' ) );
357
- if ( $api->is_ticket_event_from_another_account( $event->get( 'post_id' ) ) ) {
358
- $ticket_error = sprintf(
359
- __( 'This Event was created using a different account %s. Changes are not allowed.', AI1EC_PLUGIN_NAME ),
360
- $api->get_api_event_account( $event->get( 'post_id' ) )
361
- );
362
- }
363
- }
364
- if ( ! isset( $ticket_currency ) || is_null( $ticket_currency ) ) {
365
- //for new ticket events get the currency from the payments settings
366
- $payments_settings = $api->get_payment_settings();
367
- if ( null !== $payments_settings ) {
368
- $ticket_currency = $payments_settings['currency'];
369
- } else {
370
- $ticket_currency = 'USD';
371
- }
372
- }
373
- } else {
374
- $ticket_currency = '';
375
- }
376
-
377
- $args = array(
378
- 'cost' => $cost,
379
- 'cost_type' => $cost_type,
380
- 'ticket_url' => $ticket_url,
381
- 'event' => $empty_event,
382
- 'uid' => $uid,
383
- 'tickets' => $tickets,
384
- 'ticketing' => $ticketing,
385
- 'valid_payout_details' => $api->has_payment_settings(),
386
- 'tickets_message' => $message,
387
- 'start' => $start,
388
- 'end' => $end,
389
- 'tickets_loading_error' => $ticket_error,
390
- 'ticket_event_imported' => $ticket_event_imported,
391
- 'is_free' => $is_free,
392
- 'ticket_currency' => $ticket_currency,
393
- 'is_ticket_event' => $is_ticket_event,
394
- 'ticket_event_account' => $ticket_event_account,
395
- 'tax_options' => $tax_options
396
- );
397
-
398
- $boxes[] = $theme_loader
399
- ->get_file( 'box_event_cost.php', $args, true )
400
- ->get_content();
401
-
402
-
403
-
404
- // =========================================
405
- // = Display organizer contact information =
406
- // =========================================
407
- $submitter_html = null;
408
- if ( $event ) {
409
- $submitter_info = $event->get_submitter_info();
410
- if ( null !== $submitter_info ) {
411
- if ( 1 === $submitter_info['is_organizer'] ) {
412
- $submitter_html = Ai1ec_I18n::__( '<span class="ai1ec-info-text">The event was submitted by this Organizer.</span>' );
413
- } else if ( isset( $submitter_info['email'] ) ||
414
- isset( $submitter_info['name'] ) ) {
415
- $submitted_by = '';
416
- if ( false === ai1ec_is_blank ( $submitter_info['name'] ) ) {
417
- $submitted_by = sprintf( '<strong>%s</strong>', htmlspecialchars( $submitter_info['name'] ) );
418
- }
419
- if ( false === ai1ec_is_blank( $submitter_info['email'] ) ) {
420
- if ( '' !== $submitted_by ) {
421
- $submitted_by .= Ai1ec_I18n::__( ', email: ' );
422
- }
423
- $submitted_by .= sprintf( '<a href="mailto:%s" target="_top">%s</a>', $submitter_info['email'], $submitter_info['email'] ) ;
424
- }
425
- $submitter_html = sprintf( Ai1ec_I18n::__( '<span class="ai1ec-info-text">The event was submitted by %s.</span>' ),
426
- $submitted_by
427
- );
428
- }
429
- }
430
- }
431
- $args = array(
432
- 'contact_name' => $contact_name,
433
- 'contact_phone' => $contact_phone,
434
- 'contact_email' => $contact_email,
435
- 'contact_url' => $contact_url,
436
- 'event' => $empty_event,
437
- 'submitter_html' => $submitter_html
438
- );
439
- $boxes[] = $theme_loader
440
- ->get_file( 'box_event_contact.php', $args, true )
441
- ->get_content();
442
-
443
- // ==========================
444
- // = Parent/Child relations =
445
- // ==========================
446
- if ( $event ) {
447
- $parent = $this->_registry->get( 'model.event.parent' )
448
- ->get_parent_event( $event->get( 'post_id' ) );
449
- if ( $parent ) {
450
- try {
451
- $parent = $this->_registry->get( 'model.event', $parent );
452
- } catch ( Ai1ec_Event_Not_Found_Exception $exception ) { // ignore
453
- $parent = null;
454
- }
455
- }
456
- if ( $parent ) {
457
- $children = $this->_registry->get( 'model.event.parent' )
458
- ->get_child_event_objects( $event->get( 'post_id' ) );
459
- $args = compact( 'parent', 'children' );
460
- $args['registry'] = $this->_registry;
461
-
462
- $boxes[] = $theme_loader->get_file(
463
- 'box_event_children.php',
464
- $args,
465
- true
466
- )->get_content();
467
- }
468
-
469
- }
470
-
471
- $boxes = apply_filters( 'ai1ec_add_new_event_boxes', $boxes, $event );
472
- // Display the final view of the meta box.
473
- $args = array(
474
- 'boxes' => $boxes,
475
- );
476
-
477
- if ( $this->_is_post_event( $post ) ) {
478
- // ======================
479
- // = Display Box Review =
480
- // ======================
481
- $review = $this->_registry->get( 'model.review' );
482
- $review_content = $review->get_content( $theme_loader );
483
-
484
- if ( false === ai1ec_is_blank( $review_content ) ) {
485
- $args['review_box'] = $review_content;
486
- }
487
- }
488
-
489
- echo $theme_loader
490
- ->get_file( 'add_new_event_meta_box.php', $args, true )
491
- ->get_content();
492
- }
493
-
494
- /**
495
- * Add Banner Image meta box to the Add/Edit Event.
496
- *
497
- * @return void
498
- */
499
- public function banner_meta_box_view( $post ) {
500
- $banner_image_meta = get_post_meta( $post->ID, 'ai1ec_banner_image' );
501
- $theme_loader = $this->_registry->get( 'theme.loader' );
502
- $args = array(
503
- 'src' => $banner_image_meta && $banner_image_meta[0]
504
- ? $banner_image_meta[0] : false,
505
- 'set_text' => Ai1ec_I18n::__( 'Set banner image' ),
506
- 'remove_text' => Ai1ec_I18n::__( 'Remove banner image' ),
507
-
508
- );
509
- echo $theme_loader
510
- ->get_file( 'banner-image.twig', $args, true )
511
- ->get_content();
512
- }
513
-
514
- /**
515
- * disable_autosave method
516
- *
517
- * Callback to disable autosave script
518
- *
519
- * @param array $input List of scripts registered
520
- *
521
- * @return array Modified scripts list
522
- */
523
- public function disable_autosave( array $input ) {
524
- wp_deregister_script( 'autosave' );
525
- $autosave_key = array_search( 'autosave', $input );
526
- if ( false === $autosave_key || ! is_scalar( $autosave_key ) ) {
527
- unset( $input[$autosave_key] );
528
- }
529
- return $input;
530
- }
531
-
532
- /**
533
- * Renders Bootstrap inline alert.
534
- *
535
- * @param WP_Post $post Post object.
536
- *
537
- * @return void Method does not return.
538
- */
539
- public function event_inline_alert( $post ) {
540
- if ( $this->_is_post_event( $post ) ) {
541
- $theme_loader = $this->_registry->get( 'theme.loader' );
542
- echo $theme_loader->get_file( 'box_inline_warning.php', null, true )
543
- ->get_content();
544
- }
545
- }
546
-
547
- private function _is_post_event( $post ) {
548
- return isset( $post->post_type ) && AI1EC_POST_TYPE === $post->post_type;
549
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
 
551
  }
13
  */
14
  class Ai1ec_View_Add_New_Event extends Ai1ec_Base {
15
 
16
+ /**
17
+ * Create hook to display event meta box when creating or editing an event.
18
+ *
19
+ * @wp_hook add_meta_boxes
20
+ *
21
+ * @return void
22
+ */
23
+ public function event_meta_box_container() {
24
+ add_meta_box(
25
+ AI1EC_POST_TYPE,
26
+ Ai1ec_I18n::__( 'Event Details' ),
27
+ array( $this, 'meta_box_view' ),
28
+ AI1EC_POST_TYPE,
29
+ 'normal',
30
+ 'high'
31
+ );
32
+
33
+ add_meta_box(
34
+ AI1EC_POST_TYPE . '_features',
35
+ Ai1ec_I18n::__( 'Empower your calendar, build your community' ),
36
+ array( $this, 'features_info' ),
37
+ AI1EC_POST_TYPE,
38
+ 'side',
39
+ 'low'
40
+ );
41
+
42
+ }
43
+
44
+ /**
45
+ * Add Event Details meta box to the Add/Edit Event screen in the dashboard.
46
+ *
47
+ * @return void
48
+ */
49
+ public function features_info( $post ) {
50
+ $message = __(
51
+ '<ul class="ai1ec-features-list"><li><a href="https://time.ly/hub" target="_blank">Pull events from other calendars</a></li><li><a href="https://time.ly/hub" target="_blank">Pull events from Facebook</a></li><li><a href="https://time.ly/hub" target="_blank">Add a Newsletter</a></li><li><a href="https://time.ly/hub" target="_blank">Get public event submissions</a></li><li><a href="https://time.ly/hub" target="_blank">Charge people to post events</a></li><li><a href="https://time.ly/hub" target="_blank">Add social sharing</a></li><li><a href="https://time.ly/hub" target="_blank">And more</a></li></ul>',
52
+ AI1EC_PLUGIN_NAME
53
+ );
54
+
55
+ echo $message;
56
+ }
57
+
58
+
59
+ /**
60
+ * Add Event Details meta box to the Add/Edit Event screen in the dashboard.
61
+ *
62
+ * @return void
63
+ */
64
+ public function meta_box_view( $post ) {
65
+
66
+ $theme_loader = $this->_registry->get( 'theme.loader' );
67
+ $empty_event = $this->_registry->get( 'model.event' );
68
+
69
+ // ==================
70
+ // = Default values =
71
+ // ==================
72
+ // ATTENTION - When adding new fields to the event remember that you must
73
+ // also set up the duplicate-controller.
74
+ // TODO: Fix this duplication.
75
+ $all_day_event = '';
76
+ $instant_event = '';
77
+ $start = $this->_registry->get( 'date.time' );
78
+ $end = $this->_registry->get( 'date.time', '+1 hour' );
79
+ $timezone_name = null;
80
+ $timezones_list = $this->_registry->get( 'date.timezone' )->get_timezones( true );
81
+ $show_map = false;
82
+ $google_map = '';
83
+ $venue = '';
84
+ $country = '';
85
+ $address = '';
86
+ $city = '';
87
+ $province = '';
88
+ $postal_code = '';
89
+ $contact_name = '';
90
+ $contact_phone = '';
91
+ $contact_email = '';
92
+ $contact_url = '';
93
+ $cost = '';
94
+ $is_free = '';
95
+ $cost_type = 'free';
96
+ $rrule = '';
97
+ $rrule_text = '';
98
+ $repeating_event = false;
99
+ $exrule = '';
100
+ $exrule_text = '';
101
+ $exclude_event = false;
102
+ $exdate = '';
103
+ $show_coordinates = false;
104
+ $longitude = '';
105
+ $latitude = '';
106
+ $coordinates = '';
107
+ $ticket_url = '';
108
+
109
+ $instance_id = false;
110
+ if ( isset( $_REQUEST['instance'] ) ) {
111
+ $instance_id = absint( $_REQUEST['instance'] );
112
+ }
113
+ if ( $instance_id ) {
114
+ add_filter(
115
+ 'print_scripts_array',
116
+ array( $this, 'disable_autosave' )
117
+ );
118
+ }
119
+
120
+ try {
121
+ // on some php version, nested try catch blocks fail and the exception would never be caught.
122
+ // this is why we use this approach.
123
+ $excpt = null;
124
+ $event = null;
125
+ try {
126
+ $event = $this->_registry->get(
127
+ 'model.event',
128
+ get_the_ID(),
129
+ $instance_id
130
+ );
131
+ } catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
132
+ $ai1ec_localization_helper = $this->_registry
133
+ ->get( 'p28n.wpml' );
134
+ $translatable_id = $ai1ec_localization_helper
135
+ ->get_translatable_id();
136
+ if ( false !== $translatable_id ) {
137
+ $event = $this->_registry->get(
138
+ 'model.event',
139
+ $translatable_id,
140
+ $instance_id
141
+ );
142
+ }
143
+ }
144
+ if ( null !== $excpt ) {
145
+ throw $excpt;
146
+ }
147
+
148
+ // Existing event was found. Initialize form values with values from
149
+ // event object.
150
+ $all_day_event = $event->is_allday() ? 'checked' : '';
151
+ $instant_event = $event->is_instant() ? 'checked' : '';
152
+
153
+ $start = $event->get( 'start' );
154
+ $end = $event->get( 'end' );
155
+ $timezone_name = $event->get( 'timezone_name' );
156
+
157
+ $multi_day = $event->is_multiday();
158
+
159
+ $show_map = $event->get( 'show_map' );
160
+ $google_map = $show_map ? 'checked="checked"' : '';
161
+
162
+ $show_coordinates = $event->get( 'show_coordinates' );
163
+ $coordinates = $show_coordinates ? 'checked="checked"' : '';
164
+ $longitude = (float)$event->get( 'longitude', 0 );
165
+ $latitude = (float)$event->get( 'latitude', 0 );
166
+ // There is a known bug in Wordpress (https://core.trac.wordpress.org/ticket/15158) that saves 0 to the DB instead of null.
167
+ // We handle a special case here to avoid having the fields with a value of 0 when the user never inputted any coordinates
168
+ if ( ! $show_coordinates ) {
169
+ $longitude = '';
170
+ $latitude = '';
171
+ }
172
+
173
+ $venue = $event->get( 'venue' );
174
+ $country = $event->get( 'country' );
175
+ $address = $event->get( 'address' );
176
+ $city = $event->get( 'city' );
177
+ $province = $event->get( 'province' );
178
+ $postal_code = $event->get( 'postal_code' );
179
+ $contact_name = $event->get( 'contact_name' );
180
+ $contact_phone = $event->get( 'contact_phone' );
181
+ $contact_email = $event->get( 'contact_email' );
182
+ $contact_url = $event->get( 'contact_url' );
183
+ $cost = $event->get( 'cost' );
184
+ $ticket_url = $event->get( 'ticket_url' );
185
+ $rrule = $event->get( 'recurrence_rules' );
186
+ $exrule = $event->get( 'exception_rules' );
187
+ $exdate = $event->get( 'exception_dates' );
188
+ $repeating_event = ! empty( $rrule );
189
+ $exclude_event = ! empty( $exrule );
190
+
191
+ $is_free = '';
192
+ $free = $event->is_free();
193
+ if ( ! empty( $free ) ) {
194
+ $is_free = 'checked="checked" ';
195
+ $cost = '';
196
+ }
197
+
198
+ if ( $repeating_event ) {
199
+ $rrule_text = ucfirst(
200
+ $this->_registry->get( 'recurrence.rule' )
201
+ ->rrule_to_text( $rrule )
202
+ );
203
+ }
204
+
205
+ if ( $exclude_event ) {
206
+ $exrule_text = ucfirst(
207
+ $this->_registry->get( 'recurrence.rule' )
208
+ ->rrule_to_text( $exrule )
209
+ );
210
+ }
211
+ } catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
212
+ // Event does not exist.
213
+ // Leave form fields undefined (= zero-length strings)
214
+ $event = null;
215
+ }
216
+
217
+ // Time zone; display if set.
218
+ $timezone = '';
219
+ $timezone_string = null;
220
+ $date_timezone = $this->_registry->get( 'date.timezone' );
221
+
222
+ if (
223
+ ! empty( $timezone_name ) &&
224
+ $local_name = $date_timezone->get_name( $timezone_name )
225
+ ) {
226
+ $timezone_string = $local_name;
227
+ }
228
+ if ( null === $timezone_string ) {
229
+ $timezone_string = $date_timezone->get_default_timezone();
230
+ }
231
+
232
+ if ( $timezone_string ) {
233
+ $timezone = $this->_registry->get( 'date.system' )
234
+ ->get_gmt_offset_expr( $timezone_string );
235
+ }
236
+
237
+ if ( empty( $timezone_name ) ) {
238
+ /**
239
+ * Actual Olsen timezone name is used when value is to be directly
240
+ * exposed to user in some mean. It's possible to use named const.
241
+ * `'sys.default'` only when passing value to date.time library.
242
+ */
243
+ $timezone_name = $date_timezone->get_default_timezone();
244
+ }
245
+
246
+ // This will store each of the accordion tabs' markup, and passed as an
247
+ // argument to the final view.
248
+ $boxes = array();
249
+ $parent_event_id = null;
250
+ if ( $event ) {
251
+ $parent_event_id = $this->_registry->get( 'model.event.parent' )
252
+ ->event_parent( $event->get( 'post_id' ) );
253
+ }
254
+ // ===============================
255
+ // = Display event time and date =
256
+ // ===============================
257
+ $args = array(
258
+ 'all_day_event' => $all_day_event,
259
+ 'instant_event' => $instant_event,
260
+ 'start' => $start,
261
+ 'end' => $end,
262
+ 'repeating_event' => $repeating_event,
263
+ 'rrule' => $rrule,
264
+ 'rrule_text' => $rrule_text,
265
+ 'exclude_event' => $exclude_event,
266
+ 'exrule' => $exrule,
267
+ 'exrule_text' => $exrule_text,
268
+ 'timezone' => $timezone,
269
+ 'timezone_string' => $timezone_string,
270
+ 'timezone_name' => $timezone_name,
271
+ 'exdate' => $exdate,
272
+ 'parent_event_id' => $parent_event_id,
273
+ 'instance_id' => $instance_id,
274
+ 'timezones_list' => $timezones_list,
275
+ );
276
+
277
+ $boxes[] = $theme_loader
278
+ ->get_file( 'box_time_and_date.php', $args, true )
279
+ ->get_content();
280
+
281
+ // =================================================
282
+ // = Display event location details and Google map =
283
+ // =================================================
284
+ $args = array(
285
+ 'select_venue' => apply_filters( 'ai1ec_admin_pre_venue_html', '' ),
286
+ 'save_venue' => apply_filters( 'ai1ec_admin_post_venue_html', '' ),
287
+ 'venue' => $venue,
288
+ 'country' => $country,
289
+ 'address' => $address,
290
+ 'city' => $city,
291
+ 'province' => $province,
292
+ 'postal_code' => $postal_code,
293
+ 'google_map' => $google_map,
294
+ 'show_map' => $show_map,
295
+ 'show_coordinates' => $show_coordinates,
296
+ 'longitude' => $longitude,
297
+ 'latitude' => $latitude,
298
+ 'coordinates' => $coordinates,
299
+ );
300
+ $boxes[] = $theme_loader
301
+ ->get_file( 'box_event_location.php', $args, true )
302
+ ->get_content();
303
+
304
+ // ===================================
305
+ // = Display event ticketing options =
306
+ // ===================================
307
+ if ( $event ) {
308
+ $cost_type = get_post_meta(
309
+ $event->get( 'post_id' ),
310
+ '_ai1ec_cost_type',
311
+ true
312
+ );
313
+ if ( ! $cost_type ) {
314
+ if ( $ticket_url || $cost ) {
315
+ $cost_type = 'external';
316
+ } else {
317
+ $cost_type = 'free';
318
+ }
319
+ }
320
+ }
321
+
322
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
323
+ $api_reg = $this->_registry->get( 'model.api.api-registration' );
324
+ $ticketing = $api_reg->is_signed() && $api_reg->is_ticket_available() && $api_reg->is_ticket_enabled();
325
+ $message = $api->get_sign_message();
326
+ $ticket_error = null;
327
+ $ticket_event_imported = false;
328
+ $tickets = array( null );
329
+ $tax_options = null;
330
+
331
+ if ( ! $api_reg->is_ticket_available() ) {
332
+ $message = __(
333
+ 'Ticketing is currently not available for this website. Please, try again later.',
334
+ AI1EC_PLUGIN_NAME
335
+ );
336
+ } else if ( ! $api_reg->is_ticket_enabled() ) {
337
+ $message = __(
338
+ 'Timely Ticketing saves time & money. Create ticketing/registration right here and now. You do not pay any ticketing fees (other than regular PayPal transaction costs). Create as many ticketing/registration as you\'d like.<br /><br />Ticketing feature is not enabled for this website. Please sign up for Ticketing plan <a href="https://time.ly/tickets-existing-users/" target="_blank">here</a>.',
339
+ AI1EC_PLUGIN_NAME
340
+ );
341
+ }
342
+
343
+ if ( $event ) {
344
+ $is_ticket_event = ! is_null( $api->get_api_event_id( $event->get( 'post_id' ) ) );
345
+ $ticket_event_account = $api->get_api_event_account( $event->get( 'post_id' ) );
346
+ $ticket_event_imported = $api->is_ticket_event_imported( $event->get( 'post_id' ) );
347
+ if ( $ticketing || $ticket_event_imported ) {
348
+ if ( 'tickets' === $cost_type ) {
349
+ if ( $ticket_event_imported ) {
350
+ $response = json_decode( $api->get_ticket_types( $event->get( 'post_id' ) ) );
351
+ if ( isset( $response->data ) && 0 < count( $response->data ) ) {
352
+ $tickets = array_merge( $tickets, $response->data );
353
+ }
354
+ if ( isset( $response->error ) ) {
355
+ $ticket_error = $response->error;
356
+ }
357
+ } else {
358
+ $response = $api->get_event( $event->get( 'post_id' ) );
359
+ if ( isset( $response->data ) && 0 < count( $response->data ) ) {
360
+ $tickets = array_merge( $tickets, $response->data->ticket_types );
361
+ $tax_options = $response->data->tax_options;
362
+ }
363
+ if ( isset( $response->error ) ) {
364
+ $ticket_error = $response->error;
365
+ }
366
+ }
367
+ }
368
+ $uid = $event->get_uid();
369
+ } else {
370
+ $uid = $empty_event->get_uid();
371
+ }
372
+ $uid = $event->get_uid();
373
+ } else {
374
+ $is_ticket_event = false;
375
+ $ticket_event_account = '';
376
+ $uid = $empty_event->get_uid();
377
+ }
378
+
379
+ if ( $ticketing ) {
380
+ if ( $event ) {
381
+ $ticket_currency = $api->get_api_event_currency( $event->get( 'post_id' ) );
382
+ if ( $api->is_ticket_event_from_another_account( $event->get( 'post_id' ) ) ) {
383
+ $ticket_error = sprintf(
384
+ __( 'This Event was created using a different account %s. Changes are not allowed.', AI1EC_PLUGIN_NAME ),
385
+ $api->get_api_event_account( $event->get( 'post_id' ) )
386
+ );
387
+ }
388
+ }
389
+ if ( ! isset( $ticket_currency ) || is_null( $ticket_currency ) ) {
390
+ //for new ticket events get the currency from the payments settings
391
+ $payments_settings = $api->get_payment_settings();
392
+ if ( null !== $payments_settings ) {
393
+ $ticket_currency = $payments_settings['currency'];
394
+ } else {
395
+ $ticket_currency = 'USD';
396
+ }
397
+ }
398
+ } else {
399
+ $ticket_currency = '';
400
+ }
401
+
402
+ $args = array(
403
+ 'cost' => $cost,
404
+ 'cost_type' => $cost_type,
405
+ 'ticket_url' => $ticket_url,
406
+ 'event' => $empty_event,
407
+ 'uid' => $uid,
408
+ 'tickets' => $tickets,
409
+ 'ticketing' => $ticketing,
410
+ 'valid_payout_details' => $api->has_payment_settings(),
411
+ 'tickets_message' => $message,
412
+ 'start' => $start,
413
+ 'end' => $end,
414
+ 'tickets_loading_error' => $ticket_error,
415
+ 'ticket_event_imported' => $ticket_event_imported,
416
+ 'is_free' => $is_free,
417
+ 'ticket_currency' => $ticket_currency,
418
+ 'is_ticket_event' => $is_ticket_event,
419
+ 'ticket_event_account' => $ticket_event_account,
420
+ 'tax_options' => $tax_options
421
+ );
422
+
423
+ $boxes[] = $theme_loader
424
+ ->get_file( 'box_event_cost.php', $args, true )
425
+ ->get_content();
426
+
427
+
428
+
429
+ // =========================================
430
+ // = Display organizer contact information =
431
+ // =========================================
432
+ $submitter_html = null;
433
+ if ( $event ) {
434
+ $submitter_info = $event->get_submitter_info();
435
+ if ( null !== $submitter_info ) {
436
+ if ( 1 === $submitter_info['is_organizer'] ) {
437
+ $submitter_html = Ai1ec_I18n::__( '<span class="ai1ec-info-text">The event was submitted by this Organizer.</span>' );
438
+ } else if ( isset( $submitter_info['email'] ) ||
439
+ isset( $submitter_info['name'] ) ) {
440
+ $submitted_by = '';
441
+ if ( false === ai1ec_is_blank ( $submitter_info['name'] ) ) {
442
+ $submitted_by = sprintf( '<strong>%s</strong>', htmlspecialchars( $submitter_info['name'] ) );
443
+ }
444
+ if ( false === ai1ec_is_blank( $submitter_info['email'] ) ) {
445
+ if ( '' !== $submitted_by ) {
446
+ $submitted_by .= Ai1ec_I18n::__( ', email: ' );
447
+ }
448
+ $submitted_by .= sprintf( '<a href="mailto:%s" target="_top">%s</a>', $submitter_info['email'], $submitter_info['email'] ) ;
449
+ }
450
+ $submitter_html = sprintf( Ai1ec_I18n::__( '<span class="ai1ec-info-text">The event was submitted by %s.</span>' ),
451
+ $submitted_by
452
+ );
453
+ }
454
+ }
455
+ }
456
+ $args = array(
457
+ 'contact_name' => $contact_name,
458
+ 'contact_phone' => $contact_phone,
459
+ 'contact_email' => $contact_email,
460
+ 'contact_url' => $contact_url,
461
+ 'event' => $empty_event,
462
+ 'submitter_html' => $submitter_html
463
+ );
464
+ $boxes[] = $theme_loader
465
+ ->get_file( 'box_event_contact.php', $args, true )
466
+ ->get_content();
467
+
468
+ // ==========================
469
+ // = Parent/Child relations =
470
+ // ==========================
471
+ if ( $event ) {
472
+ $parent = $this->_registry->get( 'model.event.parent' )
473
+ ->get_parent_event( $event->get( 'post_id' ) );
474
+ if ( $parent ) {
475
+ try {
476
+ $parent = $this->_registry->get( 'model.event', $parent );
477
+ } catch ( Ai1ec_Event_Not_Found_Exception $exception ) { // ignore
478
+ $parent = null;
479
+ }
480
+ }
481
+ if ( $parent ) {
482
+ $children = $this->_registry->get( 'model.event.parent' )
483
+ ->get_child_event_objects( $event->get( 'post_id' ) );
484
+ $args = compact( 'parent', 'children' );
485
+ $args['registry'] = $this->_registry;
486
+
487
+ $boxes[] = $theme_loader->get_file(
488
+ 'box_event_children.php',
489
+ $args,
490
+ true
491
+ )->get_content();
492
+ }
493
+
494
+ }
495
+
496
+ $boxes = apply_filters( 'ai1ec_add_new_event_boxes', $boxes, $event );
497
+ // Display the final view of the meta box.
498
+ $args = array(
499
+ 'boxes' => $boxes,
500
+ );
501
+
502
+ if ( $this->_is_post_event( $post ) ) {
503
+ // ======================
504
+ // = Display Box Review =
505
+ // ======================
506
+ $review = $this->_registry->get( 'model.review' );
507
+ $review_content = $review->get_content( $theme_loader );
508
+
509
+ if ( false === ai1ec_is_blank( $review_content ) ) {
510
+ $args['review_box'] = $review_content;
511
+ }
512
+ }
513
+
514
+ echo $theme_loader
515
+ ->get_file( 'add_new_event_meta_box.php', $args, true )
516
+ ->get_content();
517
+ }
518
+
519
+ /**
520
+ * Add Banner Image meta box to the Add/Edit Event.
521
+ *
522
+ * @return void
523
+ */
524
+ public function banner_meta_box_view( $post ) {
525
+ $banner_image_meta = get_post_meta( $post->ID, 'ai1ec_banner_image' );
526
+ $theme_loader = $this->_registry->get( 'theme.loader' );
527
+ $args = array(
528
+ 'src' => $banner_image_meta && $banner_image_meta[0]
529
+ ? $banner_image_meta[0] : false,
530
+ 'set_text' => Ai1ec_I18n::__( 'Set banner image' ),
531
+ 'remove_text' => Ai1ec_I18n::__( 'Remove banner image' ),
532
+
533
+ );
534
+ echo $theme_loader
535
+ ->get_file( 'banner-image.twig', $args, true )
536
+ ->get_content();
537
+ }
538
+
539
+ /**
540
+ * disable_autosave method
541
+ *
542
+ * Callback to disable autosave script
543
+ *
544
+ * @param array $input List of scripts registered
545
+ *
546
+ * @return array Modified scripts list
547
+ */
548
+ public function disable_autosave( array $input ) {
549
+ wp_deregister_script( 'autosave' );
550
+ $autosave_key = array_search( 'autosave', $input );
551
+ if ( false === $autosave_key || ! is_scalar( $autosave_key ) ) {
552
+ unset( $input[$autosave_key] );
553
+ }
554
+ return $input;
555
+ }
556
+
557
+ /**
558
+ * Renders Bootstrap inline alert.
559
+ *
560
+ * @param WP_Post $post Post object.
561
+ *
562
+ * @return void Method does not return.
563
+ */
564
+ public function event_inline_alert( $post ) {
565
+ if ( $this->_is_post_event( $post ) ) {
566
+ $theme_loader = $this->_registry->get( 'theme.loader' );
567
+ echo $theme_loader->get_file( 'box_inline_warning.php', null, true )
568
+ ->get_content();
569
+ }
570
+ }
571
+
572
+ private function _is_post_event( $post ) {
573
+ return isset( $post->post_type ) && AI1EC_POST_TYPE === $post->post_type;
574
+ }
575
 
576
  }
app/view/admin/add-ons.php CHANGED
@@ -10,88 +10,88 @@
10
  * @subpackage AI1EC.View
11
  */
12
  class Ai1ec_View_Add_Ons extends Ai1ec_View_Admin_Abstract {
13
- /**
14
- * Adds page to the menu.
15
- *
16
- * @wp_hook admin_menu
17
- *
18
- * @return void
19
- */
20
- public function add_page() {
21
- // =======================
22
- // = Calendar Add Ons Page =
23
- // =======================
24
- add_submenu_page(
25
- AI1EC_ADMIN_BASE_URL,
26
- Ai1ec_I18n::__( 'Add-ons' ),
27
- Ai1ec_I18n::__( 'Add-ons' ),
28
- 'manage_ai1ec_feeds',
29
- AI1EC_PLUGIN_NAME . '-add-ons',
30
- array( $this, 'display_page' )
31
- );
32
- }
33
- /**
34
- * Display Add Ons list page.
35
- *
36
- * @return void
37
- */
38
- public function display_page() {
39
- wp_enqueue_style(
40
- 'ai1ec_addons.css',
41
- AI1EC_ADMIN_THEME_CSS_URL . 'addons.css',
42
- array(),
43
- AI1EC_VERSION
44
- );
45
- $content = get_site_transient( 'ai1ec_timely_addons' );
46
- $is_error = false;
47
- if (
48
- false === $content ||
49
- (
50
- defined( 'AI1EC_DEBUG' ) &&
51
- AI1EC_DEBUG
52
- )
53
- ) {
54
- $is_error = true;
55
- $feed = wp_remote_get( AI1EC_TIMELY_ADDONS_URI );
56
- if ( ! is_wp_error( $feed ) ) {
57
- $content = json_decode( wp_remote_retrieve_body( $feed ) );
58
- if ( null !== $content ) {
59
- set_site_transient( 'ai1ec_timely_addons', $content, 3600 );
60
- $is_error = false;
61
- }
62
- }
63
- }
64
- $this->_registry->get( 'theme.loader' )->get_file(
65
- 'add-ons-list/page.twig',
66
- array(
67
- 'labels' => array(
68
- 'title' => Ai1ec_I18n::__(
69
- 'Add-ons for All In One Event Calendar'
70
- ),
71
- 'button_title' => Ai1ec_I18n::__(
72
- 'Browse All Add-ons'
73
- ),
74
- 'paragraph_content' => Ai1ec_I18n::__(
75
- 'These add-ons extend the functionality of the All-in-One Event Calendar.'
76
- ),
77
- 'error' => Ai1ec_I18n::__(
78
- 'There was an error retrieving the extensions list from the server. Please try again later.'
79
- ),
80
- ),
81
- 'content' => $content,
82
- 'is_error' => $is_error,
83
- ),
84
- true
85
- )->render();
86
- }
87
 
88
- public function add_meta_box() {
89
- }
90
 
91
- public function display_meta_box( $object, $box ) {
92
- }
93
 
94
- public function handle_post() {
95
- }
96
 
97
  }
10
  * @subpackage AI1EC.View
11
  */
12
  class Ai1ec_View_Add_Ons extends Ai1ec_View_Admin_Abstract {
13
+ /**
14
+ * Adds page to the menu.
15
+ *
16
+ * @wp_hook admin_menu
17
+ *
18
+ * @return void
19
+ */
20
+ public function add_page() {
21
+ // =======================
22
+ // = Calendar Add Ons Page =
23
+ // =======================
24
+ add_submenu_page(
25
+ AI1EC_ADMIN_BASE_URL,
26
+ Ai1ec_I18n::__( 'Add-ons' ),
27
+ Ai1ec_I18n::__( 'Add-ons' ),
28
+ 'manage_ai1ec_feeds',
29
+ AI1EC_PLUGIN_NAME . '-add-ons',
30
+ array( $this, 'display_page' )
31
+ );
32
+ }
33
+ /**
34
+ * Display Add Ons list page.
35
+ *
36
+ * @return void
37
+ */
38
+ public function display_page() {
39
+ wp_enqueue_style(
40
+ 'ai1ec_addons.css',
41
+ AI1EC_ADMIN_THEME_CSS_URL . 'addons.css',
42
+ array(),
43
+ AI1EC_VERSION
44
+ );
45
+ $content = get_site_transient( 'ai1ec_timely_addons' );
46
+ $is_error = false;
47
+ if (
48
+ false === $content ||
49
+ (
50
+ defined( 'AI1EC_DEBUG' ) &&
51
+ AI1EC_DEBUG
52
+ )
53
+ ) {
54
+ $is_error = true;
55
+ $feed = wp_remote_get( AI1EC_TIMELY_ADDONS_URI );
56
+ if ( ! is_wp_error( $feed ) ) {
57
+ $content = json_decode( wp_remote_retrieve_body( $feed ) );
58
+ if ( null !== $content ) {
59
+ set_site_transient( 'ai1ec_timely_addons', $content, 3600 );
60
+ $is_error = false;
61
+ }
62
+ }
63
+ }
64
+ $this->_registry->get( 'theme.loader' )->get_file(
65
+ 'add-ons-list/page.twig',
66
+ array(
67
+ 'labels' => array(
68
+ 'title' => Ai1ec_I18n::__(
69
+ 'Add-ons for All In One Event Calendar'
70
+ ),
71
+ 'button_title' => Ai1ec_I18n::__(
72
+ 'Browse All Add-ons'
73
+ ),
74
+ 'paragraph_content' => Ai1ec_I18n::__(
75
+ 'These add-ons extend the functionality of the All-in-One Event Calendar.'
76
+ ),
77
+ 'error' => Ai1ec_I18n::__(
78
+ 'There was an error retrieving the extensions list from the server. Please try again later.'
79
+ ),
80
+ ),
81
+ 'content' => $content,
82
+ 'is_error' => $is_error,
83
+ ),
84
+ true
85
+ )->render();
86
+ }
87
 
88
+ public function add_meta_box() {
89
+ }
90
 
91
+ public function display_meta_box( $object, $box ) {
92
+ }
93
 
94
+ public function handle_post() {
95
+ }
96
 
97
  }
app/view/admin/all-events.php CHANGED
@@ -2,259 +2,259 @@
2
 
3
  class Ai1ec_View_Admin_All_Events extends Ai1ec_Base {
4
 
5
- /**
6
- * change_columns function
7
- *
8
- * Adds Event date/time column to our custom post type
9
- * and renames Date column to Post Date
10
- *
11
- * @param array $columns Existing columns
12
- *
13
- * @return array Updated columns array
14
- */
15
- public function change_columns( array $columns = array() ) {
16
- $columns['author'] = __( 'Author', AI1EC_PLUGIN_NAME );
17
- $columns['date'] = __( 'Post Date', AI1EC_PLUGIN_NAME );
18
- $columns['ai1ec_event_date'] = __( 'Event date/time', AI1EC_PLUGIN_NAME );
19
- $api = $this->_registry->get( 'model.api.api-ticketing' );
20
- if ( $api->is_signed() ) {
21
- $columns['tickets'] = __( 'Ticket Types', AI1EC_PLUGIN_NAME );
22
- }
23
- return $columns;
24
- }
25
 
26
- /**
27
- * orderby function
28
- *
29
- * Orders events by event date
30
- *
31
- * @param string $orderby Orderby sql
32
- * @param object $wp_query
33
- *
34
- * @return void
35
- **/
36
- public function orderby( $orderby, $wp_query ) {
37
 
38
- $db = $this->_registry->get( 'dbi.dbi' );
39
- $aco = $this->_registry->get( 'acl.aco' );
40
 
41
- if( true === $aco->is_all_events_page() ) {
42
- $wp_query->query = wp_parse_args( $wp_query->query );
43
- $table_name = $db->get_table_name( 'ai1ec_events' );
44
- $posts = $db->get_table_name( 'posts' );
45
- if( isset( $wp_query->query['orderby'] ) && 'ai1ec_event_date' === @$wp_query->query['orderby'] ) {
46
- $orderby = "(SELECT start FROM {$table_name} WHERE post_id = {$posts}.ID) " . $wp_query->get('order');
47
- } else if( empty( $wp_query->query['orderby'] ) || $wp_query->query['orderby'] === 'menu_order title' ) {
48
- $orderby = "(SELECT start FROM {$table_name} WHERE post_id = {$posts}.ID) " . 'desc';
49
- }
50
- }
51
- return $orderby;
52
- }
53
 
54
- /**
55
- * custom_columns function
56
- *
57
- * Adds content for custom columns
58
- *
59
- * @return void
60
- **/
61
- public function custom_columns( $column, $post_id ) {
62
- if ( 'ai1ec_event_date' === $column ) {
63
- try {
64
- $event = $this->_registry->get( 'model.event', $post_id );
65
- $time = $this->_registry->get( 'view.event.time' );
66
- echo $time->get_timespan_html( $event );
67
- } catch ( Exception $e ) {
68
- // event wasn't found, output empty string
69
- echo '';
70
- }
71
- } else if ( 'tickets' === $column ) {
72
- $api = $this->_registry->get( 'model.api.api-ticketing' );
73
- if ( $api->is_ticket_event_imported( $post_id ) ) {
74
- echo '';
75
- } else {
76
- try {
77
- $event = $this->_registry->get( 'model.event', $post_id );
78
- $api = $this->_registry->get( 'model.api.api-ticketing' );
79
- $api_event_id = $api->get_api_event_id( $post_id );
80
- if ( $api_event_id ) {
81
- echo '<a href="#" class="ai1ec-has-tickets" data-post-id="'
82
- . $post_id . '">'
83
- . __( 'Ticketing Details', AI1EC_PLUGIN_NAME ) . '</a>';
84
- }
85
 
86
- } catch ( Exception $e ) {
87
- // event wasn't found, output empty string
88
- echo '';
89
- }
90
- }
91
- }
92
- }
93
 
94
- /**
95
- * sortable_columns function
96
- *
97
- * Enable sorting of columns
98
- *
99
- * @return void
100
- **/
101
- public function sortable_columns( $columns ) {
102
- $columns['ai1ec_event_date'] = 'ai1ec_event_date';
103
- $columns['author'] = 'author';
104
- return $columns;
105
- }
106
 
107
- /**
108
- * taxonomy_filter_restrict_manage_posts function
109
- *
110
- * Adds filter dropdowns for event categories and event tags.
111
- * Adds filter dropdowns for event authors.
112
- *
113
- * @uses wp_dropdown_users To create a dropdown with current user selected.
114
- *
115
- * @return void
116
- **/
117
- function taxonomy_filter_restrict_manage_posts() {
118
- global $typenow;
119
 
120
- // =============================================
121
- // = add the dropdowns only on the events page =
122
- // =============================================
123
- if( $typenow === AI1EC_POST_TYPE ) {
124
- $filters = get_object_taxonomies( $typenow );
125
- foreach( $filters as $tax_slug ) {
126
- $tax_obj = get_taxonomy( $tax_slug );
127
- wp_dropdown_categories( array(
128
- 'show_option_all' => __( 'Show All ', AI1EC_PLUGIN_NAME ) . $tax_obj->label,
129
- 'taxonomy' => $tax_slug,
130
- 'name' => $tax_obj->name,
131
- 'orderby' => 'name',
132
- 'selected' => isset( $_GET[$tax_slug] ) ? $_GET[$tax_slug] : '',
133
- 'hierarchical' => $tax_obj->hierarchical,
134
- 'show_count' => true,
135
- 'hide_if_empty' => true,
136
- 'value_field' => 'slug',
137
- ));
138
- }
139
- $args = array(
140
- 'name' => 'author',
141
- 'show_option_all' => __( 'Show All Authors', AI1EC_PLUGIN_NAME ),
142
- );
143
- if ( isset( $_GET['user'] ) ) {
144
- $args['selected'] = (int)$_GET['user'];
145
- }
146
- wp_dropdown_users($args);
147
- }
148
- }
149
 
150
- /**
151
- * taxonomy_filter_post_type_request function
152
- *
153
- * Adds filtering of events list by event tags and event categories
154
- *
155
- * @return void
156
- **/
157
- public function taxonomy_filter_post_type_request( $query ) {
158
- global $pagenow, $typenow;
159
- if( 'edit.php' === $pagenow ) {
160
- $filters = get_object_taxonomies( $typenow );
161
- foreach( $filters as $tax_slug ) {
162
- $var = &$query->query_vars[$tax_slug];
163
- if( isset( $var ) ) {
164
- $term = null;
165
 
166
- if( is_numeric( $var ) ) {
167
- $term = get_term_by( 'id', $var, $tax_slug );
168
- } else {
169
- $term = get_term_by( 'slug', $var, $tax_slug );
170
- }
171
 
172
- if( isset( $term->slug ) ) {
173
- $var = $term->slug;
174
- }
175
- }
176
- }
177
- }
178
- // ===========================
179
- // = Order by Event date ASC =
180
- // ===========================
181
- if( 'ai1ec_event' === $typenow ) {
182
- if ( ! array_key_exists( 'orderby', $query->query_vars ) ) {
183
- $query->query_vars['orderby'] = 'ai1ec_event_date';
184
- $query->query_vars['order'] = 'desc';
185
- }
186
- }
187
- }
188
 
189
- /**
190
- * CSS and templates files needed for ticketing.
191
- */
192
- public function add_ticketing_styling() {
193
- // Add CSS
194
- $this->_registry->get( 'css.admin' )->admin_enqueue_scripts(
195
- 'ai1ec_event_page_all-in-one-event-calendar-settings'
196
- );
197
- $this->_registry->get( 'css.admin' )->process_enqueue(
198
- array(
199
- array( 'style', 'ticketing.css', ),
200
- )
201
- );
202
- }
203
 
204
- /**
205
- * Get ticket details by Event id.
206
- */
207
- public function show_ticket_details() {
208
- $post_id = $_POST['ai1ec_event_id'];
209
- $api = $this->_registry->get( 'model.api.api-ticketing' );
210
- if ( $api->is_ticket_event_from_another_account( $post_id ) ) {
211
- $tickets = json_encode(
212
- array( 'data' => array(), 'error' =>
213
- sprintf(
214
- __( 'This Event was created using a different account %s. Changes are not allowed.', AI1EC_PLUGIN_NAME ),
215
- $api->get_api_event_account( $post_id )
216
- )
217
- ) );
218
- } else {
219
- $tickets = $api->get_ticket_types( $post_id );
220
- }
221
- echo $tickets;
222
- wp_die();
223
- }
224
 
225
- /**
226
- * Get attendees list.
227
- */
228
- public function show_attendees() {
229
- $post_id = $_POST['ai1ec_event_id'];
230
- $api = $this->_registry->get( 'model.api.api-ticketing' );
231
- $tickets = $api->get_tickets( $post_id );
232
- echo $tickets;
233
- wp_die();
234
- }
235
 
236
- /**
237
- * count_future_events function
238
- *
239
- * @return Count future events
240
- **/
241
- public function count_future_events( $user_id = null ) {
242
- if ( is_admin() ) {
243
- $settings = $this->_registry->get( 'model.settings' );
244
- $current_time = $this->_registry->get( 'date.time' );
245
- $current_time->set_timezone( $settings->get( 'timezone_string' ) );
246
- $current_time = $current_time->format_to_gmt();
247
- $user_id = get_current_user_id();
248
- $where = get_posts_by_author_sql( AI1EC_POST_TYPE, true, $user_id );
249
- $db = $this->_registry->get( 'dbi.dbi' );
250
- $posts = $db->get_table_name( 'posts' );
251
- $table_name = $db->get_table_name( 'ai1ec_events' );
252
- $sql = "SELECT COUNT(*) FROM $table_name INNER JOIN $posts on $table_name.post_id = {$posts}.ID"
253
- . " $where AND $table_name.start > $current_time"; //future event
254
- return $db->get_var( $sql );
255
- } else {
256
- return 0;
257
- }
258
- }
259
 
260
  }
2
 
3
  class Ai1ec_View_Admin_All_Events extends Ai1ec_Base {
4
 
5
+ /**
6
+ * change_columns function
7
+ *
8
+ * Adds Event date/time column to our custom post type
9
+ * and renames Date column to Post Date
10
+ *
11
+ * @param array $columns Existing columns
12
+ *
13
+ * @return array Updated columns array
14
+ */
15
+ public function change_columns( array $columns = array() ) {
16
+ $columns['author'] = __( 'Author', AI1EC_PLUGIN_NAME );
17
+ $columns['date'] = __( 'Post Date', AI1EC_PLUGIN_NAME );
18
+ $columns['ai1ec_event_date'] = __( 'Event date/time', AI1EC_PLUGIN_NAME );
19
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
20
+ if ( $api->is_signed() ) {
21
+ $columns['tickets'] = __( 'Ticket Types', AI1EC_PLUGIN_NAME );
22
+ }
23
+ return $columns;
24
+ }
25
 
26
+ /**
27
+ * orderby function
28
+ *
29
+ * Orders events by event date
30
+ *
31
+ * @param string $orderby Orderby sql
32
+ * @param object $wp_query
33
+ *
34
+ * @return void
35
+ **/
36
+ public function orderby( $orderby, $wp_query ) {
37
 
38
+ $db = $this->_registry->get( 'dbi.dbi' );
39
+ $aco = $this->_registry->get( 'acl.aco' );
40
 
41
+ if( true === $aco->is_all_events_page() ) {
42
+ $wp_query->query = wp_parse_args( $wp_query->query );
43
+ $table_name = $db->get_table_name( 'ai1ec_events' );
44
+ $posts = $db->get_table_name( 'posts' );
45
+ if( isset( $wp_query->query['orderby'] ) && 'ai1ec_event_date' === @$wp_query->query['orderby'] ) {
46
+ $orderby = "(SELECT start FROM {$table_name} WHERE post_id = {$posts}.ID) " . $wp_query->get('order');
47
+ } else if( empty( $wp_query->query['orderby'] ) || $wp_query->query['orderby'] === 'menu_order title' ) {
48
+ $orderby = "(SELECT start FROM {$table_name} WHERE post_id = {$posts}.ID) " . 'desc';
49
+ }
50
+ }
51
+ return $orderby;
52
+ }
53
 
54
+ /**
55
+ * custom_columns function
56
+ *
57
+ * Adds content for custom columns
58
+ *
59
+ * @return void
60
+ **/
61
+ public function custom_columns( $column, $post_id ) {
62
+ if ( 'ai1ec_event_date' === $column ) {
63
+ try {
64
+ $event = $this->_registry->get( 'model.event', $post_id );
65
+ $time = $this->_registry->get( 'view.event.time' );
66
+ echo $time->get_timespan_html( $event );
67
+ } catch ( Exception $e ) {
68
+ // event wasn't found, output empty string
69
+ echo '';
70
+ }
71
+ } else if ( 'tickets' === $column ) {
72
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
73
+ if ( $api->is_ticket_event_imported( $post_id ) ) {
74
+ echo '';
75
+ } else {
76
+ try {
77
+ $event = $this->_registry->get( 'model.event', $post_id );
78
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
79
+ $api_event_id = $api->get_api_event_id( $post_id );
80
+ if ( $api_event_id ) {
81
+ echo '<a href="#" class="ai1ec-has-tickets" data-post-id="'
82
+ . $post_id . '">'
83
+ . __( 'Ticketing Details', AI1EC_PLUGIN_NAME ) . '</a>';
84
+ }
85
 
86
+ } catch ( Exception $e ) {
87
+ // event wasn't found, output empty string
88
+ echo '';
89
+ }
90
+ }
91
+ }
92
+ }
93
 
94
+ /**
95
+ * sortable_columns function
96
+ *
97
+ * Enable sorting of columns
98
+ *
99
+ * @return void
100
+ **/
101
+ public function sortable_columns( $columns ) {
102
+ $columns['ai1ec_event_date'] = 'ai1ec_event_date';
103
+ $columns['author'] = 'author';
104
+ return $columns;
105
+ }
106
 
107
+ /**
108
+ * taxonomy_filter_restrict_manage_posts function
109
+ *
110
+ * Adds filter dropdowns for event categories and event tags.
111
+ * Adds filter dropdowns for event authors.
112
+ *
113
+ * @uses wp_dropdown_users To create a dropdown with current user selected.
114
+ *
115
+ * @return void
116
+ **/
117
+ function taxonomy_filter_restrict_manage_posts() {
118
+ global $typenow;
119
 
120
+ // =============================================
121
+ // = add the dropdowns only on the events page =
122
+ // =============================================
123
+ if( $typenow === AI1EC_POST_TYPE ) {
124
+ $filters = get_object_taxonomies( $typenow );
125
+ foreach( $filters as $tax_slug ) {
126
+ $tax_obj = get_taxonomy( $tax_slug );
127
+ wp_dropdown_categories( array(
128
+ 'show_option_all' => __( 'Show All ', AI1EC_PLUGIN_NAME ) . $tax_obj->label,
129
+ 'taxonomy' => $tax_slug,
130
+ 'name' => $tax_obj->name,
131
+ 'orderby' => 'name',
132
+ 'selected' => isset( $_GET[$tax_slug] ) ? $_GET[$tax_slug] : '',
133
+ 'hierarchical' => $tax_obj->hierarchical,
134
+ 'show_count' => true,
135
+ 'hide_if_empty' => true,
136
+ 'value_field' => 'slug',
137
+ ));
138
+ }
139
+ $args = array(
140
+ 'name' => 'author',
141
+ 'show_option_all' => __( 'Show All Authors', AI1EC_PLUGIN_NAME ),
142
+ );
143
+ if ( isset( $_GET['user'] ) ) {
144
+ $args['selected'] = (int)$_GET['user'];
145
+ }
146
+ wp_dropdown_users($args);
147
+ }
148
+ }
149
 
150
+ /**
151
+ * taxonomy_filter_post_type_request function
152
+ *
153
+ * Adds filtering of events list by event tags and event categories
154
+ *
155
+ * @return void
156
+ **/
157
+ public function taxonomy_filter_post_type_request( $query ) {
158
+ global $pagenow, $typenow;
159
+ if( 'edit.php' === $pagenow ) {
160
+ $filters = get_object_taxonomies( $typenow );
161
+ foreach( $filters as $tax_slug ) {
162
+ $var = &$query->query_vars[$tax_slug];
163
+ if( isset( $var ) ) {
164
+ $term = null;
165
 
166
+ if( is_numeric( $var ) ) {
167
+ $term = get_term_by( 'id', $var, $tax_slug );
168
+ } else {
169
+ $term = get_term_by( 'slug', $var, $tax_slug );
170
+ }
171
 
172
+ if( isset( $term->slug ) ) {
173
+ $var = $term->slug;
174
+ }
175
+ }
176
+ }
177
+ }
178
+ // ===========================
179
+ // = Order by Event date ASC =
180
+ // ===========================
181
+ if( 'ai1ec_event' === $typenow ) {
182
+ if ( ! array_key_exists( 'orderby', $query->query_vars ) ) {
183
+ $query->query_vars['orderby'] = 'ai1ec_event_date';
184
+ $query->query_vars['order'] = 'desc';
185
+ }
186
+ }
187
+ }
188
 
189
+ /**
190
+ * CSS and templates files needed for ticketing.
191
+ */
192
+ public function add_ticketing_styling() {
193
+ // Add CSS
194
+ $this->_registry->get( 'css.admin' )->admin_enqueue_scripts(
195
+ 'ai1ec_event_page_all-in-one-event-calendar-settings'
196
+ );
197
+ $this->_registry->get( 'css.admin' )->process_enqueue(
198
+ array(
199
+ array( 'style', 'ticketing.css', ),
200
+ )
201
+ );
202
+ }
203
 
204
+ /**
205
+ * Get ticket details by Event id.
206
+ */
207
+ public function show_ticket_details() {
208
+ $post_id = $_POST['ai1ec_event_id'];
209
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
210
+ if ( $api->is_ticket_event_from_another_account( $post_id ) ) {
211
+ $tickets = json_encode(
212
+ array( 'data' => array(), 'error' =>
213
+ sprintf(
214
+ __( 'This Event was created using a different account %s. Changes are not allowed.', AI1EC_PLUGIN_NAME ),
215
+ $api->get_api_event_account( $post_id )
216
+ )
217
+ ) );
218
+ } else {
219
+ $tickets = $api->get_ticket_types( $post_id );
220
+ }
221
+ echo $tickets;
222
+ wp_die();
223
+ }
224
 
225
+ /**
226
+ * Get attendees list.
227
+ */
228
+ public function show_attendees() {
229
+ $post_id = $_POST['ai1ec_event_id'];
230
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
231
+ $tickets = $api->get_tickets( $post_id );
232
+ echo $tickets;
233
+ wp_die();
234
+ }
235
 
236
+ /**
237
+ * count_future_events function
238
+ *
239
+ * @return Count future events
240
+ **/
241
+ public function count_future_events( $user_id = null ) {
242
+ if ( is_admin() ) {
243
+ $settings = $this->_registry->get( 'model.settings' );
244
+ $current_time = $this->_registry->get( 'date.time' );
245
+ $current_time->set_timezone( $settings->get( 'timezone_string' ) );
246
+ $current_time = $current_time->format_to_gmt();
247
+ $user_id = get_current_user_id();
248
+ $where = get_posts_by_author_sql( AI1EC_POST_TYPE, true, $user_id );
249
+ $db = $this->_registry->get( 'dbi.dbi' );
250
+ $posts = $db->get_table_name( 'posts' );
251
+ $table_name = $db->get_table_name( 'ai1ec_events' );
252
+ $sql = "SELECT COUNT(*) FROM $table_name INNER JOIN $posts on $table_name.post_id = {$posts}.ID"
253
+ . " $where AND $table_name.start > $current_time"; //future event
254
+ return $db->get_var( $sql );
255
+ } else {
256
+ return 0;
257
+ }
258
+ }
259
 
260
  }
app/view/admin/calendar-feeds.php CHANGED
@@ -11,102 +11,102 @@
11
  */
12
  class Ai1ec_View_Calendar_Feeds extends Ai1ec_View_Admin_Abstract {
13
 
14
- /**
15
- * Adds page to the menu.
16
- *
17
- * @wp_hook admin_menu
18
- *
19
- * @return void
20
- */
21
- public function add_page() {
22
- // =======================
23
- // = Calendar Feeds Page =
24
- // =======================
25
- $calendar_feeds = add_submenu_page(
26
- AI1EC_ADMIN_BASE_URL,
27
- Ai1ec_I18n::__( 'Import Feeds' ),
28
- Ai1ec_I18n::__( 'Import Feeds' ),
29
- 'manage_ai1ec_feeds',
30
- AI1EC_PLUGIN_NAME . '-feeds',
31
- array( $this, 'display_page' )
32
- );
33
- $this->_registry->get( 'model.settings' )
34
- ->set( 'feeds_page', $calendar_feeds );
35
- }
36
 
37
- /**
38
- * Adds metabox to the page.
39
- *
40
- * @wp_hook admin_init
41
- *
42
- * @return void
43
- */
44
- public function add_meta_box() {
45
- // Add the 'ICS Import Settings' meta box.
46
- add_meta_box(
47
- 'ai1ec-feeds',
48
- Ai1ec_I18n::_x( 'Feed Subscriptions', 'meta box' ),
49
- array( $this, 'display_meta_box' ),
50
- $this->_registry->get( 'model.settings' )->get( 'feeds_page' ),
51
- 'left',
52
- 'default'
53
- );
54
- }
55
 
56
- /**
57
- * Display this plugin's feeds page in the admin.
58
- *
59
- * @return void
60
- */
61
- public function display_page() {
62
- $settings = $this->_registry->get( 'model.settings' );
63
- $loader = $this->_registry->get( 'theme.loader' );
64
- $args = array(
65
- 'title' => __(
66
- 'All-in-One Event Calendar: Import Feeds',
67
- AI1EC_PLUGIN_NAME
68
- ),
69
- 'settings_page' => $settings->get( 'feeds_page' ),
70
- 'calendar_settings' => false,
71
- );
72
- $file = $loader->get_file( 'feeds_settings.php', $args, true );
73
- $file->render();
74
- }
75
 
76
- /**
77
- * Renders the contents of the Calendar Feeds meta box.
78
- *
79
- * @return void
80
- */
81
- public function display_meta_box( $object, $box ) {
82
- // register the calendar feeds page.
83
- $calendar_feeds = $this->_registry->get( 'controller.calendar-feeds' );
84
- $feeds = array();
85
 
86
- array_push( $feeds, $this->_registry->get( 'calendar-feed.import' ) );
87
- // Check for user subscription - Discover events
88
- if ($this->_api_registration->has_subscription_active( 'discover-events' ) ) {
89
- array_push( $feeds, $this->_registry->get( 'calendar-feed.suggested' ) );
90
- }
91
 
92
- // Add ICS
93
- array_push( $feeds, $this->_registry->get( 'calendar-feed.ics' ) );
94
 
95
- $feeds = apply_filters( 'ai1ec_calendar_feeds', $feeds );
96
- foreach ( $feeds as $feed ) {
97
- $calendar_feeds->add_plugin( $feed );
98
- }
99
- $calendar_feeds->handle_feeds_page_post();
100
- $loader = $this->_registry->get( 'theme.loader' );
101
- $file = $loader->get_file(
102
- 'box_feeds.php',
103
- array( 'calendar_feeds' => $calendar_feeds ),
104
- true
105
- );
106
- $file->render();
107
- }
108
 
109
- public function handle_post() {
110
- }
111
 
112
  }
11
  */
12
  class Ai1ec_View_Calendar_Feeds extends Ai1ec_View_Admin_Abstract {
13
 
14
+ /**
15
+ * Adds page to the menu.
16
+ *
17
+ * @wp_hook admin_menu
18
+ *
19
+ * @return void
20
+ */
21
+ public function add_page() {
22
+ // =======================
23
+ // = Calendar Feeds Page =
24
+ // =======================
25
+ $calendar_feeds = add_submenu_page(
26
+ AI1EC_ADMIN_BASE_URL,
27
+ Ai1ec_I18n::__( 'Import Feeds' ),
28
+ Ai1ec_I18n::__( 'Import Feeds' ),
29
+ 'manage_ai1ec_feeds',
30
+ AI1EC_PLUGIN_NAME . '-feeds',
31
+ array( $this, 'display_page' )
32
+ );
33
+ $this->_registry->get( 'model.settings' )
34
+ ->set( 'feeds_page', $calendar_feeds );
35
+ }
36
 
37
+ /**
38
+ * Adds metabox to the page.
39
+ *
40
+ * @wp_hook admin_init
41
+ *
42
+ * @return void
43
+ */
44
+ public function add_meta_box() {
45
+ // Add the 'ICS Import Settings' meta box.
46
+ add_meta_box(
47
+ 'ai1ec-feeds',
48
+ Ai1ec_I18n::_x( 'Feed Subscriptions', 'meta box' ),
49
+ array( $this, 'display_meta_box' ),
50
+ $this->_registry->get( 'model.settings' )->get( 'feeds_page' ),
51
+ 'left',
52
+ 'default'
53
+ );
54
+ }
55
 
56
+ /**
57
+ * Display this plugin's feeds page in the admin.
58
+ *
59
+ * @return void
60
+ */
61
+ public function display_page() {
62
+ $settings = $this->_registry->get( 'model.settings' );
63
+ $loader = $this->_registry->get( 'theme.loader' );
64
+ $args = array(
65
+ 'title' => __(
66
+ 'All-in-One Event Calendar: Import Feeds',
67
+ AI1EC_PLUGIN_NAME
68
+ ),
69
+ 'settings_page' => $settings->get( 'feeds_page' ),
70
+ 'calendar_settings' => false,
71
+ );
72
+ $file = $loader->get_file( 'feeds_settings.php', $args, true );
73
+ $file->render();
74
+ }
75
 
76
+ /**
77
+ * Renders the contents of the Calendar Feeds meta box.
78
+ *
79
+ * @return void
80
+ */
81
+ public function display_meta_box( $object, $box ) {
82
+ // register the calendar feeds page.
83
+ $calendar_feeds = $this->_registry->get( 'controller.calendar-feeds' );
84
+ $feeds = array();
85
 
86
+ array_push( $feeds, $this->_registry->get( 'calendar-feed.import' ) );
87
+ // Check for user subscription - Discover events
88
+ if ($this->_api_registration->has_subscription_active( 'discover-events' ) ) {
89
+ array_push( $feeds, $this->_registry->get( 'calendar-feed.suggested' ) );
90
+ }
91
 
92
+ // Add ICS
93
+ array_push( $feeds, $this->_registry->get( 'calendar-feed.ics' ) );
94
 
95
+ $feeds = apply_filters( 'ai1ec_calendar_feeds', $feeds );
96
+ foreach ( $feeds as $feed ) {
97
+ $calendar_feeds->add_plugin( $feed );
98
+ }
99
+ $calendar_feeds->handle_feeds_page_post();
100
+ $loader = $this->_registry->get( 'theme.loader' );
101
+ $file = $loader->get_file(
102
+ 'box_feeds.php',
103
+ array( 'calendar_feeds' => $calendar_feeds ),
104
+ true
105
+ );
106
+ $file->render();
107
+ }
108
 
109
+ public function handle_post() {
110
+ }
111
 
112
  }
app/view/admin/event-category.php CHANGED
@@ -11,230 +11,230 @@
11
  */
12
  class Ai1ec_View_Admin_EventCategory extends Ai1ec_Base {
13
 
14
- /**
15
- * Inserts Color element at index 2 of columns array
16
- *
17
- * @param array $columns Array with event_category columns
18
- *
19
- * @return array Array with event_category columns where Color is inserted
20
- * at index 2
21
- */
22
- public function manage_event_categories_columns( $columns ) {
23
- wp_enqueue_media();
24
- $this->_registry->get( 'css.admin' )
25
- ->process_enqueue( array(
26
- array( 'style', 'bootstrap.min.css' )
27
- ) );
28
- return array_splice( $columns, 0, 3 ) + // get only first element
29
- // insert at index 2
30
- array( 'cat_color' => __( 'Color', AI1EC_PLUGIN_NAME ) ) +
31
- // insert at index 3
32
- array( 'cat_image' => __( 'Image', AI1EC_PLUGIN_NAME ) ) +
33
- // insert rest of elements at the back
34
- array_splice( $columns, 0 );
35
- }
36
-
37
- /**
38
- * Returns the color or image of the event category.
39
- *
40
- * That will be displayed on event category lists page in the backend.
41
- *
42
- * @param $not_set
43
- * @param $column_name
44
- * @param $term_id
45
- * @internal param array $columns Array with event_category columns
46
- *
47
- * @return array Array with event_category columns where Color is inserted
48
- * at index 2
49
- */
50
- public function manage_events_categories_custom_column(
51
- $not_set,
52
- $column_name,
53
- $term_id
54
- ) {
55
- switch ( $column_name ) {
56
- case 'cat_color':
57
- return $this->_registry->get( 'view.event.taxonomy' )
58
- ->get_category_color_square( $term_id );
59
- case 'cat_image':
60
- return $this->_registry->get( 'view.event.taxonomy' )
61
- ->get_category_image_square( $term_id );
62
- }
63
- }
64
-
65
- /**
66
- * Hook to process event categories creation
67
- *
68
- * @param $term_id
69
- *
70
- * @return void Method does not return.
71
- */
72
- public function created_events_categories( $term_id ) {
73
- $this->edited_events_categories( $term_id );
74
- }
75
-
76
- /**
77
- * A callback method, triggered when `event_categories' are being edited.
78
- *
79
- * @param int $term_id ID of term (category) being edited.
80
- *
81
- * @return void Method does not return.
82
- */
83
- public function edited_events_categories( $term_id ) {
84
- if ( isset( $_POST['_inline_edit'] ) ) {
85
- return;
86
- }
87
-
88
- $tag_color_value = '';
89
- if ( ! empty( $_POST['tag-color-value'] ) ) {
90
- $tag_color_value = (string)$_POST['tag-color-value'];
91
- }
92
- $tag_image_value = '';
93
- if ( ! empty( $_POST['ai1ec_category_image_url'] ) ) {
94
- $tag_image_value = (string)$_POST['ai1ec_category_image_url'];
95
- }
96
- if ( isset( $_POST['ai1ec_category_image_url_remove'] ) ) {
97
- $tag_image_value = null;
98
- }
99
-
100
- $db = $this->_registry->get( 'dbi.dbi' );
101
- $table_name = $db->get_table_name( 'ai1ec_event_category_meta' );
102
- $term = $db->get_row( $db->prepare(
103
- 'SELECT term_id FROM ' . $table_name .
104
- ' WHERE term_id = %d',
105
- $term_id
106
- ) );
107
-
108
- if ( null === $term ) { // term does not exist, create it
109
- $db->insert(
110
- $table_name,
111
- array(
112
- 'term_id' => $term_id,
113
- 'term_color' => $tag_color_value,
114
- 'term_image' => $tag_image_value,
115
- ),
116
- array(
117
- '%d',
118
- '%s',
119
- '%s',
120
- )
121
- );
122
- } else { // term exist, update it
123
- $db->update(
124
- $table_name,
125
- array(
126
- 'term_color' => $tag_color_value,
127
- 'term_image' => $tag_image_value
128
- ),
129
- array( 'term_id' => $term_id ),
130
- array( '%s', '%s' ),
131
- array( '%d' )
132
- );
133
- }
134
- }
135
-
136
- public function show_color( $term = null ) {
137
-
138
- $taxonomy = $this->_registry->get( 'model.taxonomy' );
139
- $color = '';
140
- if ( null !== $term ) {
141
- $color = $taxonomy->get_category_color( $term->term_id );
142
- }
143
-
144
- $style = '';
145
- $clr = '';
146
-
147
- if ( $color ) {
148
- $style = 'style="background-color: ' . $color . '"';
149
- $clr = $color;
150
- }
151
-
152
- $args = array(
153
- 'style' => $style,
154
- 'color' => $clr,
155
- 'label' => Ai1ec_I18n::__( 'Category Color' ),
156
- 'description' => Ai1ec_I18n::__(
157
- 'Events in this category will be identified by this color'
158
- ),
159
- 'edit' => true,
160
- );
161
-
162
- $loader = $this->_registry->get( 'theme.loader' );
163
- $loader->get_file(
164
- 'setting/categories-color-picker.twig',
165
- $args,
166
- true
167
- )->render();
168
- }
169
-
170
- /**
171
- * Edit category form
172
- *
173
- * @param $term
174
- *
175
- * @return void
176
- */
177
- public function events_categories_edit_form_fields( $term ) {
178
- $this->show_color( $term );
179
- $taxonomy = $this->_registry->get( 'model.taxonomy' );
180
- $loader = $this->_registry->get( 'theme.loader' );
181
- $image = $taxonomy->get_category_image( $term->term_id );
182
-
183
- $style = 'style="display:none"';
184
-
185
- if ( null !== $image ) {
186
- $style = '';
187
- }
188
-
189
- // Category image
190
- $args = array(
191
- 'image_src' => $image,
192
- 'image_style' => $style,
193
- 'section_name' => __( 'Category Image', AI1EC_PLUGIN_NAME ),
194
- 'label' => __( 'Add Image', AI1EC_PLUGIN_NAME ),
195
- 'remove_label' => __( 'Remove Image', AI1EC_PLUGIN_NAME ),
196
- 'description' => __(
197
- 'Assign an optional image to the category. Recommended size: square, minimum 400&times;400 pixels.',
198
- AI1EC_PLUGIN_NAME
199
- ),
200
- 'edit' => true,
201
- );
202
-
203
- $loader->get_file(
204
- 'setting/categories-image.twig',
205
- $args,
206
- true
207
- )->render();
208
- }
209
-
210
- /**
211
- * Add category form
212
- *
213
- * @return void
214
- */
215
- public function events_categories_add_form_fields() {
216
-
217
- $this->show_color();
218
-
219
- $loader = $this->_registry->get( 'theme.loader' );
220
-
221
- // Category image
222
- $args = array(
223
- 'image_src' => '',
224
- 'image_style' => 'style="display:none"',
225
- 'section_name' => __( 'Category Image', AI1EC_PLUGIN_NAME ),
226
- 'label' => __( 'Add Image', AI1EC_PLUGIN_NAME),
227
- 'description' => __( 'Assign an optional image to the category. Recommended size: square, minimum 400&times;400 pixels.', AI1EC_PLUGIN_NAME ),
228
- 'edit' => false,
229
- );
230
-
231
- $file = $loader->get_file(
232
- 'setting/categories-image.twig',
233
- $args,
234
- true
235
- );
236
-
237
- $file->render();
238
- }
239
 
240
  }
11
  */
12
  class Ai1ec_View_Admin_EventCategory extends Ai1ec_Base {
13
 
14
+ /**
15
+ * Inserts Color element at index 2 of columns array
16
+ *
17
+ * @param array $columns Array with event_category columns
18
+ *
19
+ * @return array Array with event_category columns where Color is inserted
20
+ * at index 2
21
+ */
22
+ public function manage_event_categories_columns( $columns ) {
23
+ wp_enqueue_media();
24
+ $this->_registry->get( 'css.admin' )
25
+ ->process_enqueue( array(
26
+ array( 'style', 'bootstrap.min.css' )
27
+ ) );
28
+ return array_splice( $columns, 0, 3 ) + // get only first element
29
+ // insert at index 2
30
+ array( 'cat_color' => __( 'Color', AI1EC_PLUGIN_NAME ) ) +
31
+ // insert at index 3
32
+ array( 'cat_image' => __( 'Image', AI1EC_PLUGIN_NAME ) ) +
33
+ // insert rest of elements at the back
34
+ array_splice( $columns, 0 );
35
+ }
36
+
37
+ /**
38
+ * Returns the color or image of the event category.
39
+ *
40
+ * That will be displayed on event category lists page in the backend.
41
+ *
42
+ * @param $not_set
43
+ * @param $column_name
44
+ * @param $term_id
45
+ * @internal param array $columns Array with event_category columns
46
+ *
47
+ * @return array Array with event_category columns where Color is inserted
48
+ * at index 2
49
+ */
50
+ public function manage_events_categories_custom_column(
51
+ $not_set,
52
+ $column_name,
53
+ $term_id
54
+ ) {
55
+ switch ( $column_name ) {
56
+ case 'cat_color':
57
+ return $this->_registry->get( 'view.event.taxonomy' )
58
+ ->get_category_color_square( $term_id );
59
+ case 'cat_image':
60
+ return $this->_registry->get( 'view.event.taxonomy' )
61
+ ->get_category_image_square( $term_id );
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Hook to process event categories creation
67
+ *
68
+ * @param $term_id
69
+ *
70
+ * @return void Method does not return.
71
+ */
72
+ public function created_events_categories( $term_id ) {
73
+ $this->edited_events_categories( $term_id );
74
+ }
75
+
76
+ /**
77
+ * A callback method, triggered when `event_categories' are being edited.
78
+ *
79
+ * @param int $term_id ID of term (category) being edited.
80
+ *
81
+ * @return void Method does not return.
82
+ */
83
+ public function edited_events_categories( $term_id ) {
84
+ if ( isset( $_POST['_inline_edit'] ) ) {
85
+ return;
86
+ }
87
+
88
+ $tag_color_value = '';
89
+ if ( ! empty( $_POST['tag-color-value'] ) ) {
90
+ $tag_color_value = (string)$_POST['tag-color-value'];
91
+ }
92
+ $tag_image_value = '';
93
+ if ( ! empty( $_POST['ai1ec_category_image_url'] ) ) {
94
+ $tag_image_value = (string)$_POST['ai1ec_category_image_url'];
95
+ }
96
+ if ( isset( $_POST['ai1ec_category_image_url_remove'] ) ) {
97
+ $tag_image_value = null;
98
+ }
99
+
100
+ $db = $this->_registry->get( 'dbi.dbi' );
101
+ $table_name = $db->get_table_name( 'ai1ec_event_category_meta' );
102
+ $term = $db->get_row( $db->prepare(
103
+ 'SELECT term_id FROM ' . $table_name .
104
+ ' WHERE term_id = %d',
105
+ $term_id
106
+ ) );
107
+
108
+ if ( null === $term ) { // term does not exist, create it
109
+ $db->insert(
110
+ $table_name,
111
+ array(
112
+ 'term_id' => $term_id,
113
+ 'term_color' => $tag_color_value,
114
+ 'term_image' => $tag_image_value,
115
+ ),
116
+ array(
117
+ '%d',
118
+ '%s',
119
+ '%s',
120
+ )
121
+ );
122
+ } else { // term exist, update it
123
+ $db->update(
124
+ $table_name,
125
+ array(
126
+ 'term_color' => $tag_color_value,
127
+ 'term_image' => $tag_image_value
128
+ ),
129
+ array( 'term_id' => $term_id ),
130
+ array( '%s', '%s' ),
131
+ array( '%d' )
132
+ );
133
+ }
134
+ }
135
+
136
+ public function show_color( $term = null ) {
137
+
138
+ $taxonomy = $this->_registry->get( 'model.taxonomy' );
139
+ $color = '';
140
+ if ( null !== $term ) {
141
+ $color = $taxonomy->get_category_color( $term->term_id );
142
+ }
143
+
144
+ $style = '';
145
+ $clr = '';
146
+
147
+ if ( $color ) {
148
+ $style = 'style="background-color: ' . $color . '"';
149
+ $clr = $color;
150
+ }
151
+
152
+ $args = array(
153
+ 'style' => $style,
154
+ 'color' => $clr,
155
+ 'label' => Ai1ec_I18n::__( 'Category Color' ),
156
+ 'description' => Ai1ec_I18n::__(
157
+ 'Events in this category will be identified by this color'
158
+ ),
159
+ 'edit' => true,
160
+ );
161
+
162
+ $loader = $this->_registry->get( 'theme.loader' );
163
+ $loader->get_file(
164
+ 'setting/categories-color-picker.twig',
165
+ $args,
166
+ true
167
+ )->render();
168
+ }
169
+
170
+ /**
171
+ * Edit category form
172
+ *
173
+ * @param $term
174
+ *
175
+ * @return void
176
+ */
177
+ public function events_categories_edit_form_fields( $term ) {
178
+ $this->show_color( $term );
179
+ $taxonomy = $this->_registry->get( 'model.taxonomy' );
180
+ $loader = $this->_registry->get( 'theme.loader' );
181
+ $image = $taxonomy->get_category_image( $term->term_id );
182
+
183
+ $style = 'style="display:none"';
184
+
185
+ if ( null !== $image ) {
186
+ $style = '';
187
+ }
188
+
189
+ // Category image
190
+ $args = array(
191
+ 'image_src' => $image,
192
+ 'image_style' => $style,
193
+ 'section_name' => __( 'Category Image', AI1EC_PLUGIN_NAME ),
194
+ 'label' => __( 'Add Image', AI1EC_PLUGIN_NAME ),
195
+ 'remove_label' => __( 'Remove Image', AI1EC_PLUGIN_NAME ),
196
+ 'description' => __(
197
+ 'Assign an optional image to the category. Recommended size: square, minimum 400&times;400 pixels.',
198
+ AI1EC_PLUGIN_NAME
199
+ ),
200
+ 'edit' => true,
201
+ );
202
+
203
+ $loader->get_file(
204
+ 'setting/categories-image.twig',
205
+ $args,
206
+ true
207
+ )->render();
208
+ }
209
+
210
+ /**
211
+ * Add category form
212
+ *
213
+ * @return void
214
+ */
215
+ public function events_categories_add_form_fields() {
216
+
217
+ $this->show_color();
218
+
219
+ $loader = $this->_registry->get( 'theme.loader' );
220
+
221
+ // Category image
222
+ $args = array(
223
+ 'image_src' => '',
224
+ 'image_style' => 'style="display:none"',
225
+ 'section_name' => __( 'Category Image', AI1EC_PLUGIN_NAME ),
226
+ 'label' => __( 'Add Image', AI1EC_PLUGIN_NAME),
227
+ 'description' => __( 'Assign an optional image to the category. Recommended size: square, minimum 400&times;400 pixels.', AI1EC_PLUGIN_NAME ),
228
+ 'edit' => false,
229
+ );
230
+
231
+ $file = $loader->get_file(
232
+ 'setting/categories-image.twig',
233
+ $args,
234
+ true
235
+ );
236
+
237
+ $file->render();
238
+ }
239
 
240
  }
app/view/admin/get-repeat-box.php CHANGED
@@ -10,661 +10,661 @@
10
  * @subpackage AI1EC.View
11
  */
12
  class Ai1ec_View_Admin_Get_repeat_Box extends Ai1ec_Base {
13
- /**
14
- * get_repeat_box function
15
- *
16
- * @return string
17
- **/
18
- public function get_repeat_box() {
19
- $time_system = $this->_registry->get( 'date.system' );
20
- $loader = $this->_registry->get( 'theme.loader' );
21
- $repeat = (int) $_REQUEST["repeat"];
22
- $repeat = $repeat == 1 ? 1 : 0;
23
- $post_id = (int) $_REQUEST["post_id"];
24
- $count = 100;
25
- $end = 0;
26
- $until = $time_system->current_time( true );
27
-
28
- // try getting the event
29
- try {
30
- $event = $this->_registry->get( 'model.event', $post_id );
31
- $rule = '';
32
-
33
- if ( $repeat ) {
34
- $rule = $event->get( 'recurrence_rules' )
35
- ? $event->get( 'recurrence_rules' )
36
- : '';
37
- } else {
38
- $rule = $event->get( 'exception_rules' ) ?
39
- $event->get( 'exception_rules' )
40
- : '';
41
- }
42
-
43
- $rule = $this->_registry->get( 'recurrence.rule' )->filter_rule( $rule );
44
-
45
- $rc = new SG_iCal_Recurrence(
46
- new SG_iCal_Line( 'RRULE:' . $rule )
47
- );
48
-
49
- if ( $until = $rc->getUntil() ) {
50
- $until = ( is_numeric( $until ) )
51
- ? $until
52
- : strtotime( $until );
53
- $end = 2;
54
- } elseif ( $count = $rc->getCount() ) {
55
- $count = ( is_numeric( $count ) ) ? $count : 100;
56
- $end = 1;
57
- }
58
- } catch( Ai1ec_Event_Not_Found_Exception $e ) {
59
- $rule = '';
60
- $rc = new SG_iCal_Recurrence(
61
- new SG_iCal_Line( 'RRULE:' )
62
- );
63
- }
64
-
65
- $args = array(
66
- 'row_daily' => $this->row_daily(
67
- false,
68
- $rc->getInterval() ? $rc->getInterval() : 1
69
- ),
70
- 'row_weekly' => $this->row_weekly(
71
- false,
72
- $rc->getInterval() ? $rc->getInterval() : 1,
73
- is_array( $rc->getByDay() ) ? $rc->getByDay() : array()
74
- ),
75
- 'row_monthly' => $this->row_monthly(
76
- false,
77
- $rc->getInterval() ? $rc->getInterval() : 1,
78
- ! $this->_is_monthday_empty( $rc ),
79
- $rc->getByMonthDay() ? $rc->getByMonthDay() : array(),
80
- $rc->getByDay() ? $rc->getByDay() : array()
81
- ),
82
- 'row_yearly' => $this->row_yearly(
83
- false,
84
- $rc->getInterval() ? $rc->getInterval() : 1,
85
- is_array( $rc->getByMonth() ) ? $rc->getByMonth() : array()
86
- ),
87
- 'row_custom' => $this->row_custom(
88
- false,
89
- $this->get_date_array_from_rule( $rule )
90
- ),
91
- 'count' => $this->create_count_input(
92
- 'ai1ec_count',
93
- $count
94
- ) . Ai1ec_I18n::__( 'times' ),
95
- 'end' => $this->create_end_dropdown( $end ),
96
- 'until' => $until,
97
- 'repeat' => $repeat,
98
- 'ending_type' => $end,
99
- 'selected_tab' => $rc->getFreq()
100
- ? strtolower( $rc->getFreq() )
101
- : 'custom',
102
- );
103
- $output = array(
104
- 'error' => false,
105
- 'message' => $loader->get_file(
106
- 'box_repeat.php',
107
- $args,
108
- true
109
- )->get_content(),
110
- 'repeat' => $repeat,
111
- );
112
- $json_strategy = $this->_registry->get( 'http.response.render.strategy.json' );
113
- $json_strategy->render( array( 'data' => $output ) );
114
- }
115
-
116
- /**
117
- * get_weekday_by_id function
118
- *
119
- * Returns weekday name in English
120
- *
121
- * @param int $day_id Day ID
122
- *
123
- * @return string
124
- **/
125
- public function get_weekday_by_id( $day_id, $by_value = false ) {
126
- // do not translate this !!!
127
- $week_days = array(
128
- 0 => 'SU',
129
- 1 => 'MO',
130
- 2 => 'TU',
131
- 3 => 'WE',
132
- 4 => 'TH',
133
- 5 => 'FR',
134
- 6 => 'SA',
135
- );
136
-
137
- if ( $by_value ) {
138
- while ( $_name = current( $week_days ) ) {
139
- if ( $_name == $day_id ) {
140
- return key( $week_days );
141
- }
142
- next( $week_days );
143
- }
144
- return false;
145
- }
146
- return $week_days[$day_id];
147
- }
148
-
149
- /**
150
- * convert_rrule_to_text method
151
- *
152
- * Convert a `recurrence rule' to text to display it on screen
153
- *
154
- * @return void
155
- **/
156
- public function convert_rrule_to_text() {
157
- $error = false;
158
- $message = '';
159
- // check to see if RRULE is set
160
- if ( isset( $_REQUEST['rrule'] ) ) {
161
- // check to see if rrule is empty
162
- if ( empty( $_REQUEST['rrule'] ) ) {
163
- $error = true;
164
- $message = Ai1ec_I18n::__(
165
- 'Recurrence rule cannot be empty.'
166
- );
167
- } else {
168
- //list( $rule, $value ) = explode( '=', $_REQUEST['rrule'], 2 );
169
- //if ( in_array( array(), $rule ) ) {
170
- // $message = $this->_registry->get( 'recurrence.date' );
171
- //
172
- //} else {
173
- $rrule = $this->_registry->get( 'recurrence.rule' );
174
- // convert rrule to text
175
- $message = ucfirst(
176
- $rrule->rrule_to_text( $_REQUEST['rrule'] )
177
- );
178
- //}
179
- }
180
- } else {
181
- $error = true;
182
- $message = Ai1ec_I18n::__(
183
- 'Recurrence rule was not provided.'
184
- );
185
- }
186
- $output = array(
187
- 'error' => $error,
188
- 'message' => get_magic_quotes_gpc()
189
- ? stripslashes( $message )
190
- : $message,
191
- );
192
-
193
- $json_strategy = $this->_registry->get( 'http.response.render.strategy.json' );
194
- $json_strategy->render( array( 'data' => $output ) );
195
- }
196
-
197
- /**
198
- * create_end_dropdown function
199
- *
200
- * Outputs the dropdown list for the recurrence end option.
201
- *
202
- * @param int $selected The index of the selected option, if any
203
- * @return void
204
- **/
205
- protected function create_end_dropdown( $selected = NULL ) {
206
- ob_start();
207
-
208
- $options = array(
209
- 0 => Ai1ec_I18n::__( 'Never' ),
210
- 1 => Ai1ec_I18n::__( 'After' ),
211
- 2 => Ai1ec_I18n::__( 'On date' ),
212
- );
213
-
214
- ?>
215
- <select name="ai1ec_end" id="ai1ec_end">
216
- <?php foreach( $options as $key => $val ): ?>
217
- <option value="<?php echo $key ?>"
218
- <?php if( $key === $selected ) echo 'selected="selected"' ?>>
219
- <?php echo $val ?>
220
- </option>
221
- <?php endforeach ?>
222
- </select>
223
- <?php
224
-
225
- $output = ob_get_contents();
226
- ob_end_clean();
227
-
228
- return $output;
229
- }
230
-
231
- /**
232
- * row_daily function
233
- *
234
- * Returns daily selector
235
- *
236
- * @return void
237
- **/
238
- protected function row_daily( $visible = false, $selected = 1 ) {
239
- $loader = $this->_registry->get( 'theme.loader' );
240
-
241
- $args = array(
242
- 'visible' => $visible,
243
- 'count' => $this->create_count_input(
244
- 'ai1ec_daily_count',
245
- $selected,
246
- 365
247
- ) . Ai1ec_I18n::__( 'day(s)' ),
248
- );
249
- return $loader->get_file( 'row_daily.php', $args, true )
250
- ->get_content();
251
- }
252
-
253
- /**
254
- * row_custom function
255
- *
256
- * Returns custom dates selector
257
- *
258
- * @return void
259
- **/
260
- protected function row_custom( $visible = false, $dates = array() ) {
261
- $loader = $this->_registry->get( 'theme.loader' );
262
-
263
- $args = array(
264
- 'visible' => $visible,
265
- 'selected_dates' => implode( ',', $dates )
266
- );
267
- return $loader->get_file( 'row_custom.php', $args, true )
268
- ->get_content();
269
- }
270
-
271
- /**
272
- * Generates and returns "End after X times" input
273
- *
274
- * @param Integer|NULL $count Initial value of range input
275
- *
276
- * @return String Repeat dropdown
277
- */
278
- protected function create_count_input( $name, $count = 100, $max = 365 ) {
279
- ob_start();
280
-
281
- if ( ! $count ) {
282
- $count = 100;
283
- }
284
- ?>
285
- <input type="range" name="<?php echo $name ?>" id="<?php echo $name ?>"
286
- min="1" max="<?php echo $max ?>"
287
- <?php if ( $count ) echo 'value="' . $count . '"' ?> />
288
- <?php
289
- return ob_get_clean();
290
- }
291
-
292
- /**
293
- * row_weekly function
294
- *
295
- * Returns weekly selector
296
- *
297
- * @return void
298
- **/
299
- protected function row_weekly(
300
- $visible = false,
301
- $count = 1,
302
- array $selected = array()
303
- ) {
304
- global $wp_locale;
305
- $start_of_week = $this->_registry->get( 'model.option' )
306
- ->get( 'start_of_week', 1 );
307
- $loader = $this->_registry->get( 'theme.loader' );
308
-
309
- $options = array();
310
- // get days from start_of_week until the last day
311
- for ( $i = $start_of_week; $i <= 6; ++$i ) {
312
- $options[$this->get_weekday_by_id( $i )] = $wp_locale
313
- ->weekday_initial[$wp_locale->weekday[$i]];
314
- }
315
-
316
- // get days from 0 until start_of_week
317
- if ( $start_of_week > 0 ) {
318
- for ( $i = 0; $i < $start_of_week; $i++ ) {
319
- $options[$this->get_weekday_by_id( $i )] = $wp_locale
320
- ->weekday_initial[$wp_locale->weekday[$i]];
321
- }
322
- }
323
-
324
- $args = array(
325
- 'visible' => $visible,
326
- 'count' => $this->create_count_input(
327
- 'ai1ec_weekly_count',
328
- $count,
329
- 52
330
- ) . Ai1ec_I18n::__( 'week(s)' ),
331
- 'week_days' => $this->create_list_element(
332
- 'ai1ec_weekly_date_select',
333
- $options,
334
- $selected
335
- )
336
- );
337
- return $loader->get_file( 'row_weekly.php', $args, true )
338
- ->get_content();
339
- }
340
-
341
- /**
342
- * Creates a grid of weekday, day, or month selection buttons.
343
- *
344
- * @return string
345
- */
346
- protected function create_list_element(
347
- $name,
348
- array $options = array(),
349
- array $selected = array()
350
- ) {
351
- ob_start();
352
- ?>
353
  <div class="ai1ec-btn-group-grid" id="<?php echo $name; ?>">
354
- <?php foreach ( $options as $key => $val ) : ?>
355
- <div class="ai1ec-pull-left">
356
- <a class="ai1ec-btn ai1ec-btn-default ai1ec-btn-block
357
- <?php echo in_array( $key, $selected ) ? 'ai1ec-active' : ''; ?>">
358
- <?php echo $val; ?>
359
- </a>
360
- <input type="hidden" name="<?php echo $name . '_' . $key; ?>"
361
- value="<?php echo $key; ?>">
362
- </div class="ai1ec-pull-left">
363
- <?php endforeach; ?>
364
  </div>
365
  <input type="hidden" name="<?php echo $name; ?>"
366
- value="<?php echo implode( ',', $selected ) ?>">
367
  <?php
368
- return ob_get_clean();
369
- }
370
-
371
- /**
372
- * row_monthly function
373
- *
374
- * Returns monthly selector
375
- *
376
- * @return void
377
- **/
378
- protected function row_monthly(
379
- $visible = false,
380
- $count = 1,
381
- $bymonthday = true,
382
- $month = array(),
383
- $day = array()
384
- ) {
385
- global $wp_locale;
386
- $start_of_week = $this->_registry->get( 'model.option' )
387
- ->get( 'start_of_week', 1 );
388
- $loader = $this->_registry->get( 'theme.loader' );
389
-
390
- $options_wd = array();
391
- // get days from start_of_week until the last day
392
- for ( $i = $start_of_week; $i <= 6; ++$i ) {
393
- $options_wd[$this->get_weekday_by_id( $i )] = $wp_locale
394
- ->weekday[$i];
395
- }
396
-
397
- // get days from 0 until start_of_week
398
- if ( $start_of_week > 0 ) {
399
- for ( $i = 0; $i < $start_of_week; $i++ ) {
400
- $options_wd[$this->get_weekday_by_id( $i )] = $wp_locale
401
- ->weekday[$i];
402
- }
403
- }
404
-
405
- // get options like 1st/2nd/3rd for "day number"
406
- $options_dn = array( 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5 );
407
- foreach ( $options_dn as $_dn ) {
408
- $options_dn[$_dn] = $this->_registry->get(
409
- 'date.time',
410
- strtotime( $_dn . '-01-1998 12:00:00' )
411
- )->format_i18n( 'jS' );
412
- }
413
- $options_dn['-1'] = Ai1ec_I18n::__( 'last' );
414
-
415
- $byday_checked = $bymonthday ? '' : 'checked';
416
- $byday_expanded = $bymonthday ? 'ai1ec-collapse' : 'ai1ec-in';
417
- $bymonthday_checked = $bymonthday ? 'checked' : '';
418
- $bymonthday_expanded = $bymonthday ? 'ai1ec-in' : 'ai1ec-collapse';
419
-
420
- $args = array(
421
- 'visible' => $visible,
422
- 'count' => $this->create_count_input(
423
- 'ai1ec_monthly_count',
424
- $count,
425
- 12
426
- ) . Ai1ec_I18n::__( 'month(s)' ),
427
- 'month' => $this->create_monthly_date_select(
428
- $month
429
- ),
430
- 'day_nums' => $this->create_select_element(
431
- 'ai1ec_monthly_byday_num',
432
- $options_dn,
433
- $this->_get_day_number_from_byday( $day )
434
- ),
435
- 'week_days' => $this->create_select_element(
436
- 'ai1ec_monthly_byday_weekday',
437
- $options_wd,
438
- $this->_get_day_shortname_from_byday( $day )
439
- ),
440
- 'bymonthday_checked' => $bymonthday_checked,
441
- 'byday_checked' => $byday_checked,
442
- 'bymonthday_expanded' => $bymonthday_expanded,
443
- 'byday_expanded' => $byday_expanded,
444
- );
445
- return $loader->get_file( 'row_monthly.php', $args, true )
446
- ->get_content();
447
- }
448
-
449
- /**
450
- * Creates selector for dates in monthly repeat tab.
451
- *
452
- * @return void
453
- */
454
- protected function create_monthly_date_select( $selected = array() ) {
455
- $options = array();
456
- for ( $i = 1; $i <= 31; ++$i ) {
457
- $options[$i] = $i;
458
- }
459
- return $this->create_list_element(
460
- 'ai1ec_montly_date_select',
461
- $options,
462
- $selected
463
- );
464
- }
465
-
466
- /**
467
- * create_on_the_select function
468
- *
469
- *
470
- *
471
- * @return string
472
- **/
473
- protected function create_on_the_select(
474
- $f_selected = false,
475
- $s_selected = false
476
- ) {
477
- $ret = '';
478
-
479
- $first_options = array(
480
- '0' => Ai1ec_I18n::__( 'first' ),
481
- '1' => Ai1ec_I18n::__( 'second' ),
482
- '2' => Ai1ec_I18n::__( 'third' ),
483
- '3' => Ai1ec_I18n::__( 'fourth' ),
484
- '4' => '------',
485
- '5' => Ai1ec_I18n::__( 'last' )
486
- );
487
- $ret = $this->create_select_element(
488
- 'ai1ec_monthly_each_select',
489
- $first_options,
490
- $f_selected,
491
- array( 4 )
492
- );
493
-
494
- $second_options = array(
495
- '0' => Ai1ec_I18n::__( 'Sunday' ),
496
- '1' => Ai1ec_I18n::__( 'Monday' ),
497
- '2' => Ai1ec_I18n::__( 'Tuesday' ),
498
- '3' => Ai1ec_I18n::__( 'Wednesday' ),
499
- '4' => Ai1ec_I18n::__( 'Thursday' ),
500
- '5' => Ai1ec_I18n::__( 'Friday' ),
501
- '6' => Ai1ec_I18n::__( 'Saturday' ),
502
- '7' => '--------',
503
- '8' => Ai1ec_I18n::__( 'day' ),
504
- '9' => Ai1ec_I18n::__( 'weekday' ),
505
- '10' => Ai1ec_I18n::__( 'weekend day' )
506
- );
507
-
508
- return $ret . $this->create_select_element(
509
- 'ai1ec_monthly_on_the_select',
510
- $second_options,
511
- $s_selected,
512
- array( 7 )
513
- );
514
- }
515
-
516
- /**
517
- * create_select_element function
518
- *
519
- * Render HTML <select> element
520
- *
521
- * @param string $name Name of element to be rendered
522
- * @param array $options Select <option> values as key=>value pairs
523
- * @param string $selected Key to be marked as selected [optional=false]
524
- * @param array $disabled_keys List of options to disable [optional=array]
525
- *
526
- * @return string Rendered <select> HTML element
527
- **/
528
- protected function create_select_element(
529
- $name,
530
- array $options = array(),
531
- $selected = false,
532
- array $disabled_keys = array()
533
- ) {
534
- ob_start();
535
- ?>
536
- <select name="<?php echo $name ?>" id="<?php echo $name ?>">
537
- <?php foreach( $options as $key => $val ): ?>
538
- <option value="<?php echo $key ?>"
539
- <?php echo $key === $selected ? 'selected="selected"' : '' ?>
540
- <?php echo in_array( $key, $disabled_keys ) ? 'disabled' : '' ?>>
541
- <?php echo $val ?>
542
- </option>
543
- <?php endforeach ?>
544
- </select>
545
- <?php
546
- return ob_get_clean();
547
- }
548
-
549
- /**
550
- * row_yearly function
551
- *
552
- * Returns yearly selector
553
- *
554
- * @return void
555
- **/
556
- protected function row_yearly(
557
- $visible = false,
558
- $count = 1,
559
- $year = array(),
560
- $first = false,
561
- $second = false
562
- ) {
563
- $loader = $this->_registry->get( 'theme.loader' );
564
-
565
- $args = array(
566
- 'visible' => $visible,
567
- 'count' => $this->create_count_input(
568
- 'ai1ec_yearly_count',
569
- $count,
570
- 10
571
- ) . Ai1ec_I18n::__( 'year(s)' ),
572
- 'year' => $this->create_yearly_date_select( $year ),
573
- 'on_the_select' => $this->create_on_the_select(
574
- $first,
575
- $second
576
- ),
577
- );
578
- return $loader->get_file( 'row_yearly.php', $args, true )
579
- ->get_content();
580
- }
581
-
582
- /**
583
- * create_yearly_date_select function
584
- *
585
- *
586
- *
587
- * @return void
588
- **/
589
- protected function create_yearly_date_select( $selected = array() ) {
590
- global $wp_locale;
591
- $options = array();
592
- for ( $i = 1; $i <= 12; ++$i ) {
593
- $options[$i] = $wp_locale->month_abbrev[
594
- $wp_locale->month[sprintf( '%02d', $i )]
595
- ];
596
- }
597
- return $this->create_list_element(
598
- 'ai1ec_yearly_date_select',
599
- $options,
600
- $selected
601
- );
602
- }
603
-
604
- /**
605
- * Converts recurrence rule to array of string of dates.
606
- *
607
- * @param string $rule RUle.
608
- *
609
- * @return array Array of dates or empty array.
610
- * @throws Ai1ec_Bootstrap_Exception
611
- */
612
- protected function get_date_array_from_rule( $rule ) {
613
- if (
614
- 'RDATE' !== substr( $rule, 0, 5 ) &&
615
- 'EXDATE' !== substr( $rule, 0, 6 )
616
- ) {
617
- return array();
618
- }
619
- $line = new SG_iCal_Line( 'RRULE:' . $rule );
620
- $dates = $line->getDataAsArray();
621
- $dates_as_strings = array();
622
- foreach ( $dates as $date ) {
623
- $date = str_replace( array( 'RDATE=', 'EXDATE=' ), '', $date );
624
- $date = $this->_registry->get( 'date.time', $date )->set_preferred_timezone( 'UTC' );
625
- $dates_as_strings[] = $date->format( 'm/d/Y' );
626
- }
627
- return $dates_as_strings;
628
- }
629
-
630
- /**
631
- * Returns whether recurrence rule has non null ByMonthDay.
632
- *
633
- * @param SG_iCal_Recurrence $rc iCal class.
634
- *
635
- * @return bool True or false.
636
- */
637
- protected function _is_monthday_empty( SG_iCal_Recurrence $rc ) {
638
- return false === $rc->getByMonthDay();
639
- }
640
-
641
- /**
642
- * Returns day number from by day array.
643
- *
644
- * @param array $day
645
- *
646
- * @return bool|int Day of false if empty array.
647
- */
648
- protected function _get_day_number_from_byday( array $day ) {
649
- return isset( $day[0] ) ? (int) $day[0] : false;
650
- }
651
-
652
- /**
653
- * Returns string part from "ByDay" recurrence rule.
654
- *
655
- * @param array $day Element to parse.
656
- *
657
- * @return bool|string False if empty or not matched, otherwise short day
658
- * name.
659
- */
660
- protected function _get_day_shortname_from_byday( $day ) {
661
- if ( empty( $day ) ) {
662
- return false;
663
- }
664
- $value = $day[0];
665
- if ( preg_match('/[-]?\d([A-Z]+)/', $value, $matches ) ) {
666
- return $matches[1];
667
- }
668
- return false;
669
- }
670
  }
10
  * @subpackage AI1EC.View
11
  */
12
  class Ai1ec_View_Admin_Get_repeat_Box extends Ai1ec_Base {
13
+ /**
14
+ * get_repeat_box function
15
+ *
16
+ * @return string
17
+ **/
18
+ public function get_repeat_box() {
19
+ $time_system = $this->_registry->get( 'date.system' );
20
+ $loader = $this->_registry->get( 'theme.loader' );
21
+ $repeat = (int) $_REQUEST["repeat"];
22
+ $repeat = $repeat == 1 ? 1 : 0;
23
+ $post_id = (int) $_REQUEST["post_id"];
24
+ $count = 100;
25
+ $end = 0;
26
+ $until = $time_system->current_time( true );
27
+
28
+ // try getting the event
29
+ try {
30
+ $event = $this->_registry->get( 'model.event', $post_id );
31
+ $rule = '';
32
+
33
+ if ( $repeat ) {
34
+ $rule = $event->get( 'recurrence_rules' )
35
+ ? $event->get( 'recurrence_rules' )
36
+ : '';
37
+ } else {
38
+ $rule = $event->get( 'exception_rules' ) ?
39
+ $event->get( 'exception_rules' )
40
+ : '';
41
+ }
42
+
43
+ $rule = $this->_registry->get( 'recurrence.rule' )->filter_rule( $rule );
44
+
45
+ $rc = new SG_iCal_Recurrence(
46
+ new SG_iCal_Line( 'RRULE:' . $rule )
47
+ );
48
+
49
+ if ( $until = $rc->getUntil() ) {
50
+ $until = ( is_numeric( $until ) )
51
+ ? $until
52
+ : strtotime( $until );
53
+ $end = 2;
54
+ } elseif ( $count = $rc->getCount() ) {
55
+ $count = ( is_numeric( $count ) ) ? $count : 100;
56
+ $end = 1;
57
+ }
58
+ } catch( Ai1ec_Event_Not_Found_Exception $e ) {
59
+ $rule = '';
60
+ $rc = new SG_iCal_Recurrence(
61
+ new SG_iCal_Line( 'RRULE:' )
62
+ );
63
+ }
64
+
65
+ $args = array(
66
+ 'row_daily' => $this->row_daily(
67
+ false,
68
+ $rc->getInterval() ? $rc->getInterval() : 1
69
+ ),
70
+ 'row_weekly' => $this->row_weekly(
71
+ false,
72
+ $rc->getInterval() ? $rc->getInterval() : 1,
73
+ is_array( $rc->getByDay() ) ? $rc->getByDay() : array()
74
+ ),
75
+ 'row_monthly' => $this->row_monthly(
76
+ false,
77
+ $rc->getInterval() ? $rc->getInterval() : 1,
78
+ ! $this->_is_monthday_empty( $rc ),
79
+ $rc->getByMonthDay() ? $rc->getByMonthDay() : array(),
80
+ $rc->getByDay() ? $rc->getByDay() : array()
81
+ ),
82
+ 'row_yearly' => $this->row_yearly(
83
+ false,
84
+ $rc->getInterval() ? $rc->getInterval() : 1,
85
+ is_array( $rc->getByMonth() ) ? $rc->getByMonth() : array()
86
+ ),
87
+ 'row_custom' => $this->row_custom(
88
+ false,
89
+ $this->get_date_array_from_rule( $rule )
90
+ ),
91
+ 'count' => $this->create_count_input(
92
+ 'ai1ec_count',
93
+ $count
94
+ ) . Ai1ec_I18n::__( 'times' ),
95
+ 'end' => $this->create_end_dropdown( $end ),
96
+ 'until' => $until,
97
+ 'repeat' => $repeat,
98
+ 'ending_type' => $end,
99
+ 'selected_tab' => $rc->getFreq()
100
+ ? strtolower( $rc->getFreq() )
101
+ : 'custom',
102
+ );
103
+ $output = array(
104
+ 'error' => false,
105
+ 'message' => $loader->get_file(
106
+ 'box_repeat.php',
107
+ $args,
108
+ true
109
+ )->get_content(),
110
+ 'repeat' => $repeat,
111
+ );
112
+ $json_strategy = $this->_registry->get( 'http.response.render.strategy.json' );
113
+ $json_strategy->render( array( 'data' => $output ) );
114
+ }
115
+
116
+ /**
117
+ * get_weekday_by_id function
118
+ *
119
+ * Returns weekday name in English
120
+ *
121
+ * @param int $day_id Day ID
122
+ *
123
+ * @return string
124
+ **/
125
+ public function get_weekday_by_id( $day_id, $by_value = false ) {
126
+ // do not translate this !!!
127
+ $week_days = array(
128
+ 0 => 'SU',
129
+ 1 => 'MO',
130
+ 2 => 'TU',
131
+ 3 => 'WE',
132
+ 4 => 'TH',
133
+ 5 => 'FR',
134
+ 6 => 'SA',
135
+ );
136
+
137
+ if ( $by_value ) {
138
+ while ( $_name = current( $week_days ) ) {
139
+ if ( $_name == $day_id ) {
140
+ return key( $week_days );
141
+ }
142
+ next( $week_days );
143
+ }
144
+ return false;
145
+ }
146
+ return $week_days[$day_id];
147
+ }
148
+
149
+ /**
150
+ * convert_rrule_to_text method
151
+ *
152
+ * Convert a `recurrence rule' to text to display it on screen
153
+ *
154
+ * @return void
155
+ **/
156
+ public function convert_rrule_to_text() {
157
+ $error = false;
158
+ $message = '';
159
+ // check to see if RRULE is set
160
+ if ( isset( $_REQUEST['rrule'] ) ) {
161
+ // check to see if rrule is empty
162
+ if ( empty( $_REQUEST['rrule'] ) ) {
163
+ $error = true;
164
+ $message = Ai1ec_I18n::__(
165
+ 'Recurrence rule cannot be empty.'
166
+ );
167
+ } else {
168
+ //list( $rule, $value ) = explode( '=', $_REQUEST['rrule'], 2 );
169
+ //if ( in_array( array(), $rule ) ) {
170
+ // $message = $this->_registry->get( 'recurrence.date' );
171
+ //
172
+ //} else {
173
+ $rrule = $this->_registry->get( 'recurrence.rule' );
174
+ // convert rrule to text
175
+ $message = ucfirst(
176
+ $rrule->rrule_to_text( $_REQUEST['rrule'] )
177
+ );
178
+ //}
179
+ }
180
+ } else {
181
+ $error = true;
182
+ $message = Ai1ec_I18n::__(
183
+ 'Recurrence rule was not provided.'
184
+ );
185
+ }
186
+ $output = array(
187
+ 'error' => $error,
188
+ 'message' => get_magic_quotes_gpc()
189
+ ? stripslashes( $message )
190
+ : $message,
191
+ );
192
+
193
+ $json_strategy = $this->_registry->get( 'http.response.render.strategy.json' );
194
+ $json_strategy->render( array( 'data' => $output ) );
195
+ }
196
+
197
+ /**
198
+ * create_end_dropdown function
199
+ *
200
+ * Outputs the dropdown list for the recurrence end option.
201
+ *
202
+ * @param int $selected The index of the selected option, if any
203
+ * @return void
204
+ **/
205
+ protected function create_end_dropdown( $selected = NULL ) {
206
+ ob_start();
207
+
208
+ $options = array(
209
+ 0 => Ai1ec_I18n::__( 'Never' ),
210
+ 1 => Ai1ec_I18n::__( 'After' ),
211
+ 2 => Ai1ec_I18n::__( 'On date' ),
212
+ );
213
+
214
+ ?>
215
+ <select name="ai1ec_end" id="ai1ec_end">
216
+ <?php foreach( $options as $key => $val ): ?>
217
+ <option value="<?php echo $key ?>"
218
+ <?php if( $key === $selected ) echo 'selected="selected"' ?>>
219
+ <?php echo $val ?>
220
+ </option>
221
+ <?php endforeach ?>
222
+ </select>
223
+ <?php
224
+
225
+ $output = ob_get_contents();
226
+ ob_end_clean();
227
+
228
+ return $output;
229
+ }
230
+
231
+ /**
232
+ * row_daily function
233
+ *
234
+ * Returns daily selector
235
+ *
236
+ * @return void
237
+ **/
238
+ protected function row_daily( $visible = false, $selected = 1 ) {
239
+ $loader = $this->_registry->get( 'theme.loader' );
240
+
241
+ $args = array(
242
+ 'visible' => $visible,
243
+ 'count' => $this->create_count_input(
244
+ 'ai1ec_daily_count',
245
+ $selected,
246
+ 365
247
+ ) . Ai1ec_I18n::__( 'day(s)' ),
248
+ );
249
+ return $loader->get_file( 'row_daily.php', $args, true )
250
+ ->get_content();
251
+ }
252
+
253
+ /**
254
+ * row_custom function
255
+ *
256
+ * Returns custom dates selector
257
+ *
258
+ * @return void
259
+ **/
260
+ protected function row_custom( $visible = false, $dates = array() ) {
261
+ $loader = $this->_registry->get( 'theme.loader' );
262
+
263
+ $args = array(
264
+ 'visible' => $visible,
265
+ 'selected_dates' => implode( ',', $dates )
266
+ );
267
+ return $loader->get_file( 'row_custom.php', $args, true )
268
+ ->get_content();
269
+ }
270
+
271
+ /**
272
+ * Generates and returns "End after X times" input
273
+ *
274
+ * @param Integer|NULL $count Initial value of range input
275
+ *
276
+ * @return String Repeat dropdown
277
+ */
278
+ protected function create_count_input( $name, $count = 100, $max = 365 ) {
279
+ ob_start();
280
+
281
+ if ( ! $count ) {
282
+ $count = 100;
283
+ }
284
+ ?>
285
+ <input type="range" name="<?php echo $name ?>" id="<?php echo $name ?>"
286
+ min="1" max="<?php echo $max ?>"
287
+ <?php if ( $count ) echo 'value="' . $count . '"' ?> />
288
+ <?php
289
+ return ob_get_clean();
290
+ }
291
+
292
+ /**
293
+ * row_weekly function
294
+ *
295
+ * Returns weekly selector
296
+ *
297
+ * @return void
298
+ **/
299
+ protected function row_weekly(
300
+ $visible = false,
301
+ $count = 1,
302
+ array $selected = array()
303
+ ) {
304
+ global $wp_locale;
305
+ $start_of_week = $this->_registry->get( 'model.option' )
306
+ ->get( 'start_of_week', 1 );
307
+ $loader = $this->_registry->get( 'theme.loader' );
308
+
309
+ $options = array();
310
+ // get days from start_of_week until the last day
311
+ for ( $i = $start_of_week; $i <= 6; ++$i ) {
312
+ $options[$this->get_weekday_by_id( $i )] = $wp_locale
313
+ ->weekday_initial[$wp_locale->weekday[$i]];
314
+ }
315
+
316
+ // get days from 0 until start_of_week
317
+ if ( $start_of_week > 0 ) {
318
+ for ( $i = 0; $i < $start_of_week; $i++ ) {
319
+ $options[$this->get_weekday_by_id( $i )] = $wp_locale
320
+ ->weekday_initial[$wp_locale->weekday[$i]];
321
+ }
322
+ }
323
+
324
+ $args = array(
325
+ 'visible' => $visible,
326
+ 'count' => $this->create_count_input(
327
+ 'ai1ec_weekly_count',
328
+ $count,
329
+ 52
330
+ ) . Ai1ec_I18n::__( 'week(s)' ),
331
+ 'week_days' => $this->create_list_element(
332
+ 'ai1ec_weekly_date_select',
333
+ $options,
334
+ $selected
335
+ )
336
+ );
337
+ return $loader->get_file( 'row_weekly.php', $args, true )
338
+ ->get_content();
339
+ }
340
+
341
+ /**
342
+ * Creates a grid of weekday, day, or month selection buttons.
343
+ *
344
+ * @return string
345
+ */
346
+ protected function create_list_element(
347
+ $name,
348
+ array $options = array(),
349
+ array $selected = array()
350
+ ) {
351
+ ob_start();
352
+ ?>
353
  <div class="ai1ec-btn-group-grid" id="<?php echo $name; ?>">
354
+ <?php foreach ( $options as $key => $val ) : ?>
355
+ <div class="ai1ec-pull-left">
356
+ <a class="ai1ec-btn ai1ec-btn-default ai1ec-btn-block
357
+ <?php echo in_array( $key, $selected ) ? 'ai1ec-active' : ''; ?>">
358
+ <?php echo $val; ?>
359
+ </a>
360
+ <input type="hidden" name="<?php echo $name . '_' . $key; ?>"
361
+ value="<?php echo $key; ?>">
362
+ </div class="ai1ec-pull-left">
363
+ <?php endforeach; ?>
364
  </div>
365
  <input type="hidden" name="<?php echo $name; ?>"
366
+ value="<?php echo implode( ',', $selected ) ?>">
367
  <?php
368
+ return ob_get_clean();
369
+ }
370
+
371
+ /**
372
+ * row_monthly function
373
+ *
374
+ * Returns monthly selector
375
+ *
376
+ * @return void
377
+ **/
378
+ protected function row_monthly(
379
+ $visible = false,
380
+ $count = 1,
381
+ $bymonthday = true,
382
+ $month = array(),
383
+ $day = array()
384
+ ) {
385
+ global $wp_locale;
386
+ $start_of_week = $this->_registry->get( 'model.option' )
387
+ ->get( 'start_of_week', 1 );
388
+ $loader = $this->_registry->get( 'theme.loader' );
389
+
390
+ $options_wd = array();
391
+ // get days from start_of_week until the last day
392
+ for ( $i = $start_of_week; $i <= 6; ++$i ) {
393
+ $options_wd[$this->get_weekday_by_id( $i )] = $wp_locale
394
+ ->weekday[$i];
395
+ }
396
+
397
+ // get days from 0 until start_of_week
398
+ if ( $start_of_week > 0 ) {
399
+ for ( $i = 0; $i < $start_of_week; $i++ ) {
400
+ $options_wd[$this->get_weekday_by_id( $i )] = $wp_locale
401
+ ->weekday[$i];
402
+ }
403
+ }
404
+
405
+ // get options like 1st/2nd/3rd for "day number"
406
+ $options_dn = array( 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5 );
407
+ foreach ( $options_dn as $_dn ) {
408
+ $options_dn[$_dn] = $this->_registry->get(
409
+ 'date.time',
410
+ strtotime( $_dn . '-01-1998 12:00:00' )
411
+ )->format_i18n( 'jS' );
412
+ }
413
+ $options_dn['-1'] = Ai1ec_I18n::__( 'last' );
414
+
415
+ $byday_checked = $bymonthday ? '' : 'checked';
416
+ $byday_expanded = $bymonthday ? 'ai1ec-collapse' : 'ai1ec-in';
417
+ $bymonthday_checked = $bymonthday ? 'checked' : '';
418
+ $bymonthday_expanded = $bymonthday ? 'ai1ec-in' : 'ai1ec-collapse';
419
+
420
+ $args = array(
421
+ 'visible' => $visible,
422
+ 'count' => $this->create_count_input(
423
+ 'ai1ec_monthly_count',
424
+ $count,
425
+ 12
426
+ ) . Ai1ec_I18n::__( 'month(s)' ),
427
+ 'month' => $this->create_monthly_date_select(
428
+ $month
429
+ ),
430
+ 'day_nums' => $this->create_select_element(
431
+ 'ai1ec_monthly_byday_num',
432
+ $options_dn,
433
+ $this->_get_day_number_from_byday( $day )
434
+ ),
435
+ 'week_days' => $this->create_select_element(
436
+ 'ai1ec_monthly_byday_weekday',
437
+ $options_wd,
438
+ $this->_get_day_shortname_from_byday( $day )
439
+ ),
440
+ 'bymonthday_checked' => $bymonthday_checked,
441
+ 'byday_checked' => $byday_checked,
442
+ 'bymonthday_expanded' => $bymonthday_expanded,
443
+ 'byday_expanded' => $byday_expanded,
444
+ );
445
+ return $loader->get_file( 'row_monthly.php', $args, true )
446
+ ->get_content();
447
+ }
448
+
449
+ /**
450
+ * Creates selector for dates in monthly repeat tab.
451
+ *
452
+ * @return void
453
+ */
454
+ protected function create_monthly_date_select( $selected = array() ) {
455
+ $options = array();
456
+ for ( $i = 1; $i <= 31; ++$i ) {
457
+ $options[$i] = $i;
458
+ }
459
+ return $this->create_list_element(
460
+ 'ai1ec_montly_date_select',
461
+ $options,
462
+ $selected
463
+ );
464
+ }
465
+
466
+ /**
467
+ * create_on_the_select function
468
+ *
469
+ *
470
+ *
471
+ * @return string
472
+ **/
473
+ protected function create_on_the_select(
474
+ $f_selected = false,
475
+ $s_selected = false
476
+ ) {
477
+ $ret = '';
478
+
479
+ $first_options = array(
480
+ '0' => Ai1ec_I18n::__( 'first' ),
481
+ '1' => Ai1ec_I18n::__( 'second' ),
482
+ '2' => Ai1ec_I18n::__( 'third' ),
483
+ '3' => Ai1ec_I18n::__( 'fourth' ),
484
+ '4' => '------',
485
+ '5' => Ai1ec_I18n::__( 'last' )
486
+ );
487
+ $ret = $this->create_select_element(
488
+ 'ai1ec_monthly_each_select',
489
+ $first_options,
490
+ $f_selected,
491
+ array( 4 )
492
+ );
493
+
494
+ $second_options = array(
495
+ '0' => Ai1ec_I18n::__( 'Sunday' ),
496
+ '1' => Ai1ec_I18n::__( 'Monday' ),
497
+ '2' => Ai1ec_I18n::__( 'Tuesday' ),
498
+ '3' => Ai1ec_I18n::__( 'Wednesday' ),
499
+ '4' => Ai1ec_I18n::__( 'Thursday' ),
500
+ '5' => Ai1ec_I18n::__( 'Friday' ),
501
+ '6' => Ai1ec_I18n::__( 'Saturday' ),
502
+ '7' => '--------',
503
+ '8' => Ai1ec_I18n::__( 'day' ),
504
+ '9' => Ai1ec_I18n::__( 'weekday' ),
505
+ '10' => Ai1ec_I18n::__( 'weekend day' )
506
+ );
507
+
508
+ return $ret . $this->create_select_element(
509
+ 'ai1ec_monthly_on_the_select',
510
+ $second_options,
511
+ $s_selected,
512
+ array( 7 )
513
+ );
514
+ }
515
+
516
+ /**
517
+ * create_select_element function
518
+ *
519
+ * Render HTML <select> element
520
+ *
521
+ * @param string $name Name of element to be rendered
522
+ * @param array $options Select <option> values as key=>value pairs
523
+ * @param string $selected Key to be marked as selected [optional=false]
524
+ * @param array $disabled_keys List of options to disable [optional=array]
525
+ *
526
+ * @return string Rendered <select> HTML element
527
+ **/
528
+ protected function create_select_element(
529
+ $name,
530
+ array $options = array(),
531
+ $selected = false,
532
+ array $disabled_keys = array()
533
+ ) {
534
+ ob_start();
535
+ ?>
536
+ <select name="<?php echo $name ?>" id="<?php echo $name ?>">
537
+ <?php foreach( $options as $key => $val ): ?>
538
+ <option value="<?php echo $key ?>"
539
+ <?php echo $key === $selected ? 'selected="selected"' : '' ?>
540
+ <?php echo in_array( $key, $disabled_keys ) ? 'disabled' : '' ?>>
541
+ <?php echo $val ?>
542
+ </option>
543
+ <?php endforeach ?>
544
+ </select>
545
+ <?php
546
+ return ob_get_clean();
547
+ }
548
+
549
+ /**
550
+ * row_yearly function
551
+ *
552
+ * Returns yearly selector
553
+ *
554
+ * @return void
555
+ **/
556
+ protected function row_yearly(
557
+ $visible = false,
558
+ $count = 1,
559
+ $year = array(),
560
+ $first = false,
561
+ $second = false
562
+ ) {
563
+ $loader = $this->_registry->get( 'theme.loader' );
564
+
565
+ $args = array(
566
+ 'visible' => $visible,
567
+ 'count' => $this->create_count_input(
568
+ 'ai1ec_yearly_count',
569
+ $count,
570
+ 10
571
+ ) . Ai1ec_I18n::__( 'year(s)' ),
572
+ 'year' => $this->create_yearly_date_select( $year ),
573
+ 'on_the_select' => $this->create_on_the_select(
574
+ $first,
575
+ $second
576
+ ),
577
+ );
578
+ return $loader->get_file( 'row_yearly.php', $args, true )
579
+ ->get_content();
580
+ }
581
+
582
+ /**
583
+ * create_yearly_date_select function
584
+ *
585
+ *
586
+ *
587
+ * @return void
588
+ **/
589
+ protected function create_yearly_date_select( $selected = array() ) {
590
+ global $wp_locale;
591
+ $options = array();
592
+ for ( $i = 1; $i <= 12; ++$i ) {
593
+ $options[$i] = $wp_locale->month_abbrev[
594
+ $wp_locale->month[sprintf( '%02d', $i )]
595
+ ];
596
+ }
597
+ return $this->create_list_element(
598
+ 'ai1ec_yearly_date_select',
599
+ $options,
600
+ $selected
601
+ );
602
+ }
603
+
604
+ /**
605
+ * Converts recurrence rule to array of string of dates.
606
+ *
607
+ * @param string $rule RUle.
608
+ *
609
+ * @return array Array of dates or empty array.
610
+ * @throws Ai1ec_Bootstrap_Exception
611
+ */
612
+ protected function get_date_array_from_rule( $rule ) {
613
+ if (
614
+ 'RDATE' !== substr( $rule, 0, 5 ) &&
615
+ 'EXDATE' !== substr( $rule, 0, 6 )
616
+ ) {
617
+ return array();
618
+ }
619
+ $line = new SG_iCal_Line( 'RRULE:' . $rule );
620
+ $dates = $line->getDataAsArray();
621
+ $dates_as_strings = array();
622
+ foreach ( $dates as $date ) {
623
+ $date = str_replace( array( 'RDATE=', 'EXDATE=' ), '', $date );
624
+ $date = $this->_registry->get( 'date.time', $date )->set_preferred_timezone( 'UTC' );
625
+ $dates_as_strings[] = $date->format( 'm/d/Y' );
626
+ }
627
+ return $dates_as_strings;
628
+ }
629
+
630
+ /**
631
+ * Returns whether recurrence rule has non null ByMonthDay.
632
+ *
633
+ * @param SG_iCal_Recurrence $rc iCal class.
634
+ *
635
+ * @return bool True or false.
636
+ */
637
+ protected function _is_monthday_empty( SG_iCal_Recurrence $rc ) {
638
+ return false === $rc->getByMonthDay();
639
+ }
640
+
641
+ /**
642
+ * Returns day number from by day array.
643
+ *
644
+ * @param array $day
645
+ *
646
+ * @return bool|int Day of false if empty array.
647
+ */
648
+ protected function _get_day_number_from_byday( array $day ) {
649
+ return isset( $day[0] ) ? (int) $day[0] : false;
650
+ }
651
+
652
+ /**
653
+ * Returns string part from "ByDay" recurrence rule.
654
+ *
655
+ * @param array $day Element to parse.
656
+ *
657
+ * @return bool|string False if empty or not matched, otherwise short day
658
+ * name.
659
+ */
660
+ protected function _get_day_shortname_from_byday( $day ) {
661
+ if ( empty( $day ) ) {
662
+ return false;
663
+ }
664
+ $value = $day[0];
665
+ if ( preg_match('/[-]?\d([A-Z]+)/', $value, $matches ) ) {
666
+ return $matches[1];
667
+ }
668
+ return false;
669
+ }
670
  }
app/view/admin/get-tax-box.php CHANGED
@@ -10,21 +10,21 @@
10
  * @subpackage AI1EC.View
11
  */
12
  class Ai1ec_View_Admin_Get_Tax_Box extends Ai1ec_Base {
13
- /**
14
- * get_tax_box function
15
- *
16
- * @return string
17
- **/
18
- public function get_tax_box() {
19
- $api = $this->_registry->get( 'model.api.api-ticketing' );
20
- $post_id = $_POST['ai1ec_event_id'];
21
- $modal = $api->get_tax_options_modal( $post_id );
22
- $output = array(
23
- 'error' => $modal->error,
24
- 'message' => $modal->data
25
- );
26
- $json_strategy = $this->_registry->get( 'http.response.render.strategy.json' );
27
- $json_strategy->render( array( 'data' => $output ) );
28
- }
29
 
30
  }
10
  * @subpackage AI1EC.View
11
  */
12
  class Ai1ec_View_Admin_Get_Tax_Box extends Ai1ec_Base {
13
+ /**
14
+ * get_tax_box function
15
+ *
16
+ * @return string
17
+ **/
18
+ public function get_tax_box() {
19
+ $api = $this->_registry->get( 'model.api.api-ticketing' );
20
+ $post_id = $_POST['ai1ec_event_id'];
21
+ $modal = $api->get_tax_options_modal( $post_id );
22
+ $output = array(
23
+ 'error' => $modal->error,
24
+ 'message' => $modal->data
25
+ );
26
+ $json_strategy = $this->_registry->get( 'http.response.render.strategy.json' );
27
+ $json_strategy->render( array( 'data' => $output ) );
28
+ }
29
 
30
  }
app/view/admin/nav.php CHANGED
@@ -10,27 +10,27 @@
10
  */
11
  class Ai1ec_View_Admin_Navigation extends Ai1ec_Base {
12
 
13
- /**
14
- * Adds a link to Settings page in plugin list page.
15
- *
16
- * @param array $links List of available links.
17
- *
18
- * @return array Modified links list.
19
- */
20
- public function plugin_action_links( $links ) {
21
- $settings_link = sprintf(
22
- Ai1ec_I18n::__( '<a href="%s">Settings</a>' ),
23
- ai1ec_admin_url( AI1EC_SETTINGS_BASE_URL )
24
- );
25
- array_unshift( $links, $settings_link );
26
- if ( current_user_can( 'activate_plugins' ) ) {
27
- $updates_link = sprintf(
28
- Ai1ec_I18n::__( '<a href="%s">Check for updates</a>' ),
29
- ai1ec_admin_url( AI1EC_FORCE_UPDATES_URL )
30
- );
31
- array_push( $links, $updates_link );
32
- }
33
- return $links;
34
- }
35
 
36
  }
10
  */
11
  class Ai1ec_View_Admin_Navigation extends Ai1ec_Base {
12
 
13
+ /**
14
+ * Adds a link to Settings page in plugin list page.
15
+ *
16
+ * @param array $links List of available links.
17
+ *
18
+ * @return array Modified links list.
19
+ */
20
+ public function plugin_action_links( $links ) {
21
+ $settings_link = sprintf(
22
+ Ai1ec_I18n::__( '<a href="%s">Settings</a>' ),
23
+ ai1ec_admin_url( AI1EC_SETTINGS_BASE_URL )
24
+ );
25
+ array_unshift( $links, $settings_link );
26
+ if ( current_user_can( 'activate_plugins' ) ) {
27
+ $updates_link = sprintf(
28
+ Ai1ec_I18n::__( '<a href="%s">Check for updates</a>' ),
29
+ ai1ec_admin_url( AI1EC_FORCE_UPDATES_URL )
30
+ );
31
+ array_push( $links, $updates_link );
32
+ }
33
+ return $links;
34
+ }
35
 
36
  }
app/view/admin/organize.php CHANGED
@@ -11,95 +11,95 @@
11
  */
12
  class Ai1ec_View_Organize extends Ai1ec_Base {
13
 
14
- /**
15
- * @var array The taxonomies for events
16
- */
17
- protected $_taxonomies = array();
18
 
19
- /**
20
- * Register actions to draw the headers
21
- */
22
- public function add_taxonomy_actions() {
23
- $taxonomies = get_object_taxonomies( AI1EC_POST_TYPE, 'object' );
24
- $dispatcher = $this->_registry->get( 'event.dispatcher' );
25
- $taxonomy_metadata = array(
26
- 'events_categories' => array(
27
- 'icon' => 'ai1ec-fa ai1ec-fa-folder-open'
28
- ),
29
- 'events_tags' => array(
30
- 'icon' => 'ai1ec-fa ai1ec-fa-tags'
31
- )
32
- );
33
- $taxonomy_metadata = apply_filters(
34
- 'ai1ec_add_custom_groups',
35
- $taxonomy_metadata
36
- );
37
- do_action( 'ai1ec_taxonomy_management_css' );
38
- foreach ( $taxonomies as $taxonomy => $data ) {
39
- if ( true === $data->public ) {
40
- $active_taxonomy =
41
- isset( $_GET['taxonomy'] ) &&
42
- $taxonomy === $_GET['taxonomy'];
43
- $edit_url = $edit_label = '';
44
- if ( isset( $taxonomy_metadata[$taxonomy]['url'] ) ) {
45
- $edit_url = $taxonomy_metadata[$taxonomy]['url'];
46
- $edit_label = $taxonomy_metadata[$taxonomy]['edit_label'];
47
- }
48
- $this->_taxonomies[] = array(
49
- 'taxonomy_name' => $taxonomy,
50
- 'url' => add_query_arg(
51
- array(
52
- 'post_type' => AI1EC_POST_TYPE,
53
- 'taxonomy' => $taxonomy
54
- ),
55
- admin_url( 'edit-tags.php' )
56
- ),
57
- 'name' => $data->labels->name,
58
- 'active' => $active_taxonomy,
59
- 'icon' => isset( $taxonomy_metadata[$taxonomy] ) ?
60
- $taxonomy_metadata[$taxonomy]['icon'] :
61
- '',
62
- 'edit_url' => $edit_url,
63
- 'edit_label' => $edit_label,
64
- );
65
 
66
- if ( $active_taxonomy ) {
67
- $dispatcher->register_action(
68
- $taxonomy . '_pre_add_form',
69
- array( 'view.admin.organize', 'render_header' )
70
- );
71
- $dispatcher->register_action(
72
- $taxonomy . '_pre_edit_form',
73
- array( 'view.admin.organize', 'render_header' )
74
- );
75
- }
76
- }
77
- }
78
- }
79
 
80
- /**
81
- * Render tabbed header to manage taxonomies.
82
- */
83
- public function render_header() {
84
- echo $this->get_header();
85
- }
86
 
87
- /**
88
- * Generate and return tabbed header to manage taxonomies.
89
- *
90
- * @return string HTML markup for tabbed header
91
- */
92
- public function get_header() {
93
- return $this->_registry->get( 'theme.loader' )->get_file(
94
- 'organize/header.twig',
95
- array(
96
- 'taxonomies' => apply_filters(
97
- 'ai1ec_custom_taxonomies',
98
- $this->_taxonomies
99
- ),
100
- 'text_title' => Ai1ec_I18n::__( 'Organize Events' ),
101
- ),
102
- true
103
- )->get_content();
104
- }
105
  }
11
  */
12
  class Ai1ec_View_Organize extends Ai1ec_Base {
13
 
14
+ /**
15
+ * @var array The taxonomies for events
16
+ */
17
+ protected $_taxonomies = array();
18
 
19
+ /**
20
+ * Register actions to draw the headers
21
+ */
22
+ public function add_taxonomy_actions() {
23
+ $taxonomies = get_object_taxonomies( AI1EC_POST_TYPE, 'object' );
24
+ $dispatcher = $this->_registry->get( 'event.dispatcher' );
25
+ $taxonomy_metadata = array(
26
+ 'events_categories' => array(
27
+ 'icon' => 'ai1ec-fa ai1ec-fa-folder-open'
28
+ ),
29
+ 'events_tags' => array(
30
+ 'icon' => 'ai1ec-fa ai1ec-fa-tags'
31
+ )
32
+ );
33
+ $taxonomy_metadata = apply_filters(
34
+ 'ai1ec_add_custom_groups',
35
+ $taxonomy_metadata
36
+ );
37
+ do_action( 'ai1ec_taxonomy_management_css' );
38
+ foreach ( $taxonomies as $taxonomy => $data ) {
39
+ if ( true === $data->public ) {
40
+ $active_taxonomy =
41
+ isset( $_GET['taxonomy'] ) &&
42
+ $taxonomy === $_GET['taxonomy'];
43
+ $edit_url = $edit_label = '';
44
+ if ( isset( $taxonomy_metadata[$taxonomy]['url'] ) ) {
45
+ $edit_url = $taxonomy_metadata[$taxonomy]['url'];
46
+ $edit_label = $taxonomy_metadata[$taxonomy]['edit_label'];
47
+ }
48
+ $this->_taxonomies[] = array(
49
+ 'taxonomy_name' => $taxonomy,
50
+ 'url' => add_query_arg(
51
+ array(
52
+ 'post_type' => AI1EC_POST_TYPE,
53
+ 'taxonomy' => $taxonomy
54
+ ),
55
+ admin_url( 'edit-tags.php' )
56
+ ),
57
+ 'name' => $data->labels->name,
58
+ 'active' => $active_taxonomy,
59
+ 'icon' => isset( $taxonomy_metadata[$taxonomy] ) ?
60
+ $taxonomy_metadata[$taxonomy]['icon'] :
61
+ '',
62
+ 'edit_url' => $edit_url,
63
+ 'edit_label' => $edit_label,
64
+ );
65
 
66
+ if ( $active_taxonomy ) {
67
+ $dispatcher->register_action(
68
+ $taxonomy . '_pre_add_form',
69
+ array( 'view.admin.organize', 'render_header' )
70
+ );
71
+ $dispatcher->register_action(
72
+ $taxonomy . '_pre_edit_form',
73
+ array( 'view.admin.organize', 'render_header' )
74
+ );
75
+ }
76
+ }
77
+ }
78
+ }
79
 
80
+ /**
81
+ * Render tabbed header to manage taxonomies.
82
+ */
83
+ public function render_header() {
84
+ echo $this->get_header();
85
+ }
86
 
87
+ /**
88
+ * Generate and return tabbed header to manage taxonomies.
89
+ *
90
+ * @return string HTML markup for tabbed header
91
+ */
92
+ public function get_header() {
93
+ return $this->_registry->get( 'theme.loader' )->get_file(
94
+ 'organize/header.twig',
95
+ array(
96
+ 'taxonomies' => apply_filters(
97
+ 'ai1ec_custom_taxonomies',
98
+ $this->_taxonomies
99
+ ),
100
+ 'text_title' => Ai1ec_I18n::__( 'Organize Events' ),
101
+ ),
102
+ true
103
+ )->get_content();
104
+ }
105
  }
app/view/admin/samples.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * The Calendar Samples page.
5
+ *
6
+ * @author Time.ly Network Inc.
7
+ * @since 2.1
8
+ *
9
+ * @package AI1EC
10
+ * @subpackage AI1EC.View
11
+ */
12
+ class Ai1ec_View_Samples extends Ai1ec_View_Admin_Abstract {
13
+ /**
14
+ * Adds page to the menu.
15
+ *
16
+ * @wp_hook admin_menu
17
+ *
18
+ * @return void
19
+ */
20
+ public function add_page() {
21
+ // =======================
22
+ // = Calendar Add Ons Page =
23
+ // =======================
24
+ add_submenu_page(
25
+ AI1EC_ADMIN_BASE_URL,
26
+ Ai1ec_I18n::__( 'Samples' ),
27
+ Ai1ec_I18n::__( 'Samples' ),
28
+ 'manage_ai1ec_options',
29
+ AI1EC_PLUGIN_NAME . '-samples',
30
+ array( $this, 'display_page' )
31
+ );
32
+ }
33
+ /**
34
+ * Display Add Ons list page.
35
+ *
36
+ * @return void
37
+ */
38
+ public function display_page() {
39
+ wp_enqueue_style(
40
+ 'ai1ec_samples.css',
41
+ AI1EC_ADMIN_THEME_CSS_URL . 'samples.css',
42
+ array(),
43
+ AI1EC_VERSION
44
+ );
45
+
46
+ $this->_registry->get( 'theme.loader' )->get_file(
47
+ 'samples.twig',
48
+ array(),
49
+ true
50
+ )->render();
51
+ }
52
+
53
+ public function add_meta_box() {
54
+ }
55
+
56
+ public function display_meta_box( $object, $box ) {
57
+ }
58
+
59
+ public function handle_post() {
60
+ }
61
+
62
+ }
app/view/admin/settings.php CHANGED
@@ -11,338 +11,338 @@
11
  */
12
  class Ai1ec_View_Admin_Settings extends Ai1ec_View_Admin_Abstract {
13
 
14
- /**
15
- * @var string The nonce action
16
- */
17
- CONST NONCE_ACTION = 'ai1ec_settings_save';
18
 
19
- /**
20
- * @var string The nonce name
21
- */
22
- CONST NONCE_NAME = 'ai1ec_settings_nonce';
23
 
24
- /* (non-PHPdoc)
25
- * @see Ai1ec_View_Admin_Abstract::display_page()
26
- */
27
- public function display_page() {
28
- $settings = $this->_registry->get( 'model.settings' );
29
- $args = array(
30
- 'title' => Ai1ec_I18n::__(
31
- 'All-in-One Event Calendar: Settings'
32
- ),
33
- 'nonce' => array(
34
- 'action' => self::NONCE_ACTION,
35
- 'name' => self::NONCE_NAME,
36
- 'referrer' => false,
37
- ),
38
- 'metabox' => array(
39
- 'screen' => $settings->get( 'settings_page' ),
40
- 'action' => 'left',
41
- 'object' => null
42
- ),
43
- 'support' => array(
44
- 'screen' => $settings->get( 'settings_page' ),
45
- 'action' => 'right',
46
- 'object' => null
47
- ),
48
- 'action' =>
49
- ai1ec_admin_url(
50
- '?controller=front&action=ai1ec_save_settings&plugin=' .
51
- AI1EC_PLUGIN_NAME
52
- ),
53
- );
54
- $loader = $this->_registry->get( 'theme.loader' );
55
- $file = $loader->get_file( 'setting/page.twig', $args, true );
56
- $file->render();
57
- if ( apply_filters( 'ai1ec_robots_install', true ) ) {
58
- $this->_registry->get( 'robots.helper' )->install();
59
- }
60
- }
61
 
62
- /* (non-PHPdoc)
63
- * @see Ai1ec_View_Admin_Abstract::add_page()
64
- */
65
- public function add_page() {
66
- $settings_page = add_submenu_page(
67
- AI1EC_ADMIN_BASE_URL,
68
- Ai1ec_I18n::__( 'Settings' ),
69
- Ai1ec_I18n::__( 'Settings' ),
70
- 'manage_ai1ec_options',
71
- AI1EC_PLUGIN_NAME . '-settings',
72
- array( $this, 'display_page' )
73
- );
74
- $this->_registry->get( 'model.settings' )
75
- ->set( 'settings_page', $settings_page );
76
- }
77
 
78
- /**
79
- * Adds metabox to the page.
80
- *
81
- * @wp_hook admin_init
82
- *
83
- * @return void
84
- */
85
- public function add_meta_box() {
86
- // Add the 'General Settings' meta box.
87
- add_meta_box(
88
- 'ai1ec-general-settings',
89
- Ai1ec_I18n::_x( 'General Settings', 'meta box' ),
90
- array( $this, 'display_meta_box' ),
91
- $this->_registry->get( 'model.settings' )->get( 'settings_page' ),
92
- 'left',
93
- 'default'
94
- );
95
- // Add the 'Timely' meta box.
96
- add_meta_box(
97
- 'ai1ec-support',
98
- Ai1ec_I18n::_x( 'Timely', 'meta box', AI1EC_PLUGIN_NAME ),
99
- array( $this, 'support_meta_box' ),
100
- $this->_registry->get( 'model.settings' )->get( 'settings_page' ),
101
- 'right',
102
- 'default'
103
- );
104
- }
105
 
106
- /**
107
- * Renders the Timely blog meta box
108
- *
109
- * @param mixed $object
110
- * @param mixed $box
111
- */
112
- public function support_meta_box( $object, $box ) {
113
- $newsItems = $this->_registry->get( 'news.feed' )->import_feed();
114
- $loader = $this->_registry->get( 'theme.loader' );
115
- $file = $loader->get_file(
116
- 'box_support.php',
117
- array(
118
- 'news' => $newsItems,
119
- ),
120
- true
121
- );
122
- $file->render();
123
- }
124
 
125
- /* (non-PHPdoc)
126
- * @see Ai1ec_View_Admin_Abstract::handle_post()
127
- */
128
- public function handle_post() {
129
- }
130
 
131
- /**
132
- * Displays the meta box for the settings page.
133
- *
134
- * @param mixed $object
135
- * @param mixed $box
136
- */
137
- public function display_meta_box( $object, $box ) {
138
- $tabs = array(
139
- 'viewing-events' => array(
140
- 'name' => Ai1ec_I18n::__( 'Viewing Events' ),
141
- ),
142
- 'editing-events' => array(
143
- 'name' => Ai1ec_I18n::__( 'Adding/Editing Events' ),
144
- ),
145
- 'advanced' => array(
146
- 'name' => Ai1ec_I18n::__( 'Advanced' ),
147
- 'items' => array(
148
- 'advanced' => Ai1ec_I18n::__( 'Advanced Settings' ),
149
- 'embedded-views' => Ai1ec_I18n::__( 'Shortcodes' ),
150
- 'email' => Ai1ec_I18n::__( 'Email Templates' ),
151
- 'apis' => Ai1ec_I18n::__( 'External Services' ),
152
- 'cache' => Ai1ec_I18n::__( 'Cache Report' ),
153
- )
154
- ),
155
- );
156
 
157
- // let other extensions add tabs.
158
- $tabs = apply_filters( 'ai1ec_add_setting_tabs', $tabs );
159
- $settings = $this->_registry->get( 'model.settings' );
160
- $plugin_settings = $settings->get_options();
161
- $tabs = $this->_get_tabs_to_show( $plugin_settings, $tabs );
162
- $loader = $this->_registry->get( 'theme.loader' );
163
-
164
- $api = $this->_registry->get( 'model.api.api-registration' );
165
- $signup_available = $api->is_api_sign_up_available();
166
- $signed_to_api = $api->is_signed();
167
- $ticketing_message = $api->get_sign_message();
168
- $loader = $this->_registry->get( 'theme.loader' );
169
- $account = $api->get_current_account();
170
- $signup_args = array(
171
- 'api_signed' => $signed_to_api,
172
- 'signup_available' => $signup_available,
173
- 'title' => Ai1ec_I18n::__(
174
- 'Please, Sign In to Timely Network.'
175
- ),
176
- 'nonce' => array(
177
- 'action' => 'ai1ec_api_ticketing_signup',
178
- 'name' => 'ai1ec_api_ticketing_nonce',
179
- 'referrer' => false,
180
- ),
181
- 'api_action' =>
182
- '?controller=front&action=ai1ec_api_ticketing_signup&plugin=' .
183
- AI1EC_PLUGIN_NAME,
184
- 'required_text' => Ai1ec_I18n::__( 'This field is required.' ),
185
- 'register_text' => Ai1ec_I18n::__( 'Register' ),
186
- 'sign_in_text' => Ai1ec_I18n::__( 'Sign in' ),
187
- 'signed_in_text' => Ai1ec_I18n::__(
188
- 'You are signed in to <b>Timely Network</b> as ' . $account
189
- ),
190
- 'sign_out_text' => Ai1ec_I18n::__( 'Sign out' ),
191
- 'can_sign_out' => apply_filters( 'ai1ec_api_can_sign_out', true ),
192
- 'full_name_text' => Ai1ec_I18n::__( 'Full Name:' ),
193
- 'hide_form_text' => Ai1ec_I18n::__( 'Hide form' ),
194
- 'show_form_text' => Ai1ec_I18n::__( 'Show form' ),
195
- 'email_text' => Ai1ec_I18n::__( 'Email:' ),
196
- 'password_text' => Ai1ec_I18n::__( 'Password:' ),
197
- 'confirm_password_text' => Ai1ec_I18n::__( 'Confirm Password:' ),
198
- 'phone_number_text' => Ai1ec_I18n::__( 'Phone Number:' ),
199
- 'terms_text' => Ai1ec_I18n::__(
200
- 'I confirm that I have read, understand and agree with the <a href="https://time.ly/tos">terms of service</a>.'
201
- ),
202
- 'sign_out_warning' => Ai1ec_I18n::__(
203
- '<h4>Attention Required:</h4>If you choose to sign-out of the API Timely Network this will close all the created tickets and remove user access to them. In this case, on the event page, users will see the status “Event closed”.'
204
- ),
205
- 'sign_out_cancel' => Ai1ec_I18n::__( 'Cancel' ),
206
- 'sign_out_confirm' => Ai1ec_I18n::__( 'Sign Out' ),
207
- 'sign_up_button_text' => Ai1ec_I18n::__( 'Sign Up' ),
208
- 'sign_in_button_text' => Ai1ec_I18n::__( 'Sign In' ),
209
- 'calendar_type_text' => Ai1ec_I18n::__( 'Calendar Type:' ),
210
- 'calendar_types' => array(
211
- 'tourism' => Ai1ec_I18n::__( 'Tourism' ),
212
- 'media' => Ai1ec_I18n::__( 'Media' ),
213
- 'community_hubs' => Ai1ec_I18n::__( 'Community Hubs' ),
214
- 'education' => Ai1ec_I18n::__( 'Education' ),
215
- 'venue_business' => Ai1ec_I18n::__( 'Venue/Business' ),
216
- 'artist_performer' => Ai1ec_I18n::__( 'Artist/Performer' ),
217
- 'church_spiritual' => Ai1ec_I18n::__( 'Church/Spiritual' ),
218
- 'association_group' => Ai1ec_I18n::__( 'Association/Group' ),
219
- 'other' => Ai1ec_I18n::__( 'Other' )
220
- ),
221
- );
222
- $loader->get_file( 'setting/api-signup.twig', $signup_args, true )->render();
223
-
224
-
225
- $args = array(
226
- 'tabs' => $tabs,
227
- 'content_class' => 'ai1ec-form-horizontal',
228
- 'submit' => array(
229
- 'id' => 'ai1ec_save_settings',
230
- 'value' => '<i class="ai1ec-fa ai1ec-fa-save ai1ec-fa-fw"></i> ' .
231
- Ai1ec_I18n::__( 'Save Settings' ),
232
- 'args' => array(
233
- 'class' => 'ai1ec-btn ai1ec-btn-primary ai1ec-btn-lg',
234
- ),
235
- ),
236
- 'pre_tabs_markup' => sprintf(
237
- '<div class="ai1ec-gzip-causes-js-failure">' .
238
- Ai1ec_I18n::__(
239
- 'If the form below is not working please follow <a href="%s">this link</a>.'
240
- ) .
241
- '</div>',
242
- wp_nonce_url(
243
- add_query_arg( 'ai1ec_disable_gzip_compression', '1' ),
244
- 'ai1ec_disable_gzip_compression'
245
- )
246
- )
247
- );
248
 
249
- $file = $loader->get_file( 'setting/bootstrap_tabs.twig', $args, true );
250
- $file->render();
251
-
252
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
- /**
255
- * Based on the plugin options, decides what tabs to render.
256
- *
257
- *
258
- *
259
- * @param array $plugin_settings
260
- * @param array $tabs
261
- *
262
- * @return array
263
- */
264
- protected function _get_tabs_to_show( array $plugin_settings, array $tabs ) {
265
- $index = 0;
266
- $renderer = $this->_registry->get( 'html.element.setting-renderer' );
267
- foreach ( $plugin_settings as $id => $setting ) {
268
- // if the setting is shown
269
- if ( isset ( $setting['renderer'] ) ) {
270
- $tab_to_use = isset( $setting['renderer']['item'] ) ?
271
- $setting['renderer']['item'] :
272
- $setting['renderer']['tab'];
273
- // check if it's the first one
274
- if (
275
- ! isset ( $tabs[$tab_to_use]['elements'] )
276
- ) {
277
- $tabs[$tab_to_use]['elements'] = array();
278
- }
279
- $setting['id'] = $id;
280
- // render the settings
281
- $weight = 10;
282
- if ( isset( $setting['renderer']['weight'] ) ) {
283
- $weight = (int)$setting['renderer']['weight'];
284
- }
285
- // NOTICE: do NOT change order of two first
286
- // elements {weight,index}, otherwise sorting will fail.
287
- $tabs[$tab_to_use]['elements'][] = array(
288
- 'weight' => $weight,
289
- 'index' => ++$index,
290
- 'html' => $renderer->render( $setting ),
291
- );
292
- // if the settings has an item tab, set the item as active.
293
- if ( isset( $setting['renderer']['item'] ) ) {
294
- if ( ! isset( $tabs[$setting['renderer']['tab']]['items_active'][$setting['renderer']['item']] ) ) {
295
- $tabs[$setting['renderer']['tab']]['items_active'][$setting['renderer']['item']] = true;
296
- }
297
- }
298
- }
299
- }
300
- $tabs_to_display = array();
301
- // now let's see what tabs to display.
302
- foreach ( $tabs as $name => $tab ) {
303
- // sort by weights
304
- if ( isset( $tab['elements'] ) ) {
305
- asort( $tab['elements'] );
306
- }
307
- // if a tab has more than one item.
308
- if ( isset( $tab['items'] ) ) {
309
- // if no item is active, nothing is shown
310
- if ( empty( $tab['items_active'] ) ) {
311
- continue;
312
- }
313
- // if only one item is active, do not use the dropdown
314
- if ( count( $tab['items_active'] ) === 1 ) {
315
- $name = key($tab['items_active']);
316
- $tab['name'] = $tab['items'][$name];
317
- unset ( $tab['items'] );
318
- } else {
319
- // check active items for the dropdown
320
- foreach ( $tab['items'] as $item => $longname ) {
321
- if ( ! isset( $tab['items_active'][$item] ) ) {
322
- unset( $tab['items'][$item] );
323
- }
324
- }
325
- }
326
- // lets make a check to avoid overriding tabs
327
- if ( ! isset( $tabs_to_display[$name] ) ) {
328
- $tabs_to_display[$name] = $tab;
329
- } else {
330
- $tabs_to_display[$name]['elements'] = $tab['elements'];
331
- }
332
 
333
- } else {
334
- // no items, just check for any element to display.
335
- if ( isset( $tab['elements'] ) ) {
336
- // lets make a check to avoid overriding tabs
337
- if ( ! isset( $tabs_to_display[$name] ) ) {
338
- $tabs_to_display[$name] = $tab;
339
- } else {
340
- $tabs_to_display[$name]['elements'] = $tab['elements'];
341
- }
342
- }
343
- }
344
- }
345
- return $tabs_to_display;
346
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
 
348
  }
11
  */
12
  class Ai1ec_View_Admin_Settings extends Ai1ec_View_Admin_Abstract {
13
 
14
+ /**
15
+ * @var string The nonce action
16
+ */
17
+ CONST NONCE_ACTION = 'ai1ec_settings_save';
18
 
19
+ /**
20
+ * @var string The nonce name
21
+ */
22
+ CONST NONCE_NAME = 'ai1ec_settings_nonce';
23
 
24
+ /* (non-PHPdoc)
25
+ * @see Ai1ec_View_Admin_Abstract::display_page()
26
+ */
27
+ public function display_page() {
28
+ $settings = $this->_registry->get( 'model.settings' );
29
+ $args = array(
30
+ 'title' => Ai1ec_I18n::__(
31
+ 'All-in-One Event Calendar: Settings'
32
+ ),
33
+ 'nonce' => array(
34
+ 'action' => self::NONCE_ACTION,
35
+ 'name' => self::NONCE_NAME,
36
+ 'referrer' => false,
37
+ ),
38
+ 'metabox' => array(
39
+ 'screen' => $settings->get( 'settings_page' ),
40
+ 'action' => 'left',
41
+ 'object' => null
42
+ ),
43
+ 'support' => array(
44
+ 'screen' => $settings->get( 'settings_page' ),
45
+ 'action' => 'right',
46
+ 'object' => null
47
+ ),
48
+ 'action' =>
49
+ ai1ec_admin_url(
50
+ '?controller=front&action=ai1ec_save_settings&plugin=' .
51
+ AI1EC_PLUGIN_NAME
52
+ ),
53
+ );
54
+ $loader = $this->_registry->get( 'theme.loader' );
55
+ $file = $loader->get_file( 'setting/page.twig', $args, true );
56
+ $file->render();
57
+ if ( apply_filters( 'ai1ec_robots_install', true ) ) {
58
+ $this->_registry->get( 'robots.helper' )->install();
59
+ }
60
+ }
61
 
62
+ /* (non-PHPdoc)
63
+ * @see Ai1ec_View_Admin_Abstract::add_page()
64
+ */
65
+ public function add_page() {
66
+ $settings_page = add_submenu_page(
67
+ AI1EC_ADMIN_BASE_URL,
68
+ Ai1ec_I18n::__( 'Settings' ),
69
+ Ai1ec_I18n::__( 'Settings' ),
70
+ 'manage_ai1ec_options',
71
+ AI1EC_PLUGIN_NAME . '-settings',
72
+ array( $this, 'display_page' )
73
+ );
74
+ $this->_registry->get( 'model.settings' )
75
+ ->set( 'settings_page', $settings_page );
76
+ }
77
 
78
+ /**
79
+ * Adds metabox to the page.
80
+ *
81
+ * @wp_hook admin_init
82
+ *
83
+ * @return void
84
+ */
85
+ public function add_meta_box() {
86
+ // Add the 'General Settings' meta box.
87
+ add_meta_box(
88
+ 'ai1ec-general-settings',
89
+ Ai1ec_I18n::_x( 'General Settings', 'meta box' ),
90
+ array( $this, 'display_meta_box' ),
91
+ $this->_registry->get( 'model.settings' )->get( 'settings_page' ),
92
+ 'left',
93
+ 'default'
94
+ );
95
+ // Add the 'Timely' meta box.
96
+ add_meta_box(
97
+ 'ai1ec-support',
98
+ Ai1ec_I18n::_x( 'Timely', 'meta box', AI1EC_PLUGIN_NAME ),
99
+ array( $this, 'support_meta_box' ),
100
+ $this->_registry->get( 'model.settings' )->get( 'settings_page' ),
101
+ 'right',
102
+ 'default'
103
+ );
104
+ }
105
 
106
+ /**
107
+ * Renders the Timely blog meta box
108
+ *
109
+ * @param mixed $object
110
+ * @param mixed $box
111
+ */
112
+ public function support_meta_box( $object, $box ) {
113
+ $newsItems = $this->_registry->get( 'news.feed' )->import_feed();
114
+ $loader = $this->_registry->get( 'theme.loader' );
115
+ $file = $loader->get_file(
116
+ 'box_support.php',
117
+ array(
118
+ 'news' => $newsItems,
119
+ ),
120
+ true
121
+ );
122
+ $file->render();
123
+ }
124
 
125
+ /* (non-PHPdoc)
126
+ * @see Ai1ec_View_Admin_Abstract::handle_post()
127
+ */
128
+ public function handle_post() {
129
+ }
130
 
131
+ /**
132
+ * Displays the meta box for the settings page.
133
+ *
134
+ * @param mixed $object
135
+ * @param mixed $box
136
+ */
137
+ public function display_meta_box( $object, $box ) {
138
+ $tabs = array(
139
+ 'viewing-events' => array(
140
+ 'name' => Ai1ec_I18n::__( 'Viewing Events' ),
141
+ ),
142
+ 'editing-events' => array(
143
+ 'name' => Ai1ec_I18n::__( 'Adding/Editing Events' ),
144
+ ),
145
+ 'advanced' => array(
146
+ 'name' => Ai1ec_I18n::__( 'Advanced' ),
147
+ 'items' => array(
148
+ 'advanced' => Ai1ec_I18n::__( 'Advanced Settings' ),
149
+ 'embedded-views' => Ai1ec_I18n::__( 'Shortcodes' ),
150
+ 'email' => Ai1ec_I18n::__( 'Email Templates' ),
151
+ 'apis' => Ai1ec_I18n::__( 'External Services' ),
152
+ 'cache' => Ai1ec_I18n::__( 'Cache Report' ),
153
+ )
154
+ ),
155
+ );
156
 
157
+ // let other extensions add tabs.
158
+ $tabs = apply_filters( 'ai1ec_add_setting_tabs', $tabs );
159
+ $settings = $this->_registry->get( 'model.settings' );
160
+ $plugin_settings = $settings->get_options();
161
+ $tabs = $this->_get_tabs_to_show( $plugin_settings, $tabs );
162
+ $loader = $this->_registry->get( 'theme.loader' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
+ $api = $this->_registry->get( 'model.api.api-registration' );
165
+ $signup_available = $api->is_api_sign_up_available();
166
+ $signed_to_api = $api->is_signed();
167
+ $ticketing_message = $api->get_sign_message();
168
+ $loader = $this->_registry->get( 'theme.loader' );
169
+ $account = $api->get_current_account();
170
+ $signup_args = array(
171
+ 'api_signed' => $signed_to_api,
172
+ 'signup_available' => $signup_available,
173
+ 'title' => Ai1ec_I18n::__(
174
+ 'Please, Sign In to Timely Network.'
175
+ ),
176
+ 'nonce' => array(
177
+ 'action' => 'ai1ec_api_ticketing_signup',
178
+ 'name' => 'ai1ec_api_ticketing_nonce',
179
+ 'referrer' => false,
180
+ ),
181
+ 'api_action' =>
182
+ '?controller=front&action=ai1ec_api_ticketing_signup&plugin=' .
183
+ AI1EC_PLUGIN_NAME,
184
+ 'required_text' => Ai1ec_I18n::__( 'This field is required.' ),
185
+ 'register_text' => Ai1ec_I18n::__( 'Register' ),
186
+ 'sign_in_text' => Ai1ec_I18n::__( 'Sign in' ),
187
+ 'signed_in_text' => Ai1ec_I18n::__(
188
+ 'You are signed in to <b>Timely Network</b> as ' . $account
189
+ ),
190
+ 'sign_out_text' => Ai1ec_I18n::__( 'Sign out' ),
191
+ 'can_sign_out' => apply_filters( 'ai1ec_api_can_sign_out', true ),
192
+ 'full_name_text' => Ai1ec_I18n::__( 'Full Name:' ),
193
+ 'hide_form_text' => Ai1ec_I18n::__( 'Hide form' ),
194
+ 'show_form_text' => Ai1ec_I18n::__( 'Show form' ),
195
+ 'email_text' => Ai1ec_I18n::__( 'Email:' ),
196
+ 'password_text' => Ai1ec_I18n::__( 'Password:' ),
197
+ 'confirm_password_text' => Ai1ec_I18n::__( 'Confirm Password:' ),
198
+ 'phone_number_text' => Ai1ec_I18n::__( 'Phone Number:' ),
199
+ 'terms_text' => Ai1ec_I18n::__(
200
+ 'I confirm that I have read, understand and agree with the <a href="https://time.ly/tos">terms of service</a>.'
201
+ ),
202
+ 'sign_out_warning' => Ai1ec_I18n::__(
203
+ '<h4>Attention Required:</h4>If you choose to sign-out of the API Timely Network this will close all the created tickets and remove user access to them. In this case, on the event page, users will see the status “Event closed”.'
204
+ ),
205
+ 'sign_out_cancel' => Ai1ec_I18n::__( 'Cancel' ),
206
+ 'sign_out_confirm' => Ai1ec_I18n::__( 'Sign Out' ),
207
+ 'sign_up_button_text' => Ai1ec_I18n::__( 'Sign Up' ),
208
+ 'sign_in_button_text' => Ai1ec_I18n::__( 'Sign In' ),
209
+ 'calendar_type_text' => Ai1ec_I18n::__( 'Calendar Type:' ),
210
+ 'calendar_types' => array(
211
+ 'tourism' => Ai1ec_I18n::__( 'Tourism' ),
212
+ 'media' => Ai1ec_I18n::__( 'Media' ),
213
+ 'community_hubs' => Ai1ec_I18n::__( 'Community Hubs' ),
214
+ 'education' => Ai1ec_I18n::__( 'Education' ),
215
+ 'venue_business' => Ai1ec_I18n::__( 'Venue/Business' ),
216
+ 'artist_performer' => Ai1ec_I18n::__( 'Artist/Performer' ),
217
+ 'church_spiritual' => Ai1ec_I18n::__( 'Church/Spiritual' ),
218
+ 'association_group' => Ai1ec_I18n::__( 'Association/Group' ),
219
+ 'other' => Ai1ec_I18n::__( 'Other' )
220
+ ),
221
+ );
222
+ $loader->get_file( 'setting/api-signup.twig', $signup_args, true )->render();
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
+ $args = array(
226
+ 'tabs' => $tabs,
227
+ 'content_class' => 'ai1ec-form-horizontal',
228
+ 'submit' => array(
229
+ 'id' => 'ai1ec_save_settings',
230
+ 'value' => '<i class="ai1ec-fa ai1ec-fa-save ai1ec-fa-fw"></i> ' .
231
+ Ai1ec_I18n::__( 'Save Settings' ),
232
+ 'args' => array(
233
+ 'class' => 'ai1ec-btn ai1ec-btn-primary ai1ec-btn-lg',
234
+ ),
235
+ ),
236
+ 'pre_tabs_markup' => sprintf(
237
+ '<div class="ai1ec-gzip-causes-js-failure">' .
238
+ Ai1ec_I18n::__(
239
+ 'If the form below is not working please follow <a href="%s">this link</a>.'
240
+ ) .
241
+ '</div>',
242
+ wp_nonce_url(
243
+ add_query_arg( 'ai1ec_disable_gzip_compression', '1' ),
244
+ 'ai1ec_disable_gzip_compression'
245
+ )
246
+ )
247
+ );
248
+
249
+ $file = $loader->get_file( 'setting/bootstrap_tabs.twig', $args, true );
250
+ $file->render();
251
+
252
+ }
253
+
254
+ /**
255
+ * Based on the plugin options, decides what tabs to render.
256
+ *
257
+ *
258
+ *
259
+ * @param array $plugin_settings
260
+ * @param array $tabs
261
+ *
262
+ * @return array
263
+ */
264
+ protected function _get_tabs_to_show( array $plugin_settings, array $tabs ) {
265
+ $index = 0;
266
+ $renderer = $this->_registry->get( 'html.element.setting-renderer' );
267
+ foreach ( $plugin_settings as $id => $setting ) {
268
+ // if the setting is shown
269
+ if ( isset ( $setting['renderer'] ) ) {
270
+ $tab_to_use = isset( $setting['renderer']['item'] ) ?
271
+ $setting['renderer']['item'] :
272
+ $setting['renderer']['tab'];
273
+ // check if it's the first one
274
+ if (
275
+ ! isset ( $tabs[$tab_to_use]['elements'] )
276
+ ) {
277
+ $tabs[$tab_to_use]['elements'] = array();
278
+ }
279
+ $setting['id'] = $id;
280
+ // render the settings
281
+ $weight = 10;
282
+ if ( isset( $setting['renderer']['weight'] ) ) {
283
+ $weight = (int)$setting['renderer']['weight'];
284
+ }
285
+ // NOTICE: do NOT change order of two first
286
+ // elements {weight,index}, otherwise sorting will fail.
287
+ $tabs[$tab_to_use]['elements'][] = array(
288
+ 'weight' => $weight,
289
+ 'index' => ++$index,
290
+ 'html' => $renderer->render( $setting ),
291
+ );
292
+ // if the settings has an item tab, set the item as active.
293
+ if ( isset( $setting['renderer']['item'] ) ) {
294
+ if ( ! isset( $tabs[$setting['renderer']['tab']]['items_active'][$setting['renderer']['item']] ) ) {
295
+ $tabs[$setting['renderer']['tab']]['items_active'][$setting['renderer']['item']] = true;
296
+ }
297
+ }
298
+ }
299
+ }
300
+ $tabs_to_display = array();
301
+ // now let's see what tabs to display.
302
+ foreach ( $tabs as $name => $tab ) {
303
+ // sort by weights
304
+ if ( isset( $tab['elements'] ) ) {
305
+ asort( $tab['elements'] );
306
+ }
307
+ // if a tab has more than one item.
308
+ if ( isset( $tab['items'] ) ) {
309
+ // if no item is active, nothing is shown
310
+ if ( empty( $tab['items_active'] ) ) {
311
+ continue;
312
+ }
313
+ // if only one item is active, do not use the dropdown
314
+ if ( count( $tab['items_active'] ) === 1 ) {
315
+ $name = key($tab['items_active']);
316
+ $tab['name'] = $tab['items'][$name];
317
+ unset ( $tab['items'] );
318
+ } else {
319
+ // check active items for the dropdown
320
+ foreach ( $tab['items'] as $item => $longname ) {
321
+ if ( ! isset( $tab['items_active'][$item] ) ) {
322
+ unset( $tab['items'][$item] );
323
+ }
324
+ }
325
+ }
326
+ // lets make a check to avoid overriding tabs
327
+ if ( ! isset( $tabs_to_display[$name] ) ) {
328
+ $tabs_to_display[$name] = $tab;
329
+ } else {
330
+ $tabs_to_display[$name]['elements'] = $tab['elements'];
331
+ }
332
+
333
+ } else {
334
+ // no items, just check for any element to display.
335
+ if ( isset( $tab['elements'] ) ) {
336
+ // lets make a check to avoid overriding tabs
337
+ if ( ! isset( $tabs_to_display[$name] ) ) {
338
+ $tabs_to_display[$name] = $tab;
339
+ } else {
340
+ $tabs_to_display[$name]['elements'] = $tab['elements'];
341
+ }
342
+ }
343
+ }
344
+ }
345
+ return $tabs_to_display;
346
+ }
347
 
348
  }
app/view/admin/theme-options.php CHANGED
@@ -11,214 +11,214 @@
11
  */
12
  class Ai1ec_View_Theme_Options extends Ai1ec_View_Admin_Abstract {
13
 
14
- /**
15
- * @var string The nonce action
16
- */
17
- const NONCE_ACTION = 'ai1ec_theme_options_save';
18
-
19
- /**
20
- * @var string The nonce name
21
- */
22
- const NONCE_NAME = 'ai1ec_theme_options_nonce';
23
-
24
- /**
25
- * @var string The id/name of the submit button.
26
- */
27
- const SUBMIT_ID = 'ai1ec_save_themes_options';
28
-
29
- /**
30
- * @var string The id/name of the Reset button.
31
- */
32
- const RESET_ID = 'ai1ec_reset_themes_options';
33
-
34
- /**
35
- * @var string
36
- */
37
- public $title;
38
-
39
- /**
40
- * @var string
41
- */
42
- public $meta_box_id;
43
-
44
- /**
45
- * Adds the page to the correct menu.
46
- */
47
- public function add_page() {
48
- $theme_options_page = add_submenu_page(
49
- AI1EC_ADMIN_BASE_URL,
50
- Ai1ec_I18n::__( 'Theme Options' ),
51
- Ai1ec_I18n::__( 'Theme Options' ),
52
- 'manage_ai1ec_options',
53
- AI1EC_PLUGIN_NAME . '-edit-css',
54
- array( $this, 'display_page' )
55
- );
56
- $settings = $this->_registry->get( 'model.settings' );
57
- if ( false !== $settings->get( 'less_variables_page' ) ) {
58
- // Make copy of Theme Options page at its old location.
59
- $submenu['themes.php'][] = array(
60
- Ai1ec_I18n::__( 'Calendar Theme Options' ),
61
- 'manage_ai1ec_options',
62
- AI1EC_THEME_OPTIONS_BASE_URL,
63
- );
64
- };
65
- $settings->set( 'less_variables_page', $theme_options_page );
66
- }
67
-
68
- /**
69
- * Add meta box for page.
70
- *
71
- * @wp_hook admin_init
72
- *
73
- * @return void
74
- */
75
- public function add_meta_box() {
76
- // Add the 'General Settings' meta box.
77
- add_meta_box(
78
- 'ai1ec-less-variables-tabs',
79
- Ai1ec_I18n::_x( 'Calendar Theme Options', 'meta box' ),
80
- array( $this, 'display_meta_box' ),
81
- $this->_registry->get( 'model.settings' )
82
- ->get( 'less_variables_page' ),
83
- 'left',
84
- 'default'
85
- );
86
- }
87
-
88
- /**
89
- * Display the page html
90
- */
91
- public function display_page() {
92
-
93
- $settings = $this->_registry->get( 'model.settings' );
94
-
95
- $args = array(
96
- 'title' => Ai1ec_I18n::__(
97
- 'Calendar Theme Options'
98
- ),
99
- 'nonce' => array(
100
- 'action' => self::NONCE_ACTION,
101
- 'name' => self::NONCE_NAME,
102
- 'referrer' => false,
103
- ),
104
- 'metabox' => array(
105
- 'screen' => $settings->get( 'themes_option_page' ),
106
- 'action' => 'left',
107
- 'object' => null
108
- ),
109
- 'action' =>
110
- '?controller=front&action=ai1ec_save_theme_options&plugin=' . AI1EC_PLUGIN_NAME
111
- );
112
-
113
- $frontend = $this->_registry->get( 'css.frontend' );
114
-
115
- $loader = $this->_registry->get( 'theme.loader' );
116
-
117
- $file = $loader->get_file( 'theme-options/page.twig', $args, true );
118
-
119
- return $file->render();
120
-
121
- }
122
-
123
- /**
124
- * Displays the meta box for the settings page.
125
- *
126
- * @param mixed $object
127
- * @param mixed $box
128
- */
129
- public function display_meta_box( $object, $box ) {
130
-
131
- $tabs = array(
132
- 'general' => array(
133
- 'name' => Ai1ec_I18n::__( 'General' ),
134
- ),
135
- 'table' => array(
136
- 'name' => Ai1ec_I18n::__( 'Tables' ),
137
- ),
138
- 'buttons' => array(
139
- 'name' => Ai1ec_I18n::__( 'Buttons' ),
140
- ),
141
- 'forms' => array(
142
- 'name' => Ai1ec_I18n::__( 'Forms' ),
143
- ),
144
- 'calendar' => array(
145
- 'name' => Ai1ec_I18n::__( 'Calendar general' ),
146
- ),
147
- 'month' => array(
148
- 'name' => Ai1ec_I18n::__( 'Month/week/day view' ),
149
- ),
150
- 'agenda' => array(
151
- 'name' => Ai1ec_I18n::__( 'Agenda view' ),
152
- ),
153
- );
154
-
155
- $tabs = apply_filters( 'ai1ec_less_variables_tabs', $tabs );
156
-
157
- $less_variables = $this->_registry
158
- ->get( 'less.lessphp' )->get_saved_variables();
159
- $tabs = $this->_get_tabs_to_show( $less_variables, $tabs );
160
-
161
- $loader = $this->_registry->get( 'theme.loader' );
162
- $args = array(
163
- 'stacked' => true,
164
- 'content_class' => 'ai1ec-form-horizontal',
165
- 'tabs' => $tabs,
166
- 'submit' => array(
167
- 'id' => self::SUBMIT_ID,
168
- 'value' => '<i class="ai1ec-fa ai1ec-fa-save ai1ec-fa-fw"></i> ' .
169
- Ai1ec_I18n::__( 'Save Options' ),
170
- 'args' => array(
171
- 'class' => 'ai1ec-btn ai1ec-btn-primary ai1ec-btn-lg',
172
- ),
173
- ),
174
- 'reset' => array(
175
- 'id' => self::RESET_ID,
176
- 'value' => '<i class="ai1ec-fa ai1ec-fa-undo ai1ec-fa-fw"></i> ' .
177
- Ai1ec_I18n::__( 'Reset to Defaults' ),
178
- 'args' => array(
179
- 'class' => 'ai1ec-btn ai1ec-btn-danger ai1ec-btn-lg',
180
- ),
181
- ),
182
- );
183
- $file = $loader->get_file( 'theme-options/bootstrap_tabs.twig', $args, true );
184
- $file->render();
185
-
186
- }
187
-
188
- /**
189
- * Return the theme options tabs
190
- *
191
- * @param array $less_variables
192
- * @param array $tabs list of tabs
193
- *
194
- * @return array the array of tabs to display
195
- */
196
- protected function _get_tabs_to_show( array $less_variables, array $tabs) {
197
-
198
- // Inizialize the array of tabs that will be added to the layout
199
- $bootstrap_tabs_to_add = array();
200
-
201
- foreach( $tabs as $id => $tab ){
202
- $tab['elements'] = array();
203
- $bootstrap_tabs_to_add[$id] = $tab;
204
- }
205
- foreach ( $less_variables as $variable_id => $variable_attributes ) {
206
- $variable_attributes['id'] = $variable_id;
207
- $renderable = $this->_registry->get(
208
- 'less.variable.' . $variable_attributes['type'],
209
- $variable_attributes
210
- );
211
- $bootstrap_tabs_to_add[$variable_attributes['tab']]['elements'][] = array(
212
- 'html' => $renderable->render()
213
- );
214
- }
215
- return $bootstrap_tabs_to_add;
216
- }
217
-
218
- /**
219
- * Handle post, likely to be deprecated to use commands.
220
- */
221
- public function handle_post() {
222
-
223
- }
224
  }
11
  */
12
  class Ai1ec_View_Theme_Options extends Ai1ec_View_Admin_Abstract {
13
 
14
+ /**
15
+ * @var string The nonce action
16
+ */
17
+ const NONCE_ACTION = 'ai1ec_theme_options_save';
18
+
19
+ /**
20
+ * @var string The nonce name
21
+ */
22
+ const NONCE_NAME = 'ai1ec_theme_options_nonce';
23
+
24
+ /**
25
+ * @var string The id/name of the submit button.
26
+ */
27
+ const SUBMIT_ID = 'ai1ec_save_themes_options';
28
+
29
+ /**
30
+ * @var string The id/name of the Reset button.
31
+ */
32
+ const RESET_ID = 'ai1ec_reset_themes_options';
33
+
34
+ /**
35
+ * @var string
36
+ */
37
+ public $title;
38
+
39
+ /**
40
+ * @var string
41
+ */
42
+ public $meta_box_id;
43
+
44
+ /**
45
+ * Adds the page to the correct menu.
46
+ */
47
+ public function add_page() {
48
+ $theme_options_page = add_submenu_page(
49
+ AI1EC_ADMIN_BASE_URL,
50
+ Ai1ec_I18n::__( 'Theme Options' ),
51
+ Ai1ec_I18n::__( 'Theme Options' ),
52
+ 'manage_ai1ec_options',
53
+ AI1EC_PLUGIN_NAME . '-edit-css',
54
+ array( $this, 'display_page' )
55
+ );
56
+ $settings = $this->_registry->get( 'model.settings' );
57
+ if ( false !== $settings->get( 'less_variables_page' ) ) {
58
+ // Make copy of Theme Options page at its old location.
59
+ $submenu['themes.php'][] = array(
60
+ Ai1ec_I18n::__( 'Calendar Theme Options' ),
61
+ 'manage_ai1ec_options',
62
+ AI1EC_THEME_OPTIONS_BASE_URL,
63
+ );
64
+ };
65
+ $settings->set( 'less_variables_page', $theme_options_page );
66
+ }
67
+
68
+ /**
69
+ * Add meta box for page.
70
+ *
71
+ * @wp_hook admin_init
72
+ *
73
+ * @return void
74
+ */
75
+ public function add_meta_box() {
76
+ // Add the 'General Settings' meta box.
77
+ add_meta_box(
78
+ 'ai1ec-less-variables-tabs',
79
+ Ai1ec_I18n::_x( 'Calendar Theme Options', 'meta box' ),
80
+ array( $this, 'display_meta_box' ),
81
+ $this->_registry->get( 'model.settings' )
82
+ ->get( 'less_variables_page' ),
83
+ 'left',
84
+ 'default'
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Display the page html
90
+ */
91
+ public function display_page() {
92
+
93
+ $settings = $this->_registry->get( 'model.settings' );
94
+
95
+ $args = array(
96
+ 'title' => Ai1ec_I18n::__(
97
+ 'Calendar Theme Options'
98
+ ),
99
+ 'nonce' => array(
100
+ 'action' => self::NONCE_ACTION,
101
+ 'name' => self::NONCE_NAME,
102
+ 'referrer' => false,
103
+ ),
104
+ 'metabox' => array(
105
+ 'screen' => $settings->get( 'themes_option_page' ),
106
+ 'action' => 'left',
107
+ 'object' => null
108
+ ),
109
+ 'action' =>
110
+ '?controller=front&action=ai1ec_save_theme_options&plugin=' . AI1EC_PLUGIN_NAME
111
+ );
112
+
113
+ $frontend = $this->_registry->get( 'css.frontend' );
114
+
115
+ $loader = $this->_registry->get( 'theme.loader' );
116
+
117
+ $file = $loader->get_file( 'theme-options/page.twig', $args, true );
118
+
119
+ return $file->render();
120
+
121
+ }
122
+
123
+ /**
124
+ * Displays the meta box for the settings page.
125
+ *
126
+ * @param mixed $object
127
+ * @param mixed $box
128
+ */
129
+ public function display_meta_box( $object, $box ) {
130
+
131
+ $tabs = array(
132
+ 'general' => array(
133
+ 'name' => Ai1ec_I18n::__( 'General' ),
134
+ ),
135
+ 'table' => array(
136
+ 'name' => Ai1ec_I18n::__( 'Tables' ),
137
+ ),
138
+ 'buttons' => array(
139
+ 'name' => Ai1ec_I18n::__( 'Buttons' ),
140
+ ),
141
+ 'forms' => array(
142
+ 'name' => Ai1ec_I18n::__( 'Forms' ),
143
+ ),
144
+ 'calendar' => array(
145
+ 'name' => Ai1ec_I18n::__( 'Calendar general' ),
146
+ ),
147
+ 'month' => array(
148
+ 'name' => Ai1ec_I18n::__( 'Month/week/day view' ),
149
+ ),
150
+ 'agenda' => array(
151
+ 'name' => Ai1ec_I18n::__( 'Agenda view' ),
152
+ ),
153
+ );
154
+
155
+ $tabs = apply_filters( 'ai1ec_less_variables_tabs', $tabs );
156
+
157
+ $less_variables = $this->_registry
158
+ ->get( 'less.lessphp' )->get_saved_variables();
159
+ $tabs = $this->_get_tabs_to_show( $less_variables, $tabs );
160
+
161
+ $loader = $this->_registry->get( 'theme.loader' );
162
+ $args = array(
163
+ 'stacked' => true,
164
+ 'content_class' => 'ai1ec-form-horizontal',
165
+ 'tabs' => $tabs,
166
+ 'submit' => array(
167
+ 'id' => self::SUBMIT_ID,
168
+ 'value' => '<i class="ai1ec-fa ai1ec-fa-save ai1ec-fa-fw"></i> ' .
169
+ Ai1ec_I18n::__( 'Save Options' ),
170
+ 'args' => array(
171
+ 'class' => 'ai1ec-btn ai1ec-btn-primary ai1ec-btn-lg',
172
+ ),
173
+ ),
174
+ 'reset' => array(
175
+ 'id' => self::RESET_ID,
176
+ 'value' => '<i class="ai1ec-fa ai1ec-fa-undo ai1ec-fa-fw"></i> ' .
177
+ Ai1ec_I18n::__( 'Reset to Defaults' ),
178
+ 'args' => array(
179
+ 'class' => 'ai1ec-btn ai1ec-btn-danger ai1ec-btn-lg',
180
+ ),
181
+ ),
182
+ );
183
+ $file = $loader->get_file( 'theme-options/bootstrap_tabs.twig', $args, true );
184
+ $file->render();
185
+
186
+ }
187
+
188
+ /**
189
+ * Return the theme options tabs
190
+ *
191
+ * @param array $less_variables
192
+ * @param array $tabs list of tabs
193
+ *
194
+ * @return array the array of tabs to display
195
+ */
196
+ protected function _get_tabs_to_show( array $less_variables, array $tabs) {
197
+
198
+ // Inizialize the array of tabs that will be added to the layout
199
+ $bootstrap_tabs_to_add = array();
200
+
201
+ foreach( $tabs as $id => $tab ){
202
+ $tab['elements'] = array();
203
+ $bootstrap_tabs_to_add[$id] = $tab;
204
+ }
205
+ foreach ( $less_variables as $variable_id => $variable_attributes ) {
206
+ $variable_attributes['id'] = $variable_id;
207
+ $renderable = $this->_registry->get(
208
+ 'less.variable.' . $variable_attributes['type'],
209
+ $variable_attributes
210
+ );
211
+ $bootstrap_tabs_to_add[$variable_attributes['tab']]['elements'][] = array(
212
+ 'html' => $renderable->render()
213
+ );
214
+ }
215
+ return $bootstrap_tabs_to_add;
216
+ }
217
+
218
+ /**
219
+ * Handle post, likely to be deprecated to use commands.
220
+ */
221
+ public function handle_post() {
222
+
223
+ }
224
  }
app/view/admin/theme-switching.php CHANGED
@@ -11,58 +11,58 @@
11
  */
12
  class Ai1ec_View_Admin_Theme_Switching extends Ai1ec_View_Admin_Abstract {
13
 
14
- /* (non-PHPdoc)
15
- * @see Ai1ec_View_Admin_Abstract::display_page()
16
- */
17
- public function display_page() {
18
- global $ct;
19
- // defaults
20
- $activated = isset( $_GET['activated'] ) ? true : false;
21
- $deleted = false;
22
 
23
- $_list_table = $this->_registry->get( 'theme.list' );
24
- $_list_table->prepare_items();
25
 
26
- $args = array(
27
- 'activated' => $activated,
28
- 'deleted' => $deleted,
29
- 'ct' => $ct,
30
- 'wp_list_table' => $_list_table,
31
- 'page_title' => Ai1ec_I18n::__(
32
- 'All-in-One Event Calendar: Themes'
33
- ),
34
- );
35
 
36
- add_thickbox();
37
- wp_enqueue_script( 'theme-preview' );
38
- $loader = $this->_registry->get( 'theme.loader' );
39
 
40
- $file = $loader->get_file( 'themes.php', $args, true );
41
- return $file->render();
42
- }
43
 
44
- /* (non-PHPdoc)
45
- * @see Ai1ec_View_Admin_Abstract::add_page()
46
- */
47
- public function add_page() {
48
- global $submenu;
49
- // ===============
50
- // = Themes Page =
51
- // ===============
52
- $themes_page = add_submenu_page(
53
- AI1EC_ADMIN_BASE_URL,
54
- Ai1ec_I18n::__( 'Calendar Themes' ),
55
- Ai1ec_I18n::__( 'Calendar Themes' ),
56
- 'switch_ai1ec_themes',
57
- AI1EC_PLUGIN_NAME . '-themes',
58
- array( $this, 'display_page' )
59
- );
60
- }
61
 
62
- public function add_meta_box() {
63
- }
64
 
65
- public function handle_post() {
66
- }
67
 
68
  }
11
  */
12
  class Ai1ec_View_Admin_Theme_Switching extends Ai1ec_View_Admin_Abstract {
13
 
14
+ /* (non-PHPdoc)
15
+ * @see Ai1ec_View_Admin_Abstract::display_page()
16
+ */
17
+ public function display_page() {
18
+ global $ct;
19
+ // defaults
20
+ $activated = isset( $_GET['activated'] ) ? true : false;
21
+ $deleted = false;
22
 
23
+ $_list_table = $this->_registry->get( 'theme.list' );
24
+ $_list_table->prepare_items();
25
 
26
+ $args = array(
27
+ 'activated' => $activated,
28
+ 'deleted' => $deleted,
29
+ 'ct' => $ct,
30
+ 'wp_list_table' => $_list_table,
31
+ 'page_title' => Ai1ec_I18n::__(
32
+ 'All-in-One Event Calendar: Themes'
33
+ ),
34
+ );
35
 
36
+ add_thickbox();
37
+ wp_enqueue_script( 'theme-preview' );
38
+ $loader = $this->_registry->get( 'theme.loader' );
39
 
40
+ $file = $loader->get_file( 'themes.php', $args, true );
41
+ return $file->render();
42
+ }
43
 
44
+ /* (non-PHPdoc)
45
+ * @see Ai1ec_View_Admin_Abstract::add_page()
46
+ */
47
+ public function add_page() {
48
+ global $submenu;
49
+ // ===============
50
+ // = Themes Page =
51
+ // ===============
52
+ $themes_page = add_submenu_page(
53
+ AI1EC_ADMIN_BASE_URL,
54
+ Ai1ec_I18n::__( 'Calendar Themes' ),
55
+ Ai1ec_I18n::__( 'Calendar Themes' ),
56
+ 'switch_ai1ec_themes',
57
+ AI1EC_PLUGIN_NAME . '-themes',
58
+ array( $this, 'display_page' )
59
+ );
60
+ }
61
 
62
+ public function add_meta_box() {
63
+ }
64
 
65
+ public function handle_post() {
66
+ }
67
 
68
  }
app/view/admin/tickets.php CHANGED
@@ -11,184 +11,184 @@
11
  */
12
  class Ai1ec_View_Tickets extends Ai1ec_View_Admin_Abstract {
13
 
14
- /**
15
- * @var string The nonce action
16
- */
17
- const NONCE_ACTION = 'ai1ec_api_ticketing_signup';
18
-
19
- /**
20
- * @var string The nonce name
21
- */
22
- const NONCE_NAME = 'ai1ec_api_ticketing_nonce';
23
-
24
- /**
25
- * @var string The id/name of the submit button.
26
- */
27
- const SUBMIT_ID = 'ai1ec_api_ticketing_signup';
28
-
29
- /**
30
- * Adds the page to the correct menu.
31
- */
32
- public function add_page() {
33
- add_submenu_page(
34
- AI1EC_ADMIN_BASE_URL,
35
- __( 'Ticketing', AI1EC_PLUGIN_NAME ),
36
- __( 'Ticketing', AI1EC_PLUGIN_NAME ),
37
- 'manage_ai1ec_feeds',
38
- AI1EC_PLUGIN_NAME . '-tickets',
39
- array( $this, 'display_page' )
40
- );
41
- }
42
-
43
- /**
44
- * Add meta box for page.
45
- *
46
- * @wp_hook admin_init
47
- *
48
- * @return void
49
- */
50
- public function add_meta_box() {}
51
-
52
- /**
53
- * Display the page html
54
- */
55
- public function display_page() {
56
-
57
- $signed_to_api = $this->_api_registration->is_signed();
58
- $signup_available = $this->_api_registration->is_api_sign_up_available();
59
- $ticketing_available = $this->_api_registration->is_ticket_available();
60
- $ticketing_enabled = $this->_api_registration->has_subscription_active( Ai1ec_Api_Features::CODE_TICKETING );
61
- $ticketing_message = $this->_api_registration->get_sign_message();
62
- $loader = $this->_registry->get( 'theme.loader' );
63
-
64
- if ( ! $signed_to_api ) {
65
-
66
- if ( false === ai1ec_is_blank( $ticketing_message ) ) {
67
- $this->_api_registration->clear_sign_message();
68
- }
69
-
70
- $args = array(
71
- 'title' => Ai1ec_I18n::__(
72
- 'Time.ly Ticketing'
73
- ),
74
- 'sign_up_text' => 'Please, <a href="edit.php?post_type=ai1ec_event&page=all-in-one-event-calendar-settings">Sign Up for a Timely Network account</a> to use Ticketing or Import Feeds.',
75
- 'signup_form' => Ai1ec_I18n::__( 'You need to sign up for a Timely Network account in order to use Ticketing or Import Feeds<br /><br />' ) .
76
- (
77
- $signup_available
78
- ? Ai1ec_I18n::__( '<a href="edit.php?post_type=ai1ec_event&page=all-in-one-event-calendar-settings" class="ai1ec-btn ai1ec-btn-primary ai1ec-btn-lg">Sign In to Timely Network</a>' )
79
- : Ai1ec_I18n::__( '<b>Signing up for a Timely Network account is currently unavailable. Please, try again later.</b>' )
80
- )
81
-
82
- );
83
- $file = $loader->get_file( 'ticketing/signup.twig', $args, true );
84
- } elseif ( ! $ticketing_available ) {
85
- $args = array(
86
- 'title' => Ai1ec_I18n::__(
87
- 'Time.ly Ticketing'
88
- ),
89
- 'sign_up_text' => '',
90
- 'signup_form' => 'Ticketing is currently not available for this website. Please, try again later.'
91
-
92
- );
93
- $file = $loader->get_file( 'ticketing/signup.twig', $args, true );
94
- } elseif ( ! $ticketing_enabled ) {
95
- $args = array(
96
- 'title' => Ai1ec_I18n::__(
97
- 'Time.ly Ticketing'
98
- ),
99
- 'sign_up_text' => '',
100
- 'signup_form' => 'Timely Ticketing saves time & money. Create ticketing/registration right here and now. You do not pay any ticketing fees (other than regular PayPal transaction costs). Create as many ticketing/registration as you\'d like.<br /><br />Ticketing feature is not enabled for this website. Please sign up for Ticketing plan <a href="https://time.ly/tickets-existing-users/" target="_blank">here</a>.'
101
- );
102
- $file = $loader->get_file( 'ticketing/signup.twig', $args, true );
103
- } else {
104
- $response = $this->_api_registration->get_payment_preferences();
105
- $purchases = $this->_api_registration->get_purchases();
106
- $args = array(
107
- 'title' => Ai1ec_I18n::__(
108
- 'Time.ly Ticketing'
109
- ),
110
- 'settings_text' => Ai1ec_I18n::__( 'Settings' ),
111
- 'sales_text' => Ai1ec_I18n::__( 'Sales' ),
112
- 'select_payment_text' => Ai1ec_I18n::__( 'Please provide your PayPal details.' ),
113
- 'cheque_text' => Ai1ec_I18n::__( 'Cheque' ),
114
- 'paypal_text' => Ai1ec_I18n::__( 'PayPal' ),
115
- 'currency_text' => Ai1ec_I18n::__( 'Preferred currency for tickets:' ),
116
- 'required_text' => Ai1ec_I18n::__( 'This field is required.' ),
117
- 'save_changes_text' => Ai1ec_I18n::__( 'Save Changes' ),
118
- 'date_text' => Ai1ec_I18n::__( 'Date' ),
119
- 'event_text' => Ai1ec_I18n::__( 'Event' ),
120
- 'purchaser_text' => Ai1ec_I18n::__( 'Purchaser' ),
121
- 'tickets_text' => Ai1ec_I18n::__( 'Tickets' ),
122
- 'email_text' => Ai1ec_I18n::__( 'Email' ),
123
- 'status_text' => Ai1ec_I18n::__( 'Status' ),
124
- 'total_text' => Ai1ec_I18n::__( 'Total' ),
125
- 'sign_out_button_text' => Ai1ec_I18n::__( 'Sign Out' ),
126
- 'payment_method' => $response->payment_method,
127
- 'paypal_email' => $response->paypal_email,
128
- 'first_name' => $response->first_name,
129
- 'last_name' => $response->last_name,
130
- 'currency' => $response->currency,
131
- 'nonce' => array(
132
- 'action' => self::NONCE_ACTION,
133
- 'name' => self::NONCE_NAME,
134
- 'referrer' => false,
135
- ),
136
- 'action' =>
137
- '?controller=front&action=ai1ec_api_ticketing_signup&plugin=' .
138
- AI1EC_PLUGIN_NAME,
139
- 'purchases' => $purchases,
140
- 'paypal_currencies' => array (
141
- array( 'description' => Ai1ec_I18n::__( 'United States Dollar' ), 'code' => 'USD' ),
142
- array( 'description' => Ai1ec_I18n::__( 'Canadian Dollar' ), 'code' => 'CAD' ),
143
- array( 'description' => Ai1ec_I18n::__( 'Australian Dollar' ), 'code' => 'AUD' ),
144
- array( 'description' => Ai1ec_I18n::__( 'Brazilian Real' ), 'code' => 'BRL', 'note' => Ai1ec_I18n::__( 'Note: This currency is supported as a payment currency and a currency balance for in-country PayPal accounts only.' ) ),
145
- array( 'description' => Ai1ec_I18n::__( 'Czech Koruna' ), 'code' => 'CZK' ),
146
- array( 'description' => Ai1ec_I18n::__( 'Danish Krone' ), 'code' => 'DKK' ),
147
- array( 'description' => Ai1ec_I18n::__( 'Euro' ), 'code' => 'EUR' ),
148
- array( 'description' => Ai1ec_I18n::__( 'Hong Kong Dollar' ), 'code' => 'HKD' ),
149
- array( 'description' => Ai1ec_I18n::__( 'Hungarian Forint' ), 'code' => 'HUF', 'note' => Ai1ec_I18n::__( 'Note: Decimal amounts are not supported for this currency. Passing a decimal amount will throw an error.' ) ),
150
- array( 'description' => Ai1ec_I18n::__( 'Israeli New Sheqel' ), 'code' => 'ILS' ),
151
- array( 'description' => Ai1ec_I18n::__( 'Japanese Yen' ), 'code' => 'JPY', 'note' => Ai1ec_I18n::__( 'Note: This currency does not support decimals. Passing a decimal amount will throw an error. 1,000,000' ) ),
152
- array( 'description' => Ai1ec_I18n::__( 'Malaysian Ringgit' ), 'code' => 'MYR', 'note' => Ai1ec_I18n::__( 'Note: This currency is supported as a payment currency and a currency balance for in-country PayPal accounts only.' ) ),
153
- array( 'description' => Ai1ec_I18n::__( 'Mexican Peso' ), 'code' => 'MXN' ),
154
- array( 'description' => Ai1ec_I18n::__( 'Norwegian Krone' ), 'code' => 'NOK' ),
155
- array( 'description' => Ai1ec_I18n::__( 'New Zealand Dollar' ), 'code' => 'NZD' ),
156
- array( 'description' => Ai1ec_I18n::__( 'Philippine Peso' ), 'code' => 'PHP' ),
157
- array( 'description' => Ai1ec_I18n::__( 'Polish Zloty' ), 'code' => 'PLN' ),
158
- array( 'description' => Ai1ec_I18n::__( 'Pound Sterling' ), 'code' => 'GBP' ),
159
- array( 'description' => Ai1ec_I18n::__( 'Russian Ruble' ), 'code' => 'RUB', 'note' => Ai1ec_I18n::__( 'For in-border payments (payments made within Russia), the Russian Ruble is the only accepted currency. If you use another currency for in-border payments, the transaction will fail' ) ),
160
- array( 'description' => Ai1ec_I18n::__( 'Singapore Dollar' ), 'code' => 'SGD' ),
161
- array( 'description' => Ai1ec_I18n::__( 'Swedish Krona' ), 'code' => 'SEK' ),
162
- array( 'description' => Ai1ec_I18n::__( 'Swiss Franc' ), 'code' => 'CHF' ),
163
- array( 'description' => Ai1ec_I18n::__( 'Taiwan New Dollar' ), 'code' => 'TWD', 'note' => Ai1ec_I18n::__( 'Note: Decimal amounts are not supported for this currency. Passing a decimal amount will throw an error.' ) ),
164
- array( 'description' => Ai1ec_I18n::__( 'Thai Baht' ), 'code' => 'THB' ),
165
- )
166
- );
167
- $file = $loader->get_file( 'ticketing/manage.twig', $args, true );
168
- }
169
-
170
- $this->_registry->get( 'css.admin' )->admin_enqueue_scripts(
171
- 'ai1ec_event_page_all-in-one-event-calendar-settings'
172
- );
173
- $this->_registry->get( 'css.admin' )->process_enqueue(
174
- array(
175
- array( 'style', 'ticketing.css', ),
176
- )
177
- );
178
- if ( isset( $_POST['ai1ec_save_settings'] ) ) {
179
- $response = $this->_api_registration->save_payment_preferences();
180
-
181
- // this redirect makes sure that the error messages appear on the screen
182
- if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
183
- header( "Location: " . $_SERVER['HTTP_REFERER'] );
184
- }
185
- }
186
- return $file->render();
187
- }
188
-
189
- /**
190
- * Handle post, likely to be deprecated to use commands.
191
- */
192
- public function handle_post(){}
193
 
194
  }
11
  */
12
  class Ai1ec_View_Tickets extends Ai1ec_View_Admin_Abstract {
13
 
14
+ /**
15
+ * @var string The nonce action
16
+ */
17
+ const NONCE_ACTION = 'ai1ec_api_ticketing_signup';
18
+
19
+ /**
20
+ * @var string The nonce name
21
+ */
22
+ const NONCE_NAME = 'ai1ec_api_ticketing_nonce';
23
+
24
+ /**
25
+ * @var string The id/name of the submit button.
26
+ */
27
+ const SUBMIT_ID = 'ai1ec_api_ticketing_signup';
28
+
29
+ /**
30
+ * Adds the page to the correct menu.
31
+ */
32
+ public function add_page() {
33
+ add_submenu_page(
34
+ AI1EC_ADMIN_BASE_URL,
35
+ __( 'Ticketing', AI1EC_PLUGIN_NAME ),
36
+ __( 'Ticketing', AI1EC_PLUGIN_NAME ),
37
+ 'manage_ai1ec_feeds',
38
+ AI1EC_PLUGIN_NAME . '-tickets',
39
+ array( $this, 'display_page' )
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Add meta box for page.
45
+ *
46
+ * @wp_hook admin_init
47
+ *
48
+ * @return void
49
+ */
50
+ public function add_meta_box() {}
51
+
52
+ /**
53
+ * Display the page html
54
+ */
55
+ public function display_page() {
56
+
57
+ $signed_to_api = $this->_api_registration->is_signed();
58
+ $signup_available = $this->_api_registration->is_api_sign_up_available();
59
+ $ticketing_available = $this->_api_registration->is_ticket_available();
60
+ $ticketing_enabled = $this->_api_registration->has_subscription_active( Ai1ec_Api_Features::CODE_TICKETING );
61
+ $ticketing_message = $this->_api_registration->get_sign_message();
62
+ $loader = $this->_registry->get( 'theme.loader' );
63
+
64
+ if ( ! $signed_to_api ) {
65
+
66
+ if ( false === ai1ec_is_blank( $ticketing_message ) ) {
67
+ $this->_api_registration->clear_sign_message();
68
+ }
69
+
70
+ $args = array(
71
+ 'title' => Ai1ec_I18n::__(
72
+ 'Time.ly Ticketing'
73
+ ),
74
+ 'sign_up_text' => 'Please, <a href="edit.php?post_type=ai1ec_event&page=all-in-one-event-calendar-settings">Sign Up for a Timely Network account</a> to use Ticketing or Import Feeds.',
75
+ 'signup_form' => Ai1ec_I18n::__( 'You need to sign up for a Timely Network account in order to use Ticketing or Import Feeds<br /><br />' ) .
76
+ (
77
+ $signup_available
78
+ ? Ai1ec_I18n::__( '<a href="edit.php?post_type=ai1ec_event&page=all-in-one-event-calendar-settings" class="ai1ec-btn ai1ec-btn-primary ai1ec-btn-lg">Sign In to Timely Network</a>' )
79
+ : Ai1ec_I18n::__( '<b>Signing up for a Timely Network account is currently unavailable. Please, try again later.</b>' )
80
+ )
81
+
82
+ );
83
+ $file = $loader->get_file( 'ticketing/signup.twig', $args, true );
84
+ } elseif ( ! $ticketing_available ) {
85
+ $args = array(
86
+ 'title' => Ai1ec_I18n::__(
87
+ 'Time.ly Ticketing'
88
+ ),
89
+ 'sign_up_text' => '',
90
+ 'signup_form' => 'Ticketing is currently not available for this website. Please, try again later.'
91
+
92
+ );
93
+ $file = $loader->get_file( 'ticketing/signup.twig', $args, true );
94
+ } elseif ( ! $ticketing_enabled ) {
95
+ $args = array(
96
+ 'title' => Ai1ec_I18n::__(
97
+ 'Time.ly Ticketing'
98
+ ),
99
+ 'sign_up_text' => '',
100
+ 'signup_form' => 'Timely Ticketing saves time & money. Create ticketing/registration right here and now. You do not pay any ticketing fees (other than regular PayPal transaction costs). Create as many ticketing/registration as you\'d like.<br /><br />Ticketing feature is not enabled for this website. Please sign up for Ticketing plan <a href="https://time.ly/tickets-existing-users/" target="_blank">here</a>.'
101
+ );
102
+ $file = $loader->get_file( 'ticketing/signup.twig', $args, true );
103
+ } else {
104
+ $response = $this->_api_registration->get_payment_preferences();
105
+ $purchases = $this->_api_registration->get_purchases();
106
+ $args = array(
107
+ 'title' => Ai1ec_I18n::__(
108
+ 'Time.ly Ticketing'
109
+ ),
110
+ 'settings_text' => Ai1ec_I18n::__( 'Settings' ),
111
+ 'sales_text' => Ai1ec_I18n::__( 'Sales' ),
112
+ 'select_payment_text' => Ai1ec_I18n::__( 'Please provide your PayPal details.' ),
113
+ 'cheque_text' => Ai1ec_I18n::__( 'Cheque' ),
114
+ 'paypal_text' => Ai1ec_I18n::__( 'PayPal' ),
115
+ 'currency_text' => Ai1ec_I18n::__( 'Preferred currency for tickets:' ),
116
+ 'required_text' => Ai1ec_I18n::__( 'This field is required.' ),
117
+ 'save_changes_text' => Ai1ec_I18n::__( 'Save Changes' ),
118
+ 'date_text' => Ai1ec_I18n::__( 'Date' ),
119
+ 'event_text' => Ai1ec_I18n::__( 'Event' ),
120
+ 'purchaser_text' => Ai1ec_I18n::__( 'Purchaser' ),
121
+ 'tickets_text' => Ai1ec_I18n::__( 'Tickets' ),
122
+ 'email_text' => Ai1ec_I18n::__( 'Email' ),
123
+ 'status_text' => Ai1ec_I18n::__( 'Status' ),
124
+ 'total_text' => Ai1ec_I18n::__( 'Total' ),
125
+ 'sign_out_button_text' => Ai1ec_I18n::__( 'Sign Out' ),
126
+ 'payment_method' => $response->payment_method,
127
+ 'paypal_email' => $response->paypal_email,
128
+ 'first_name' => $response->first_name,
129
+ 'last_name' => $response->last_name,
130
+ 'currency' => $response->currency,
131
+ 'nonce' => array(
132
+ 'action' => self::NONCE_ACTION,
133
+ 'name' => self::NONCE_NAME,
134
+ 'referrer' => false,
135
+ ),
136
+ 'action' =>
137
+ '?controller=front&action=ai1ec_api_ticketing_signup&plugin=' .
138
+ AI1EC_PLUGIN_NAME,
139
+ 'purchases' => $purchases,
140
+ 'paypal_currencies' => array (
141
+ array( 'description' => Ai1ec_I18n::__( 'United States Dollar' ), 'code' => 'USD' ),
142
+ array( 'description' => Ai1ec_I18n::__( 'Canadian Dollar' ), 'code' => 'CAD' ),
143
+ array( 'description' => Ai1ec_I18n::__( 'Australian Dollar' ), 'code' => 'AUD' ),
144
+ array( 'description' => Ai1ec_I18n::__( 'Brazilian Real' ), 'code' => 'BRL', 'note' => Ai1ec_I18n::__( 'Note: This currency is supported as a payment currency and a currency balance for in-country PayPal accounts only.' ) ),
145
+ array( 'description' => Ai1ec_I18n::__( 'Czech Koruna' ), 'code' => 'CZK' ),
146
+ array( 'description' => Ai1ec_I18n::__( 'Danish Krone' ), 'code' => 'DKK' ),
147
+ array( 'description' => Ai1ec_I18n::__( 'Euro' ), 'code' => 'EUR' ),
148
+ array( 'description' => Ai1ec_I18n::__( 'Hong Kong Dollar' ), 'code' => 'HKD' ),
149
+ array( 'description' => Ai1ec_I18n::__( 'Hungarian Forint' ), 'code' => 'HUF', 'note' => Ai1ec_I18n::__( 'Note: Decimal amounts are not supported for this currency. Passing a decimal amount will throw an error.' ) ),
150
+ array( 'description' => Ai1ec_I18n::__( 'Israeli New Sheqel' ), 'code' => 'ILS' ),
151
+ array( 'description' => Ai1ec_I18n::__( 'Japanese Yen' ), 'code' => 'JPY', 'note' => Ai1ec_I18n::__( 'Note: This currency does not support decimals. Passing a decimal amount will throw an error. 1,000,000' ) ),
152
+ array( 'description' => Ai1ec_I18n::__( 'Malaysian Ringgit' ), 'code' => 'MYR', 'note' => Ai1ec_I18n::__( 'Note: This currency is supported as a payment currency and a currency balance for in-country PayPal accounts only.' ) ),
153
+ array( 'description' => Ai1ec_I18n::__( 'Mexican Peso' ), 'code' => 'MXN' ),
154
+ array( 'description' => Ai1ec_I18n::__( 'Norwegian Krone' ), 'code' => 'NOK' ),
155
+ array( 'description' => Ai1ec_I18n::__( 'New Zealand Dollar' ), 'code' => 'NZD' ),
156
+ array( 'description' => Ai1ec_I18n::__( 'Philippine Peso' ), 'code' => 'PHP' ),
157
+ array( 'description' => Ai1ec_I18n::__( 'Polish Zloty' ), 'code' => 'PLN' ),
158
+ array( 'description' => Ai1ec_I18n::__( 'Pound Sterling' ), 'code' => 'GBP' ),
159
+ array( 'description' => Ai1ec_I18n::__( 'Russian Ruble' ), 'code' => 'RUB', 'note' => Ai1ec_I18n::__( 'For in-border payments (payments made within Russia), the Russian Ruble is the only accepted currency. If you use another currency for in-border payments, the transaction will fail' ) ),
160
+ array( 'description' => Ai1ec_I18n::__( 'Singapore Dollar' ), 'code' => 'SGD' ),
161
+ array( 'description' => Ai1ec_I18n::__( 'Swedish Krona' ), 'code' => 'SEK' ),
162
+ array( 'description' => Ai1ec_I18n::__( 'Swiss Franc' ), 'code' => 'CHF' ),
163
+ array( 'description' => Ai1ec_I18n::__( 'Taiwan New Dollar' ), 'code' => 'TWD', 'note' => Ai1ec_I18n::__( 'Note: Decimal amounts are not supported for this currency. Passing a decimal amount will throw an error.' ) ),
164
+ array( 'description' => Ai1ec_I18n::__( 'Thai Baht' ), 'code' => 'THB' ),
165
+ )
166
+ );
167
+ $file = $loader->get_file( 'ticketing/manage.twig', $args, true );
168
+ }
169
+
170
+ $this->_registry->get( 'css.admin' )->admin_enqueue_scripts(
171
+ 'ai1ec_event_page_all-in-one-event-calendar-settings'
172
+ );
173
+ $this->_registry->get( 'css.admin' )->process_enqueue(
174
+ array(
175
+ array( 'style', 'ticketing.css', ),
176
+ )
177
+ );
178
+ if ( isset( $_POST['ai1ec_save_settings'] ) ) {
179
+ $response = $this->_api_registration->save_payment_preferences();
180
+
181
+ // this redirect makes sure that the error messages appear on the screen
182
+ if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
183
+ header( "Location: " . $_SERVER['HTTP_REFERER'] );
184
+ }
185
+ }
186
+ return $file->render();
187
+ }
188
+
189
+ /**
190
+ * Handle post, likely to be deprecated to use commands.
191
+ */
192
+ public function handle_post(){}
193
 
194
  }
app/view/admin/widget-creator.php CHANGED
@@ -11,139 +11,139 @@
11
  */
12
  class Ai1ec_View_Widget_Creator extends Ai1ec_View_Admin_Abstract {
13
 
14
- /**
15
- * Adds page to the menu.
16
- *
17
- * @wp_hook admin_menu
18
- *
19
- * @return void
20
- */
21
- public function add_page() {
22
- add_submenu_page(
23
- AI1EC_ADMIN_BASE_URL,
24
- __( 'Widget Creator', AI1EC_PLUGIN_NAME ),
25
- __( 'Widget Creator', AI1EC_PLUGIN_NAME ),
26
- 'manage_ai1ec_feeds',
27
- AI1EC_PLUGIN_NAME . '-widget-creator',
28
- array( $this, 'display_page' )
29
- );
30
- }
31
 
32
- /**
33
- * Display this plugin's feeds page in the admin.
34
- *
35
- * @return void
36
- */
37
- public function display_page() {
38
- $this->_registry->get( 'css.admin' )->admin_enqueue_scripts(
39
- 'ai1ec_event_page_all-in-one-event-calendar-settings'
40
- );
41
- $this->_registry->get( 'css.admin' )->process_enqueue(
42
- array(
43
- array( 'style', 'super-widget.css', ),
44
- )
45
- );
46
- $args = array(
47
- 'title' => __(
48
- 'Widget Creator',
49
- AI1EC_PLUGIN_NAME
50
- ),
51
- 'metabox' => array(
52
- 'screen' => 'ai1ec-super-widget',
53
- 'action' => 'left',
54
- 'object' => null
55
- ),
56
- );
57
- $loader = $this->_registry->get( 'theme.loader' );
58
- $file = $loader->get_file( 'widget-creator/page.twig', $args, true );
59
- $file->render();
60
- }
61
 
62
- /* (non-PHPdoc)
63
- * @see Ai1ec_View_Admin_Settings::handle_post()
64
- */
65
- public function handle_post() {
66
- }
67
 
68
- /* (non-PHPdoc)
69
- * @see Ai1ec_View_Admin_Settings::add_meta_box()
70
- */
71
- public function add_meta_box() {
72
- add_meta_box(
73
- 'ai1ec-widget-creator',
74
- _x( 'Widget Creator', 'meta box', AI1EC_PLUGIN_NAME ),
75
- array( $this, 'display_meta_box' ),
76
- 'ai1ec-super-widget',
77
- 'left',
78
- 'default'
79
- );
80
- }
81
 
82
- /**
83
- * Renders the settings
84
- *
85
- * @param array $settings
86
- *
87
- * @return array
88
- */
89
- public function get_html_from_settings( array $settings ) {
90
- $named_elements = array();
91
- foreach ( $settings as $id => $setting ) {
92
- $named_elements[$id] = $this->_registry->get(
93
- 'html.element.setting.' . $setting['renderer']['class'],
94
- array(
95
- 'id' => $id,
96
- 'value' => $setting['value'],
97
- 'renderer' => $setting['renderer'],
98
- )
99
- )->render();
100
- }
101
- return $named_elements;
102
- }
103
 
104
- /* (non-PHPdoc)
105
- * @see Ai1ec_View_Admin_Settings::display_meta_box()
106
- */
107
- public function display_meta_box( $object, $box ) {
108
- $widgets = $this->_registry->get( 'controller.javascript-widget' )
109
- ->get_widgets();
110
- // this is just for the Super Widget which doesn't fully implement Ai1ec_Embeddable
111
- $widgets = apply_filters( 'ai1ec_widget_creators_widgets', $widgets );
112
- $tabs = array();
113
- foreach ( $widgets as $widget_id => $widget_class ) {
114
- $widget = $this->_registry->get( $widget_class );
115
- $tabs[$widget_id] = array(
116
- 'name' => $widget->get_name(),
117
- 'icon' => $widget->get_icon(),
118
- 'requirements' => $widget->check_requirements(),
119
- 'elements' => $this->get_html_from_settings(
120
- $widget->get_configurable_for_widget_creation()
121
- )
122
- );
123
- }
124
 
125
- $loader = $this->_registry->get( 'theme.loader' );
126
- $file = $loader->get_file(
127
- 'widget-creator/super-widget-contents.twig',
128
- array(
129
- 'tabs' => $tabs,
130
- 'siteurl' => trailingslashit( preg_replace( '/^.*?:/', '', ai1ec_get_site_url() ) ),
131
- 'text_common_info' => Ai1ec_I18n::__( 'Use this tool to generate code snippets you can add to <strong>an external website</strong> to embed new calendars and widgets.' ),
132
- 'text_alert' => Ai1ec_I18n::__( '<h4>Attention!</h4><p>These widgets are designed to be embedded in <strong>external sites only</strong> and may cause conflicts if used within the same WordPress site.</p>' ),
133
- 'text_alternatives' => sprintf(
134
- Ai1ec_I18n::__( '<p>Use <a href="%s"><strong>Appearance</strong> &gt; <strong>Widgets</strong></a> to add event widgets to your WordPress site as you would any other widget, or use <a href="%s" target="_blank">shortcodes</a> to embed the full calendar.</strong></p>' ),
135
- admin_url( 'widgets.php' ),
136
- 'https://time.ly/document/user-guide/using-calendar/display-multiple-calendars-site/'
137
- ),
138
- 'display_alert' => apply_filters( 'ai1ec_display_widget_creator_warning', true ),
139
- 'text_preview' => Ai1ec_I18n::__( 'Preview:' ),
140
- 'text_paste' => Ai1ec_I18n::__( 'Paste this code onto your site:' ),
141
- 'text_updated_code' => Ai1ec_I18n::__( 'This code will update to reflect changes made to the settings. Changing settings will not affect previously embedded widgets.' ),
142
- ),
143
- true
144
- );
145
- $file->render();
146
- }
147
 
148
 
149
  }
11
  */
12
  class Ai1ec_View_Widget_Creator extends Ai1ec_View_Admin_Abstract {
13
 
14
+ /**
15
+ * Adds page to the menu.
16
+ *
17
+ * @wp_hook admin_menu
18
+ *
19
+ * @return void
20
+ */
21
+ public function add_page() {
22
+ add_submenu_page(
23
+ AI1EC_ADMIN_BASE_URL,
24
+ __( 'Widget Creator', AI1EC_PLUGIN_NAME ),
25
+ __( 'Widget Creator', AI1EC_PLUGIN_NAME ),
26
+ 'manage_ai1ec_feeds',
27
+ AI1EC_PLUGIN_NAME . '-widget-creator',
28
+ array( $this, 'display_page' )
29
+ );
30
+ }
31
 
32
+ /**
33
+ * Display this plugin's feeds page in the admin.
34
+ *
35
+ * @return void
36
+ */
37
+ public function display_page() {
38
+ $this->_registry->get( 'css.admin' )->admin_enqueue_scripts(
39
+ 'ai1ec_event_page_all-in-one-event-calendar-settings'
40
+ );
41
+ $this->_registry->get( 'css.admin' )->process_enqueue(
42
+ array(
43
+ array( 'style', 'super-widget.css', ),
44
+ )
45
+ );
46
+ $args = array(
47
+ 'title' => __(
48
+ 'Widget Creator',
49
+ AI1EC_PLUGIN_NAME
50
+ ),
51
+ 'metabox' => array(
52
+ 'screen' => 'ai1ec-super-widget',
53
+ 'action' => 'left',
54
+ 'object' => null
55
+ ),
56
+ );
57
+ $loader = $this->_registry->get( 'theme.loader' );
58
+ $file = $loader->get_file( 'widget-creator/page.twig', $args, true );
59
+ $file->render();
60
+ }
61
 
62
+ /* (non-PHPdoc)
63
+ * @see Ai1ec_View_Admin_Settings::handle_post()
64
+ */
65
+ public function handle_post() {
66
+ }
67
 
68
+ /* (non-PHPdoc)
69
+ * @see Ai1ec_View_Admin_Settings::add_meta_box()
70
+ */
71
+ public function add_meta_box() {
72
+ add_meta_box(
73
+ 'ai1ec-widget-creator',
74
+ _x( 'Widget Creator', 'meta box', AI1EC_PLUGIN_NAME ),
75
+ array( $this, 'display_meta_box' ),
76
+ 'ai1ec-super-widget',
77
+ 'left',
78
+ 'default'
79
+ );
80
+ }
81
 
82
+ /**
83
+ * Renders the settings
84
+ *
85
+ * @param array $settings
86
+ *
87
+ * @return array
88
+ */
89
+ public function get_html_from_settings( array $settings ) {
90
+ $named_elements = array();
91
+ foreach ( $settings as $id => $setting ) {
92
+ $named_elements[$id] = $this->_registry->get(
93
+ 'html.element.setting.' . $setting['renderer']['class'],
94
+ array(
95
+ 'id' => $id,
96
+ 'value' => $setting['value'],
97
+ 'renderer' => $setting['renderer'],
98
+ )
99
+ )->render();
100
+ }
101
+ return $named_elements;
102
+ }
103
 
104
+ /* (non-PHPdoc)
105
+ * @see Ai1ec_View_Admin_Settings::display_meta_box()
106
+ */
107
+ public function display_meta_box( $object, $box ) {
108
+ $widgets = $this->_registry->get( 'controller.javascript-widget' )
109
+ ->get_widgets();
110
+ // this is just for the Super Widget which doesn't fully implement Ai1ec_Embeddable
111
+ $widgets = apply_filters( 'ai1ec_widget_creators_widgets', $widgets );
112
+ $tabs = array();
113
+ foreach ( $widgets as $widget_id => $widget_class ) {
114
+ $widget = $this->_registry->get( $widget_class );
115
+ $tabs[$widget_id] = array(
116
+ 'name' => $widget->get_name(),
117
+ 'icon' => $widget->get_icon(),
118
+ 'requirements' => $widget->check_requirements(),
119
+ 'elements' => $this->get_html_from_settings(
120
+ $widget->get_configurable_for_widget_creation()
121
+ )
122
+ );
123
+ }
124
 
125
+ $loader = $this->_registry->get( 'theme.loader' );
126
+ $file = $loader->get_file(
127
+ 'widget-creator/super-widget-contents.twig',
128
+ array(
129
+ 'tabs' => $tabs,
130
+ 'siteurl' => trailingslashit( preg_replace( '/^.*?:/', '', ai1ec_get_site_url() ) ),
131
+ 'text_common_info' => Ai1ec_I18n::__( 'Use this tool to generate code snippets you can add to <strong>an external website</strong> to embed new calendars and widgets.' ),
132
+ 'text_alert' => Ai1ec_I18n::__( '<h4>Attention!</h4><p>These widgets are designed to be embedded in <strong>external sites only</strong> and may cause conflicts if used within the same WordPress site.</p>' ),
133
+ 'text_alternatives' => sprintf(
134
+ Ai1ec_I18n::__( '<p>Use <a href="%s"><strong>Appearance</strong> &gt; <strong>Widgets</strong></a> to add event widgets to your WordPress site as you would any other widget, or use <a href="%s" target="_blank">shortcodes</a> to embed the full calendar.</strong></p>' ),
135
+ admin_url( 'widgets.php' ),
136
+ 'https://time.ly/document/user-guide/using-calendar/display-multiple-calendars-site/'
137
+ ),
138
+ 'display_alert' => apply_filters( 'ai1ec_display_widget_creator_warning', true ),
139
+ 'text_preview' => Ai1ec_I18n::__( 'Preview:' ),
140
+ 'text_paste' => Ai1ec_I18n::__( 'Paste this code onto your site:' ),
141
+ 'text_updated_code' => Ai1ec_I18n::__( 'This code will update to reflect changes made to the settings. Changing settings will not affect previously embedded widgets.' ),
142
+ ),
143
+ true
144
+ );
145
+ $file->render();
146
+ }
147
 
148
 
149
  }
app/view/calendar/fallbacks.php CHANGED
@@ -11,35 +11,35 @@
11
  */
12
  class Ai1ec_Calendar_Avatar_Fallbacks extends Ai1ec_Base {
13
 
14
- /**
15
- * Default avatar fallbacks.
16
- *
17
- * @var array
18
- */
19
- protected $_fallbacks = array(
20
- 'post_thumbnail',
21
- 'content_img',
22
- 'category_avatar',
23
- );
24
 
25
- /**
26
- * Get registered fallbacks.
27
- *
28
- * @return array
29
- */
30
- public function get_all() {
31
- return apply_filters( 'ai1ec_avatar_fallbacks', $this->_fallbacks );
32
- }
33
 
34
- /**
35
- * Register new avatar fallbacks.
36
- *
37
- * @param array $fallbacks Fallbacks.
38
- *
39
- * @return void Method does not return.
40
- */
41
- public function set( array $fallbacks ) {
42
- $this->_fallbacks = $fallbacks;
43
- }
44
 
45
  }
11
  */
12
  class Ai1ec_Calendar_Avatar_Fallbacks extends Ai1ec_Base {
13
 
14
+ /**
15
+ * Default avatar fallbacks.
16
+ *
17
+ * @var array
18
+ */
19
+ protected $_fallbacks = array(
20
+ 'post_thumbnail',
21
+ 'content_img',
22
+ 'category_avatar',
23
+ );
24
 
25
+ /**
26
+ * Get registered fallbacks.
27
+ *
28
+ * @return array
29
+ */
30
+ public function get_all() {
31
+ return apply_filters( 'ai1ec_avatar_fallbacks', $this->_fallbacks );
32
+ }
33
 
34
+ /**
35
+ * Register new avatar fallbacks.
36
+ *
37
+ * @param array $fallbacks Fallbacks.
38
+ *
39
+ * @return void Method does not return.
40
+ */
41
+ public function set( array $fallbacks ) {
42
+ $this->_fallbacks = $fallbacks;
43
+ }
44
 
45
  }
app/view/calendar/page.php CHANGED
@@ -11,512 +11,512 @@
11
  */
12
  class Ai1ec_Calendar_Page extends Ai1ec_Base {
13
 
14
- /**
15
- * @var Ai1ec_Memory_Utility Instance of memory to hold exact dates
16
- */
17
- protected $_exact_dates = NULL;
18
-
19
- /**
20
- * Public constructor
21
- *
22
- * @param Ai1ec_Registry_Object $registry The registry object
23
- */
24
- public function __construct( Ai1ec_Registry_Object $registry ) {
25
- parent::__construct( $registry );
26
- $this->_exact_dates = $registry->get( 'cache.memory' );
27
- }
28
-
29
- /**
30
- * Get the content if the calendar page
31
- *
32
- * @param Ai1ec_Request_Parser $request Request object.
33
- * @param string $caller Method caller, expected one of
34
- * ['shortcode', 'render-command']
35
- * Defaults to 'render-command'.
36
- *
37
- * @return string Content.
38
- */
39
- public function get_content(
40
- Ai1ec_Request_Parser $request,
41
- $caller = 'render-command'
42
- ) {
43
- // Get args for the current view; required to generate HTML for views
44
- // dropdown list, categories, tags, subscribe buttons, and of course the
45
- // view itself.
46
- $view_args = $this->get_view_args_for_view( $request );
47
-
48
- try {
49
- $action = $this->_registry->get( 'model.settings-view' )
50
- ->get_configured( $view_args['action'] );
51
- } catch ( Ai1ec_Settings_Exception $exception ) {
52
- // short-circuit and return error message
53
- return '<div id="ai1ec-container"><div class="timely"><p>' .
54
- Ai1ec_I18n::__(
55
- 'There was an error loading calendar. Please contact site administrator and inform him to configure calendar views.'
56
- ) .
57
- '</p></div></div>';
58
- }
59
- $type = $request->get( 'request_type' );
60
- $is_json = $this->_registry->get( 'http.request' )->is_json_required(
61
- $view_args['request_format'], $action
62
- );
63
-
64
- // Add view-specific args to the current view args.
65
- $exact_date = $this->get_exact_date( $request );
66
- try {
67
- $view_obj = $this->_registry->get(
68
- 'view.calendar.view.' . $action,
69
- $request
70
- );
71
- } catch ( Ai1ec_Bootstrap_Exception $exc ) {
72
- $this->_registry->get( 'notification.admin' )->store(
73
- sprintf(
74
- Ai1ec_I18n::__( 'Calendar was unable to initialize %s view and has reverted to Agenda view. Please check if you have installed the latest versions of calendar add-ons.' ),
75
- ucfirst( $action )
76
- ),
77
- 'error',
78
- 0,
79
- array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
80
- true
81
- );
82
- // don't disable calendar - just switch to agenda which should
83
- // always exists
84
- $action = 'agenda';
85
- $view_obj = $this->_registry->get(
86
- 'view.calendar.view.' . $action,
87
- $request
88
- );
89
- }
90
- $view_args = $view_obj->get_extra_arguments( $view_args, $exact_date );
91
-
92
- // Get HTML for views dropdown list.
93
- $dropdown_args = $view_args;
94
- if (
95
- isset( $dropdown_args['time_limit'] ) &&
96
- false !== $exact_date
97
- ) {
98
- $dropdown_args['exact_date'] = $exact_date;
99
- }
100
- $views_dropdown =
101
- $this->get_html_for_views_dropdown( $dropdown_args, $view_obj );
102
- // Add views dropdown markup to view args.
103
- $view_args['views_dropdown'] = $views_dropdown;
104
-
105
- $settings = $this->_registry->get( 'model.settings' );
106
- if ( $settings->get( 'ai1ec_use_frontend_rendering' ) ) {
107
- $view_args['request_format'] = 'json';
108
- }
109
-
110
- // Get HTML for subscribe buttons.
111
- $subscribe_buttons = $this->get_html_for_subscribe_buttons( $view_args );
112
-
113
- // Get HTML for view itself.
114
- $view = $view_obj->get_content( $view_args );
115
-
116
- $router = $this->_registry->get( 'routing.router' );
117
- $are_filters_set = $router->is_at_least_one_filter_set_in_request(
118
- $view_args
119
- );
120
-
121
- if (
122
- $is_json &&
123
- ( $view_args['no_navigation'] || $type !== 'html' )
124
- ) {
125
- // send data both for json and jsonp as shortcodes are jsonp
126
- return array(
127
- 'html' => $view,
128
- 'views_dropdown' => $views_dropdown,
129
- 'subscribe_buttons' => $subscribe_buttons,
130
- 'are_filters_set' => $are_filters_set,
131
- 'is_json' => $is_json,
132
- );
133
-
134
- } else {
135
- $loader = $this->_registry->get( 'theme.loader' );
136
- $empty = $loader->get_file( 'empty.twig', array(), false );
137
- // Get HTML for categories and for tags
138
- $taxonomy = $this->_registry->get( 'view.calendar.taxonomy' );
139
- $categories = $taxonomy->get_html_for_categories(
140
- $view_args
141
- );
142
- $tags = $taxonomy->get_html_for_tags(
143
- $view_args,
144
- true
145
- );
146
-
147
- // option to show filters in the super widget
148
- // Define new arguments for overall calendar view
149
- $filter_args = array(
150
- 'views_dropdown' => $views_dropdown,
151
- 'categories' => $categories,
152
- 'tags' => $tags,
153
- 'contribution_buttons' => apply_filters(
154
- 'ai1ec_contribution_buttons',
155
- '',
156
- $type,
157
- $caller
158
- ),
159
- 'additional_buttons' => apply_filters(
160
- 'ai1ec_additional_buttons',
161
- '',
162
- $view_args
163
- ),
164
- 'show_dropdowns' => apply_filters(
165
- 'ai1ec_show_dropdowns',
166
- true
167
- ),
168
- 'show_select2' => apply_filters(
169
- 'ai1ec_show_select2',
170
- false
171
- ),
172
- 'span_for_select2' => apply_filters(
173
- 'ai1ec_span_for_select2',
174
- ''
175
- ),
176
- 'authors' => apply_filters(
177
- 'ai1ec_authors',
178
- ''
179
- ),
180
- 'save_view_btngroup' => apply_filters(
181
- 'ai1ec_save_view_btngroup',
182
- $empty
183
- ),
184
- 'view_args' => $view_args,
185
- 'request' => $request,
186
- );
187
-
188
- $filter_menu = $loader->get_file(
189
- 'filter-menu.twig',
190
- $filter_args,
191
- false
192
- )->get_content();
193
- // hide filters in the SW
194
- if ( 'true' !== $request->get( 'display_filters' ) && 'jsonp' === $type ) {
195
- $filter_menu = '';
196
- }
197
-
198
- $calendar_args = array(
199
- 'version' => AI1EC_VERSION,
200
- 'filter_menu' => $filter_menu,
201
- 'view' => $view,
202
- 'subscribe_buttons' => $subscribe_buttons,
203
- 'disable_standard_filter_menu' => apply_filters(
204
- 'ai1ec_disable_standard_filter_menu',
205
- false
206
- ),
207
- 'inline_js_calendar' => apply_filters(
208
- 'ai1ec_inline_js_calendar',
209
- ''
210
- ),
211
- 'after_view' => apply_filters(
212
- 'ai1ec_after_view',
213
- ''
214
- ),
215
- 'ai1ec_above_calendar' => apply_filters(
216
- 'ai1ec_above_calendar',
217
- ''
218
- ),
219
- );
220
-
221
- if ( is_array( $calendar_args['view'] ) ) {
222
- $view_args['request_format'] = 'html';
223
- $calendar_args['view'] = $view_obj->get_content( $view_args );
224
- }
225
- $calendar = $loader->get_file( 'calendar.twig', $calendar_args, false );
226
- // if it's just html, only the calendar html must be returned.
227
- if ( 'html' === $type ) {
228
- return $calendar->get_content();
229
- }
230
- // send data both for json and jsonp as shortcodes are jsonp
231
- return array(
232
- 'html' => $calendar->get_content(),
233
- 'views_dropdown' => $views_dropdown,
234
- 'subscribe_buttons' => $subscribe_buttons,
235
- 'are_filters_set' => $are_filters_set,
236
- 'is_json' => $is_json
237
- );
238
- }
239
- }
240
-
241
- /**
242
- * Render the HTML for the `subscribe' buttons.
243
- *
244
- * @param array $view_args Args to pass.
245
- *
246
- * @return string Rendered HTML to include in output.
247
- */
248
- public function get_html_for_subscribe_buttons( array $view_args ) {
249
- $settings = $this->_registry->get( 'model.settings' );
250
- $turn_off_subscribe = $settings->get( 'turn_off_subscription_buttons' );
251
- if ( $turn_off_subscribe ) {
252
- return '';
253
- }
254
-
255
- $args = array(
256
- 'url_args' => '',
257
- 'is_filtered' => false,
258
- 'export_url' => AI1EC_EXPORT_URL,
259
- 'export_url_no_html' => AI1EC_EXPORT_URL . '&no_html=true',
260
- 'text_filtered' => Ai1ec_I18n::__( 'Subscribe to filtered calendar' ),
261
- 'text_subscribe' => Ai1ec_I18n::__( 'Subscribe' ),
262
- 'text_get_calendar' => Ai1ec_I18n::__( 'Get a Timely Calendar' ),
263
- 'show_get_calendar' => ! $settings->get( 'disable_get_calendar_button' ),
264
- 'text' => $this->_registry
265
- ->get( 'view.calendar.subscribe-button' )
266
- ->get_labels(),
267
- 'placement' => 'up',
268
- );
269
- if ( ! empty( $view_args['cat_ids'] ) ) {
270
- $args['url_args'] .= '&ai1ec_cat_ids=' .
271
- implode( ',', $view_args['cat_ids'] );
272
- $args['is_filtered'] = true;
273
- }
274
- if ( ! empty( $view_args['tag_ids'] ) ) {
275
- $args['url_args'] .= '&ai1ec_tag_ids=' .
276
- implode( ',', $view_args['tag_ids'] );
277
- $args['is_filtered'] = true;
278
- }
279
- if ( ! empty( $view_args['post_ids'] ) ) {
280
- $args['url_args'] .= '&ai1ec_post_ids=' .
281
- implode( ',', $view_args['post_ids'] );
282
- $args['is_filtered'] = true;
283
- }
284
- $args = apply_filters(
285
- 'ai1ec_subscribe_buttons_arguments',
286
- $args,
287
- $view_args
288
- );
289
- $localization = $this->_registry->get( 'p28n.wpml' );
290
- if (
291
- NULL !== ( $use_lang = $localization->get_language() )
292
- ) {
293
- $args['url_args'] .= '&lang=' . $use_lang;
294
- }
295
- $subscribe = $this->_registry->get( 'theme.loader' )
296
- ->get_file( 'subscribe-buttons.twig', $args, false );
297
- return $subscribe->get_content();
298
- }
299
-
300
- /**
301
- * This function generates the html for the view dropdowns.
302
- *
303
- * @param array $view_args Args passed to view
304
- * @param Ai1ec_Calendar_View_Abstract $view View object
305
- */
306
- protected function get_html_for_views_dropdown(
307
- array $view_args,
308
- Ai1ec_Calendar_View_Abstract $view
309
- ) {
310
- $settings = $this->_registry->get( 'model.settings' );
311
- $available_views = array();
312
- $enabled_views = (array)$settings->get( 'enabled_views', array() );
313
- $view_names = array();
314
- $mode = wp_is_mobile() ? '_mobile' : '';
315
- foreach ( $enabled_views as $key => $val ) {
316
- $view_names[$key] = translate_nooped_plural(
317
- $val['longname'],
318
- 1
319
- );
320
- // Find out if view is enabled in requested mode (mobile or desktop). If
321
- // no mode-specific setting is available, fall back to desktop setting.
322
- $view_enabled = isset( $enabled_views[$key]['enabled' . $mode] ) ?
323
- $enabled_views[$key]['enabled' . $mode] :
324
- $enabled_views[$key]['enabled'];
325
- $values = array();
326
- $options = $view_args;
327
- if ( $view_enabled ) {
328
- if ( $view instanceof Ai1ec_Calendar_View_Agenda ) {
329
- if (
330
- isset( $options['exact_date'] ) &&
331
- ! isset( $options['time_limit'] )
332
- ) {
333
- $options['time_limit'] = $options['exact_date'];
334
- }
335
- unset( $options['exact_date'] );
336
- } else {
337
- unset( $options['time_limit'] );
338
- }
339
- unset( $options['month_offset'] );
340
- unset( $options['week_offset'] );
341
- unset( $options['oneday_offset'] );
342
- $options['action'] = $key;
343
- $values['desc'] = translate_nooped_plural(
344
- $val['longname'],
345
- 1
346
- );
347
- if ( $settings->get( 'ai1ec_use_frontend_rendering' ) ) {
348
- $options['request_format'] = 'json';
349
- }
350
- $href = $this->_registry->get( 'html.element.href', $options );
351
- $values['href'] = $href->generate_href();
352
- $available_views[$key] = $values;
353
- }
354
- };
355
- $args = array(
356
- 'view_names' => $view_names,
357
- 'available_views' => $available_views,
358
- 'current_view' => $view_args['action'],
359
- 'data_type' => $view_args['data_type'],
360
- );
361
-
362
- $views_dropdown = $this->_registry->get( 'theme.loader' )
363
- ->get_file( 'views_dropdown.twig', $args, false );
364
- return $views_dropdown->get_content();
365
- }
366
-
367
- /**
368
- * Get the exact date from request if available, or else from settings.
369
- *
370
- * @param Ai1ec_Abstract_Query settings
371
- *
372
- * @return boolean|int
373
- */
374
- private function get_exact_date( Ai1ec_Abstract_Query $request ) {
375
- $settings = $this->_registry->get( 'model.settings' );
376
-
377
- // Preprocess exact_date.
378
- // Check to see if a date has been specified.
379
- $exact_date = $request->get( 'exact_date' );
380
- $use_key = $exact_date;
381
- if ( null === ( $exact_date = $this->_exact_dates->get( $use_key ) ) ) {
382
- $exact_date = $use_key;
383
- // Let's check if we have a date
384
- if ( false !== $exact_date ) {
385
- // If it's not a timestamp
386
- if ( ! Ai1ec_Validation_Utility::is_valid_time_stamp( $exact_date ) ) {
387
- // Try to parse it
388
- $exact_date = $this->return_gmtime_from_exact_date( $exact_date );
389
- if ( false === $exact_date ) {
390
- return null;
391
- }
392
- }
393
- }
394
- // Last try, let's see if an exact date is set in settings.
395
- if ( false === $exact_date && $settings->get( 'exact_date' ) !== '' ) {
396
- $exact_date = $this->return_gmtime_from_exact_date(
397
- $settings->get( 'exact_date' )
398
- );
399
- }
400
- $this->_exact_dates->set( $use_key, $exact_date );
401
- }
402
- return $exact_date;
403
- }
404
-
405
- /**
406
- * Decomposes an 'exact_date' parameter into month, day, year components based
407
- * on date pattern defined in settings (assumed to be in local time zone),
408
- * then returns a timestamp in GMT.
409
- *
410
- * @param string $exact_date 'exact_date' parameter passed to a view
411
- * @return bool|int false if argument not provided or invalid,
412
- * else UNIX timestamp in GMT
413
- */
414
- private function return_gmtime_from_exact_date( $exact_date ) {
415
- $input_format = $this->_registry->get( 'model.settings' )
416
- ->get( 'input_date_format' );
417
-
418
- $date = Ai1ec_Validation_Utility::format_as_iso(
419
- $exact_date,
420
- $input_format
421
- );
422
- if ( false === $date ) {
423
- $exact_date = false;
424
- } else {
425
- $exact_date = $this->_registry->get(
426
- 'date.time',
427
- $date,
428
- 'sys.default'
429
- )->format_to_gmt();
430
- if ( $exact_date < 0 ) {
431
- return false;
432
- }
433
- }
434
- return $exact_date;
435
- }
436
-
437
- /**
438
- * Returns the correct data attribute to use in views
439
- *
440
- * @param string $type
441
- */
442
- private function return_data_type_for_request_type( $type ) {
443
- $data_type = 'data-type="json"';
444
- if ( $type === 'jsonp' ) {
445
- $data_type = 'data-type="jsonp"';
446
- }
447
- return $data_type;
448
- }
449
-
450
- /**
451
- * Get the parameters for the view from the request object
452
- *
453
- * @param Ai1ec_Abstract_Query $request
454
- *
455
- * @return array
456
- */
457
- protected function get_view_args_for_view( Ai1ec_Abstract_Query $request ) {
458
- $settings = $this->_registry->get( 'model.settings' );
459
- // Define arguments for specific calendar sub-view (month, agenda, etc.)
460
- // Preprocess action.
461
- // Allow action w/ or w/o ai1ec_ prefix. Remove ai1ec_ if provided.
462
- $action = $request->get( 'action' );
463
-
464
- if ( 0 === strncmp( $action, 'ai1ec_', 6 ) ) {
465
- $action = substr( $action, 6 );
466
- }
467
- $view_args = $request->get_dict(
468
- apply_filters(
469
- 'ai1ec_view_args_for_view',
470
- array(
471
- 'post_ids',
472
- 'auth_ids',
473
- 'cat_ids',
474
- 'tag_ids',
475
- 'events_limit',
476
- 'instance_ids',
477
- )
478
- )
479
- );
480
- $type = $request->get( 'request_type' );
481
- if ( 'html' === $type ) {
482
- $add_defaults = array(
483
- 'cat_ids' => 'categories',
484
- 'tag_ids' => 'tags',
485
- );
486
- foreach ( $add_defaults as $query => $default ) {
487
- if ( empty( $view_args[$query] ) ) {
488
- $setting = $settings->get( 'default_tags_categories' );
489
- if ( isset( $setting[$default] ) ) {
490
- $view_args[$query] = $setting[$default];
491
- }
492
- }
493
- }
494
- }
495
-
496
- $view_args['data_type'] = $this->return_data_type_for_request_type(
497
- $type
498
- );
499
-
500
- $view_args['request_format'] = $request->get( 'request_format' );
501
- $exact_date = $this->get_exact_date( $request );
502
-
503
- $view_args['no_navigation'] = $request->get( 'no_navigation' ) == true;
504
-
505
- // Find out which view of the calendar page was requested, and render it
506
- // accordingly.
507
- $view_args['action'] = $action;
508
-
509
- $view_args['request'] = $request;
510
- $view_args = apply_filters(
511
- 'ai1ec_view_args_array',
512
- $view_args
513
- );
514
- if ( null === $exact_date ) {
515
- $href = $this->_registry->get( 'html.element.href', $view_args )
516
- ->generate_href();
517
- return Ai1ec_Http_Response_Helper::redirect( $href, 307 );
518
-
519
- }
520
- return $view_args;
521
- }
522
  }
11
  */
12
  class Ai1ec_Calendar_Page extends Ai1ec_Base {
13
 
14
+ /**
15
+ * @var Ai1ec_Memory_Utility Instance of memory to hold exact dates
16
+ */
17
+ protected $_exact_dates = NULL;
18
+
19
+ /**
20
+ * Public constructor
21
+ *
22
+ * @param Ai1ec_Registry_Object $registry The registry object
23
+ */
24
+ public function __construct( Ai1ec_Registry_Object $registry ) {
25
+ parent::__construct( $registry );
26
+ $this->_exact_dates = $registry->get( 'cache.memory' );
27
+ }
28
+
29
+ /**
30
+ * Get the content if the calendar page
31
+ *
32
+ * @param Ai1ec_Request_Parser $request Request object.
33
+ * @param string $caller Method caller, expected one of
34
+ * ['shortcode', 'render-command']
35
+ * Defaults to 'render-command'.
36
+ *
37
+ * @return string Content.
38
+ */
39
+ public function get_content(
40
+ Ai1ec_Request_Parser $request,
41
+ $caller = 'render-command'
42
+ ) {
43
+ // Get args for the current view; required to generate HTML for views
44
+ // dropdown list, categories, tags, subscribe buttons, and of course the
45
+ // view itself.
46
+ $view_args = $this->get_view_args_for_view( $request );
47
+
48
+ try {
49
+ $action = $this->_registry->get( 'model.settings-view' )
50
+ ->get_configured( $view_args['action'] );
51
+ } catch ( Ai1ec_Settings_Exception $exception ) {
52
+ // short-circuit and return error message
53
+ return '<div id="ai1ec-container"><div class="timely"><p>' .
54
+ Ai1ec_I18n::__(
55
+ 'There was an error loading calendar. Please contact site administrator and inform him to configure calendar views.'
56
+ ) .
57
+ '</p></div></div>';
58
+ }
59
+ $type = $request->get( 'request_type' );
60
+ $is_json = $this->_registry->get( 'http.request' )->is_json_required(
61
+ $view_args['request_format'], $action
62
+ );
63
+
64
+ // Add view-specific args to the current view args.
65
+ $exact_date = $this->get_exact_date( $request );
66
+ try {
67
+ $view_obj = $this->_registry->get(
68
+ 'view.calendar.view.' . $action,
69
+ $request
70
+ );
71
+ } catch ( Ai1ec_Bootstrap_Exception $exc ) {
72
+ $this->_registry->get( 'notification.admin' )->store(
73
+ sprintf(
74
+ Ai1ec_I18n::__( 'Calendar was unable to initialize %s view and has reverted to Agenda view. Please check if you have installed the latest versions of calendar add-ons.' ),
75
+ ucfirst( $action )
76
+ ),
77
+ 'error',
78
+ 0,
79
+ array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
80
+ true
81
+ );
82
+ // don't disable calendar - just switch to agenda which should
83
+ // always exists
84
+ $action = 'agenda';
85
+ $view_obj = $this->_registry->get(
86
+ 'view.calendar.view.' . $action,
87
+ $request
88
+ );
89
+ }
90
+ $view_args = $view_obj->get_extra_arguments( $view_args, $exact_date );
91
+
92
+ // Get HTML for views dropdown list.
93
+ $dropdown_args = $view_args;
94
+ if (
95
+ isset( $dropdown_args['time_limit'] ) &&
96
+ false !== $exact_date
97
+ ) {
98
+ $dropdown_args['exact_date'] = $exact_date;
99
+ }
100
+ $views_dropdown =
101
+ $this->get_html_for_views_dropdown( $dropdown_args, $view_obj );
102
+ // Add views dropdown markup to view args.
103
+ $view_args['views_dropdown'] = $views_dropdown;
104
+
105
+ $settings = $this->_registry->get( 'model.settings' );
106
+ if ( $settings->get( 'ai1ec_use_frontend_rendering' ) ) {
107
+ $view_args['request_format'] = 'json';
108
+ }
109
+
110
+ // Get HTML for subscribe buttons.
111
+ $subscribe_buttons = $this->get_html_for_subscribe_buttons( $view_args );
112
+
113
+ // Get HTML for view itself.
114
+ $view = $view_obj->get_content( $view_args );
115
+
116
+ $router = $this->_registry->get( 'routing.router' );
117
+ $are_filters_set = $router->is_at_least_one_filter_set_in_request(
118
+ $view_args
119
+ );
120
+
121
+ if (
122
+ $is_json &&
123
+ ( $view_args['no_navigation'] || $type !== 'html' )
124
+ ) {
125
+ // send data both for json and jsonp as shortcodes are jsonp
126
+ return array(
127
+ 'html' => $view,
128
+ 'views_dropdown' => $views_dropdown,
129
+ 'subscribe_buttons' => $subscribe_buttons,
130
+ 'are_filters_set' => $are_filters_set,
131
+ 'is_json' => $is_json,
132
+ );
133
+
134
+ } else {
135
+ $loader = $this->_registry->get( 'theme.loader' );
136
+ $empty = $loader->get_file( 'empty.twig', array(), false );
137
+ // Get HTML for categories and for tags
138
+ $taxonomy = $this->_registry->get( 'view.calendar.taxonomy' );
139
+ $categories = $taxonomy->get_html_for_categories(
140
+ $view_args
141
+ );
142
+ $tags = $taxonomy->get_html_for_tags(
143
+ $view_args,
144
+ true
145
+ );
146
+
147
+ // option to show filters in the super widget
148
+ // Define new arguments for overall calendar view
149
+ $filter_args = array(
150
+ 'views_dropdown' => $views_dropdown,
151
+ 'categories' => $categories,
152
+ 'tags' => $tags,
153
+ 'contribution_buttons' => apply_filters(
154
+ 'ai1ec_contribution_buttons',
155
+ '',
156
+ $type,
157
+ $caller
158
+ ),
159
+ 'additional_buttons' => apply_filters(
160
+ 'ai1ec_additional_buttons',
161
+ '',
162
+ $view_args
163
+ ),
164
+ 'show_dropdowns' => apply_filters(
165
+ 'ai1ec_show_dropdowns',
166
+ true
167
+ ),
168
+ 'show_select2' => apply_filters(
169
+ 'ai1ec_show_select2',
170
+ false
171
+ ),
172
+ 'span_for_select2' => apply_filters(
173
+ 'ai1ec_span_for_select2',
174
+ ''
175
+ ),
176
+ 'authors' => apply_filters(
177
+ 'ai1ec_authors',
178
+ ''
179
+ ),
180
+ 'save_view_btngroup' => apply_filters(
181
+ 'ai1ec_save_view_btngroup',
182
+ $empty
183
+ ),
184
+ 'view_args' => $view_args,
185
+ 'request' => $request,
186
+ );
187
+
188
+ $filter_menu = $loader->get_file(
189
+ 'filter-menu.twig',
190
+ $filter_args,
191
+ false
192
+ )->get_content();
193
+ // hide filters in the SW
194
+ if ( 'true' !== $request->get( 'display_filters' ) && 'jsonp' === $type ) {
195
+ $filter_menu = '';
196
+ }
197
+
198
+ $calendar_args = array(
199
+ 'version' => AI1EC_VERSION,
200
+ 'filter_menu' => $filter_menu,
201
+ 'view' => $view,
202
+ 'subscribe_buttons' => $subscribe_buttons,
203
+ 'disable_standard_filter_menu' => apply_filters(
204
+ 'ai1ec_disable_standard_filter_menu',
205
+ false
206
+ ),
207
+ 'inline_js_calendar' => apply_filters(
208
+ 'ai1ec_inline_js_calendar',
209
+ ''
210
+ ),
211
+ 'after_view' => apply_filters(
212
+ 'ai1ec_after_view',
213
+ ''
214
+ ),
215
+ 'ai1ec_above_calendar' => apply_filters(
216
+ 'ai1ec_above_calendar',
217
+ ''
218
+ ),
219
+ );
220
+
221
+ if ( is_array( $calendar_args['view'] ) ) {
222
+ $view_args['request_format'] = 'html';
223
+ $calendar_args['view'] = $view_obj->get_content( $view_args );
224
+ }
225
+ $calendar = $loader->get_file( 'calendar.twig', $calendar_args, false );
226
+ // if it's just html, only the calendar html must be returned.
227
+ if ( 'html' === $type ) {
228
+ return $calendar->get_content();
229
+ }
230
+ // send data both for json and jsonp as shortcodes are jsonp
231
+ return array(
232
+ 'html' => $calendar->get_content(),
233
+ 'views_dropdown' => $views_dropdown,
234
+ 'subscribe_buttons' => $subscribe_buttons,
235
+ 'are_filters_set' => $are_filters_set,
236
+ 'is_json' => $is_json
237
+ );
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Render the HTML for the `subscribe' buttons.
243
+ *
244
+ * @param array $view_args Args to pass.
245
+ *
246
+ * @return string Rendered HTML to include in output.
247
+ */
248
+ public function get_html_for_subscribe_buttons( array $view_args ) {
249
+ $settings = $this->_registry->get( 'model.settings' );
250
+ $turn_off_subscribe = $settings->get( 'turn_off_subscription_buttons' );
251
+ if ( $turn_off_subscribe ) {
252
+ return '';
253
+ }
254
+
255
+ $args = array(
256
+ 'url_args' => '',
257
+ 'is_filtered' => false,
258
+ 'export_url' => AI1EC_EXPORT_URL,
259
+ 'export_url_no_html' => AI1EC_EXPORT_URL . '&no_html=true',
260
+ 'text_filtered' => Ai1ec_I18n::__( 'Subscribe to filtered calendar' ),
261
+ 'text_subscribe' => Ai1ec_I18n::__( 'Subscribe' ),
262
+ 'text_get_calendar' => Ai1ec_I18n::__( 'Get a Timely Calendar' ),
263
+ 'show_get_calendar' => ! $settings->get( 'disable_get_calendar_button' ),
264
+ 'text' => $this->_registry
265
+ ->get( 'view.calendar.subscribe-button' )
266
+ ->get_labels(),
267
+ 'placement' => 'up',
268
+ );
269
+ if ( ! empty( $view_args['cat_ids'] ) ) {
270
+ $args['url_args'] .= '&ai1ec_cat_ids=' .
271
+ implode( ',', $view_args['cat_ids'] );
272
+ $args['is_filtered'] = true;
273
+ }
274
+ if ( ! empty( $view_args['tag_ids'] ) ) {
275
+ $args['url_args'] .= '&ai1ec_tag_ids=' .
276
+ implode( ',', $view_args['tag_ids'] );
277
+ $args['is_filtered'] = true;
278
+ }
279
+ if ( ! empty( $view_args['post_ids'] ) ) {
280
+ $args['url_args'] .= '&ai1ec_post_ids=' .
281
+ implode( ',', $view_args['post_ids'] );
282
+ $args['is_filtered'] = true;
283
+ }
284
+ $args = apply_filters(
285
+ 'ai1ec_subscribe_buttons_arguments',
286
+ $args,
287
+ $view_args
288
+ );
289
+ $localization = $this->_registry->get( 'p28n.wpml' );
290
+ if (
291
+ NULL !== ( $use_lang = $localization->get_language() )
292
+ ) {
293
+ $args['url_args'] .= '&lang=' . $use_lang;
294
+ }
295
+ $subscribe = $this->_registry->get( 'theme.loader' )
296
+ ->get_file( 'subscribe-buttons.twig', $args, false );
297
+ return $subscribe->get_content();
298
+ }
299
+
300
+ /**
301
+ * This function generates the html for the view dropdowns.
302
+ *
303
+ * @param array $view_args Args passed to view
304
+ * @param Ai1ec_Calendar_View_Abstract $view View object
305
+ */
306
+ protected function get_html_for_views_dropdown(
307
+ array $view_args,
308
+ Ai1ec_Calendar_View_Abstract $view
309
+ ) {
310
+ $settings = $this->_registry->get( 'model.settings' );
311
+ $available_views = array();
312
+ $enabled_views = (array)$settings->get( 'enabled_views', array() );
313
+ $view_names = array();
314
+ $mode = wp_is_mobile() ? '_mobile' : '';
315
+ foreach ( $enabled_views as $key => $val ) {
316
+ $view_names[$key] = translate_nooped_plural(
317
+ $val['longname'],
318
+ 1
319
+ );
320
+ // Find out if view is enabled in requested mode (mobile or desktop). If
321
+ // no mode-specific setting is available, fall back to desktop setting.
322
+ $view_enabled = isset( $enabled_views[$key]['enabled' . $mode] ) ?
323
+ $enabled_views[$key]['enabled' . $mode] :
324
+ $enabled_views[$key]['enabled'];
325
+ $values = array();
326
+ $options = $view_args;
327
+ if ( $view_enabled ) {
328
+ if ( $view instanceof Ai1ec_Calendar_View_Agenda ) {
329
+ if (
330
+ isset( $options['exact_date'] ) &&
331
+ ! isset( $options['time_limit'] )
332
+ ) {
333
+ $options['time_limit'] = $options['exact_date'];
334
+ }
335
+ unset( $options['exact_date'] );
336
+ } else {
337
+ unset( $options['time_limit'] );
338
+ }
339
+ unset( $options['month_offset'] );
340
+ unset( $options['week_offset'] );
341
+ unset( $options['oneday_offset'] );
342
+ $options['action'] = $key;
343
+ $values['desc'] = translate_nooped_plural(
344
+ $val['longname'],
345
+ 1
346
+ );
347
+ if ( $settings->get( 'ai1ec_use_frontend_rendering' ) ) {
348
+ $options['request_format'] = 'json';
349
+ }
350
+ $href = $this->_registry->get( 'html.element.href', $options );
351
+ $values['href'] = $href->generate_href();
352
+ $available_views[$key] = $values;
353
+ }
354
+ };
355
+ $args = array(
356
+ 'view_names' => $view_names,
357
+ 'available_views' => $available_views,
358
+ 'current_view' => $view_args['action'],
359
+ 'data_type' => $view_args['data_type'],
360
+ );
361
+
362
+ $views_dropdown = $this->_registry->get( 'theme.loader' )
363
+ ->get_file( 'views_dropdown.twig', $args, false );
364
+ return $views_dropdown->get_content();
365
+ }
366
+
367
+ /**
368
+ * Get the exact date from request if available, or else from settings.
369
+ *
370
+ * @param Ai1ec_Abstract_Query settings
371
+ *
372
+ * @return boolean|int
373
+ */
374
+ private function get_exact_date( Ai1ec_Abstract_Query $request ) {
375
+ $settings = $this->_registry->get( 'model.settings' );
376
+
377
+ // Preprocess exact_date.
378
+ // Check to see if a date has been specified.
379
+ $exact_date = $request->get( 'exact_date' );
380
+ $use_key = $exact_date;
381
+ if ( null === ( $exact_date = $this->_exact_dates->get( $use_key ) ) ) {
382
+ $exact_date = $use_key;
383
+ // Let's check if we have a date
384
+ if ( false !== $exact_date ) {
385
+ // If it's not a timestamp
386
+ if ( ! Ai1ec_Validation_Utility::is_valid_time_stamp( $exact_date ) ) {
387
+ // Try to parse it
388
+ $exact_date = $this->return_gmtime_from_exact_date( $exact_date );
389
+ if ( false === $exact_date ) {
390
+ return null;
391
+ }
392
+ }
393
+ }
394
+ // Last try, let's see if an exact date is set in settings.
395
+ if ( false === $exact_date && $settings->get( 'exact_date' ) !== '' ) {
396
+ $exact_date = $this->return_gmtime_from_exact_date(
397
+ $settings->get( 'exact_date' )
398
+ );
399
+ }
400
+ $this->_exact_dates->set( $use_key, $exact_date );
401
+ }
402
+ return $exact_date;
403
+ }
404
+
405
+ /**
406
+ * Decomposes an 'exact_date' parameter into month, day, year components based
407
+ * on date pattern defined in settings (assumed to be in local time zone),
408
+ * then returns a timestamp in GMT.
409
+ *
410
+ * @param string $exact_date 'exact_date' parameter passed to a view
411
+ * @return bool|int false if argument not provided or invalid,
412
+ * else UNIX timestamp in GMT
413
+ */
414
+ private function return_gmtime_from_exact_date( $exact_date ) {
415
+ $input_format = $this->_registry->get( 'model.settings' )
416
+ ->get( 'input_date_format' );
417
+
418
+ $date = Ai1ec_Validation_Utility::format_as_iso(
419
+ $exact_date,
420
+ $input_format
421
+ );
422
+ if ( false === $date ) {
423
+ $exact_date = false;
424
+ } else {
425
+ $exact_date = $this->_registry->get(
426
+ 'date.time',
427
+ $date,
428
+ 'sys.default'
429
+ )->format_to_gmt();
430
+ if ( $exact_date < 0 ) {
431
+ return false;
432
+ }
433
+ }
434
+ return $exact_date;
435
+ }
436
+
437
+ /**
438
+ * Returns the correct data attribute to use in views
439
+ *
440
+ * @param string $type
441
+ */
442
+ private function return_data_type_for_request_type( $type ) {
443
+ $data_type = 'data-type="json"';
444
+ if ( $type === 'jsonp' ) {
445
+ $data_type = 'data-type="jsonp"';
446
+ }
447
+ return $data_type;
448
+ }
449
+
450
+ /**
451
+ * Get the parameters for the view from the request object
452
+ *
453
+ * @param Ai1ec_Abstract_Query $request
454
+ *
455
+ * @return array
456
+ */
457
+ protected function get_view_args_for_view( Ai1ec_Abstract_Query $request ) {
458
+ $settings = $this->_registry->get( 'model.settings' );
459
+ // Define arguments for specific calendar sub-view (month, agenda, etc.)
460
+ // Preprocess action.
461
+ // Allow action w/ or w/o ai1ec_ prefix. Remove ai1ec_ if provided.
462
+ $action = $request->get( 'action' );
463
+
464
+ if ( 0 === strncmp( $action, 'ai1ec_', 6 ) ) {
465
+ $action = substr( $action, 6 );
466
+ }
467
+ $view_args = $request->get_dict(
468
+ apply_filters(
469
+ 'ai1ec_view_args_for_view',
470
+ array(
471
+ 'post_ids',
472
+ 'auth_ids',
473
+ 'cat_ids',
474
+ 'tag_ids',
475
+ 'events_limit',
476
+ 'instance_ids',
477
+ )
478
+ )
479
+ );
480
+ $type = $request->get( 'request_type' );
481
+ if ( 'html' === $type ) {
482
+ $add_defaults = array(
483
+ 'cat_ids' => 'categories',
484
+ 'tag_ids' => 'tags',
485
+ );
486
+ foreach ( $add_defaults as $query => $default ) {
487
+ if ( empty( $view_args[$query] ) ) {
488
+ $setting = $settings->get( 'default_tags_categories' );
489
+ if ( isset( $setting[$default] ) ) {
490
+ $view_args[$query] = $setting[$default];
491
+ }
492
+ }
493
+ }
494
+ }
495
+
496
+ $view_args['data_type'] = $this->return_data_type_for_request_type(
497
+ $type
498
+ );
499
+
500
+ $view_args['request_format'] = $request->get( 'request_format' );
501
+ $exact_date = $this->get_exact_date( $request );
502
+
503
+ $view_args['no_navigation'] = $request->get( 'no_navigation' ) == true;
504
+
505
+ // Find out which view of the calendar page was requested, and render it
506
+ // accordingly.
507
+ $view_args['action'] = $action;
508
+
509
+ $view_args['request'] = $request;
510
+ $view_args = apply_filters(
511
+ 'ai1ec_view_args_array',
512
+ $view_args
513
+ );
514
+ if ( null === $exact_date ) {
515
+ $href = $this->_registry->get( 'html.element.href', $view_args )
516
+ ->generate_href();
517
+ return Ai1ec_Http_Response_Helper::redirect( $href, 307 );
518
+
519
+ }
520
+ return $view_args;
521
+ }
522
  }
app/view/calendar/shortcode.php CHANGED
@@ -10,147 +10,147 @@
10
  */
11
  class Ai1ec_View_
10
  */
11
  class Ai1ec_View_