Export WordPress data to XML/CSV - Version 1.2.6

Version Description

  • improvement: disable scheduled exports via Automatic Scheduling UI
  • API: add new filter wp_all_export_no_cache to avoid server cache for export files
  • bug fix: saving scheduling license not working
Download this release

Release Info

Developer soflyy
Plugin Icon 128x128 Export WordPress data to XML/CSV
Version 1.2.6
Comparing to
See all releases

Code changes from version 1.2.5 to 1.2.6

Files changed (133) hide show
  1. actions/admin_init.php +0 -0
  2. actions/admin_menu.php +0 -0
  3. actions/admin_notices.php +0 -0
  4. actions/wp_loaded.php +16 -0
  5. classes/config.php +0 -0
  6. classes/download.php +3 -0
  7. classes/helper.php +0 -0
  8. classes/input.php +0 -0
  9. classes/session.php +0 -0
  10. config/options.php +1 -1
  11. controllers/admin/export.php +0 -0
  12. controllers/admin/help.php +0 -0
  13. controllers/admin/settings.php +0 -0
  14. controllers/controller.php +0 -0
  15. controllers/controller/admin.php +0 -0
  16. helpers/backward.php +0 -0
  17. helpers/pmxe_functions.php +0 -0
  18. helpers/str_getcsv.php +0 -0
  19. helpers/wp_redirect_or_javascript.php +0 -0
  20. models/export/list.php +0 -0
  21. models/export/record.php +0 -0
  22. models/model.php +0 -0
  23. models/model/list.php +0 -0
  24. models/model/record.php +0 -0
  25. readme.txt +9 -2
  26. schema.php +0 -0
  27. src/Scheduling/Scheduling.php +21 -8
  28. src/Scheduling/SchedulingApi.php +37 -12
  29. src/Scheduling/views/SchedulingOptions.php +40 -40
  30. static/css/admin-ie.css +0 -0
  31. static/css/admin-wp-3.8.css +0 -0
  32. static/css/admin.css +0 -0
  33. static/img/date-picker.gif +0 -0
  34. static/img/down.gif +0 -0
  35. static/img/drag.png +0 -0
  36. static/img/ico-add-new.png +0 -0
  37. static/img/ico-remove.png +0 -0
  38. static/img/loading.png +0 -0
  39. static/img/progress_animated.gif +0 -0
  40. static/img/screen-options-right-up.gif +0 -0
  41. static/img/screen-options-right.gif +0 -0
  42. static/img/stars.png +0 -0
  43. static/js/admin.js +0 -0
  44. static/js/jquery/css/redmond/images/animated-overlay.gif +0 -0
  45. static/js/jquery/css/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  46. static/js/jquery/css/redmond/images/ui-bg_flat_0_aaaaaa_40x100_1.png +0 -0
  47. static/js/jquery/css/redmond/images/ui-bg_flat_55_fbec88_40x100.png +0 -0
  48. static/js/jquery/css/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png +0 -0
  49. static/js/jquery/css/redmond/images/ui-bg_glass_85_dfeffc_1x400.png +0 -0
  50. static/js/jquery/css/redmond/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  51. static/js/jquery/css/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png +0 -0
  52. static/js/jquery/css/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png +0 -0
  53. static/js/jquery/css/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png +0 -0
  54. static/js/jquery/css/redmond/images/ui-icons_217bc0_256x240.png +0 -0
  55. static/js/jquery/css/redmond/images/ui-icons_2e83ff_256x240.png +0 -0
  56. static/js/jquery/css/redmond/images/ui-icons_469bdd_256x240.png +0 -0
  57. static/js/jquery/css/redmond/images/ui-icons_6da8d5_256x240.png +0 -0
  58. static/js/jquery/css/redmond/images/ui-icons_cd0a0a_256x240.png +0 -0
  59. static/js/jquery/css/redmond/images/ui-icons_d8e7f3_256x240.png +0 -0
  60. static/js/jquery/css/redmond/images/ui-icons_f9bd01_256x240.png +0 -0
  61. static/js/jquery/css/redmond/jquery-ui.css +0 -0
  62. static/js/jquery/css/select2/select2-bootstrap.css +0 -0
  63. static/js/jquery/css/select2/select2-spinner.gif +0 -0
  64. static/js/jquery/css/select2/select2.css +0 -0
  65. static/js/jquery/css/select2/select2.png +0 -0
  66. static/js/jquery/css/smoothness/images/tipsy.gif +0 -0
  67. static/js/jquery/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  68. static/js/jquery/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  69. static/js/jquery/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  70. static/js/jquery/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  71. static/js/jquery/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  72. static/js/jquery/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  73. static/js/jquery/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  74. static/js/jquery/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  75. static/js/jquery/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
  76. static/js/jquery/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  77. static/js/jquery/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
  78. static/js/jquery/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
  79. static/js/jquery/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  80. static/js/jquery/css/smoothness/jquery-ui.css +0 -0
  81. static/js/jquery/css/smoothness/jquery.tipsy.css +0 -0
  82. static/js/jquery/jquery.tipsy.js +0 -0
  83. static/js/jquery/select2.min.js +0 -0
  84. static/js/jquery/ui.autocomplete.js +0 -0
  85. static/js/jquery/ui.datepicker.js +0 -0
  86. static/js/pmxe.js +0 -0
  87. trunk/actions/admin_head.php +26 -0
  88. trunk/actions/admin_init.php +5 -0
  89. trunk/actions/admin_menu.php +22 -0
  90. trunk/actions/admin_notices.php +19 -0
  91. trunk/actions/init.php +8 -0
  92. trunk/actions/pmxe_after_export.php +417 -0
  93. trunk/actions/pmxe_before_export.php +42 -0
  94. trunk/actions/pmxe_exported_post.php +26 -0
  95. trunk/actions/wp_ajax_dismiss_export_warnings.php +23 -0
  96. trunk/actions/wp_ajax_generate_zapier_api_key.php +18 -0
  97. trunk/actions/wp_ajax_redirect_after_addon_installed.php +24 -0
  98. trunk/actions/wp_ajax_save_scheduling.php +56 -0
  99. trunk/actions/wp_ajax_scheduling_dialog_content.php +853 -0
  100. trunk/actions/wp_ajax_wpae_available_rules.php +106 -0
  101. trunk/actions/wp_ajax_wpae_filtering.php +91 -0
  102. trunk/actions/wp_ajax_wpae_filtering_count.php +372 -0
  103. trunk/actions/wp_ajax_wpae_preview.php +436 -0
  104. trunk/actions/wp_ajax_wpallexport.php +327 -0
  105. trunk/actions/wp_loaded.php +251 -0
  106. trunk/actions/wpmu_new_blog.php +21 -0
  107. trunk/banner-772x250.png +0 -0
  108. trunk/classes/CdataStrategy.php +7 -0
  109. trunk/classes/CdataStrategyAlways.php +12 -0
  110. trunk/classes/CdataStrategyFactory.php +22 -0
  111. trunk/classes/CdataStrategyIllegalCharacters.php +24 -0
  112. trunk/classes/CdataStrategyIllegalCharactersHtmlEntities.php +12 -0
  113. trunk/classes/CdataStrategyNever.php +12 -0
  114. trunk/classes/XMLWriter.php +420 -0
  115. trunk/classes/chunk.php +274 -0
  116. trunk/classes/config.php +99 -0
  117. trunk/classes/download.php +58 -0
  118. trunk/classes/handler.php +134 -0
  119. trunk/classes/helper.php +139 -0
  120. trunk/classes/input.php +81 -0
  121. trunk/classes/installer.php +40 -0
  122. trunk/classes/session.php +96 -0
  123. trunk/classes/wpallimport.php +582 -0
  124. trunk/classes/zip.php +54 -0
  125. trunk/config/options.php +22 -0
  126. trunk/controllers/admin/export.php +563 -0
  127. trunk/controllers/admin/feedback.php +12 -0
  128. trunk/controllers/admin/help.php +12 -0
  129. trunk/controllers/admin/manage.php +526 -0
  130. trunk/controllers/admin/settings.php +178 -0
  131. trunk/controllers/controller.php +182 -0
  132. trunk/controllers/controller/admin.php +129 -0
  133. trunk/dist/app.js +20043 -0
actions/admin_init.php CHANGED
File without changes
actions/admin_menu.php CHANGED
File without changes
actions/admin_notices.php CHANGED
File without changes
actions/wp_loaded.php CHANGED
@@ -206,7 +206,23 @@ function pmxe_wp_loaded() {
206
 
207
  die;
208
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  $fileurl = str_replace( "\\", "/", $fileurl );
 
210
  wp_redirect($fileurl);
211
  }
212
  else
206
 
207
  die;
208
  }
209
+
210
+ if(apply_filters('wp_all_export_no_cache', false)) {
211
+
212
+ // If we are doing a google merchants export, send the file as a download.
213
+ header("Content-type: " . mime_content_type($filepath));
214
+ header("Content-Disposition: attachment; filename=" . basename($filepath));
215
+ header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
216
+ header("Cache-Control: post-check=0, pre-check=0", false);
217
+ header("Pragma: no-cache");
218
+
219
+ readfile($filepath);
220
+
221
+ die;
222
+ }
223
+
224
  $fileurl = str_replace( "\\", "/", $fileurl );
225
+
226
  wp_redirect($fileurl);
227
  }
228
  else
classes/config.php CHANGED
File without changes
classes/download.php CHANGED
@@ -43,6 +43,9 @@ class PMXE_Download
43
  if (php_sapi_name() != 'cli-server') {
44
  header($header);
45
  header("Content-Disposition: attachment; filename=\"" . basename($file_name) . "\"");
 
 
 
46
  }
47
  while (ob_get_level()) {
48
  ob_end_clean();
43
  if (php_sapi_name() != 'cli-server') {
44
  header($header);
45
  header("Content-Disposition: attachment; filename=\"" . basename($file_name) . "\"");
46
+ header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
47
+ header("Cache-Control: post-check=0, pre-check=0", false);
48
+ header("Pragma: no-cache");
49
  }
50
  while (ob_get_level()) {
51
  ob_end_clean();
classes/helper.php CHANGED
File without changes
classes/input.php CHANGED
File without changes
classes/session.php CHANGED
File without changes
config/options.php CHANGED
@@ -4,7 +4,7 @@
4
  * and can be changed by corresponding WordPress function calls
5
  */
6
  $config = array(
7
- "info_api_url" => "http://www.wpallimport.com/export/?utm_source=export-plugin-free&utm_medium=info-api-url",
8
  "dismiss" => 0,
9
  "dismiss_manage_top" => 0,
10
  "dismiss_manage_bottom" => 0,
4
  * and can be changed by corresponding WordPress function calls
5
  */
6
  $config = array(
7
+ "info_api_url" => "http://www.wpallimport.com",
8
  "dismiss" => 0,
9
  "dismiss_manage_top" => 0,
10
  "dismiss_manage_bottom" => 0,
controllers/admin/export.php CHANGED
File without changes
controllers/admin/help.php CHANGED
File without changes
controllers/admin/settings.php CHANGED
File without changes
controllers/controller.php CHANGED
File without changes
controllers/controller/admin.php CHANGED
File without changes
helpers/backward.php CHANGED
File without changes
helpers/pmxe_functions.php CHANGED
File without changes
helpers/str_getcsv.php CHANGED
File without changes
helpers/wp_redirect_or_javascript.php CHANGED
File without changes
models/export/list.php CHANGED
File without changes
models/export/record.php CHANGED
File without changes
models/model.php CHANGED
File without changes
models/model/list.php CHANGED
File without changes
models/model/record.php CHANGED
File without changes
readme.txt CHANGED
@@ -1,8 +1,9 @@
1
  === Export any WordPress data to XML/CSV ===
2
  Contributors: soflyy, wpallimport
3
  Requires at least: 4.1
4
- Tested up to: 5.2.3
5
- Stable tag: 1.2.5
 
6
  Tags: export, wordpress csv export, wordpress xml export, export woocommerce, migrate, export csv from wordpress, export xml from wordpress, advanced xml export, advanced csv export, export data, bulk csv export, export custom post type, export woocommerce products, export woocommerce orders, migrate woocommerce, csv export, export csv, xml export, export xml, csv exporter, datafeed
7
 
8
  Easily export any data from WordPress. Drag & drop to create a completely custom spreadsheet, CSV, or XML file.
@@ -90,6 +91,12 @@ Either: -
90
  * Unzip wp-all-export.zip and upload the contents to /wp-content/plugins/, and then activate the plugin from the Plugins page in WordPress
91
 
92
  == Changelog ==
 
 
 
 
 
 
93
  = 1.2.5 =
94
  * bug fix: preserve existing admin body classes
95
 
1
  === Export any WordPress data to XML/CSV ===
2
  Contributors: soflyy, wpallimport
3
  Requires at least: 4.1
4
+ Tested up to: 5.4.1
5
+ Stable tag: 1.2.6
6
+
7
  Tags: export, wordpress csv export, wordpress xml export, export woocommerce, migrate, export csv from wordpress, export xml from wordpress, advanced xml export, advanced csv export, export data, bulk csv export, export custom post type, export woocommerce products, export woocommerce orders, migrate woocommerce, csv export, export csv, xml export, export xml, csv exporter, datafeed
8
 
9
  Easily export any data from WordPress. Drag & drop to create a completely custom spreadsheet, CSV, or XML file.
91
  * Unzip wp-all-export.zip and upload the contents to /wp-content/plugins/, and then activate the plugin from the Plugins page in WordPress
92
 
93
  == Changelog ==
94
+
95
+ = 1.2.6 =
96
+ * improvement: disable scheduled exports via Automatic Scheduling UI
97
+ * API: add new filter wp_all_export_no_cache to avoid server cache for export files
98
+ * bug fix: saving scheduling license not working
99
+
100
  = 1.2.5 =
101
  * bug fix: preserve existing admin body classes
102
 
schema.php CHANGED
File without changes
src/Scheduling/Scheduling.php CHANGED
@@ -22,15 +22,12 @@ class Scheduling
22
  $this->licensingManager = $licensingManager;
23
  }
24
 
25
- public function schedule($elementId, ScheduleTime $scheduleTime, $schedulingEnabled)
26
  {
27
  $elementId = intval($elementId);
28
 
29
- if ($schedulingEnabled == 1) {
30
- $this->enableSchedule($elementId, $scheduleTime);
31
- } else {
32
- $this->disableSchedule($elementId);
33
- }
34
  }
35
 
36
  public function scheduleExists($elementId)
@@ -90,6 +87,7 @@ class Scheduling
90
 
91
  if ($schedulingEnabled == 1) {
92
 
 
93
 
94
  if ($post['scheduling_run_on'] == 'weekly') {
95
  $monthly = false;
@@ -107,6 +105,8 @@ class Scheduling
107
  new \Wpae\Scheduling\Interval\ScheduleTime($timesArray, $monthly, $post['scheduling_timezone']),
108
  $schedulingEnabled
109
  );
 
 
110
  }
111
  }
112
 
@@ -157,9 +157,22 @@ class Scheduling
157
  $this->schedulingApi->updateSchedule($scheduleId, $scheduleTime);
158
  }
159
 
160
- private function disableSchedule($elementId)
 
 
 
 
 
 
 
 
 
161
  {
162
- $this->deleteSchedule($elementId);
 
 
 
 
163
  }
164
 
165
  public static function buildTimesArray($schedulingWeeklyDays, $schedulingTimes)
22
  $this->licensingManager = $licensingManager;
23
  }
24
 
25
+ private function schedule($elementId, ScheduleTime $scheduleTime)
26
  {
27
  $elementId = intval($elementId);
28
 
29
+ $this->enableSchedule($elementId, $scheduleTime);
30
+
 
 
 
31
  }
32
 
33
  public function scheduleExists($elementId)
87
 
88
  if ($schedulingEnabled == 1) {
89
 
90
+ $this->userEnableSchedule($id);
91
 
92
  if ($post['scheduling_run_on'] == 'weekly') {
93
  $monthly = false;
105
  new \Wpae\Scheduling\Interval\ScheduleTime($timesArray, $monthly, $post['scheduling_timezone']),
106
  $schedulingEnabled
107
  );
108
+ } else {
109
+ $this->userDisableSchedule($id);
110
  }
111
  }
112
 
157
  $this->schedulingApi->updateSchedule($scheduleId, $scheduleTime);
158
  }
159
 
160
+ public function userDisableSchedule($elementId)
161
+ {
162
+ $remoteSchedule = $this->getSchedule($elementId);
163
+
164
+ if ($remoteSchedule) {
165
+ $this->schedulingApi->disableSchedule($remoteSchedule->id);
166
+ }
167
+ }
168
+
169
+ public function userEnableSchedule($elementId)
170
  {
171
+ $remoteSchedule = $this->getSchedule($elementId);
172
+
173
+ if ($remoteSchedule) {
174
+ $this->schedulingApi->enableSchedule($remoteSchedule->id);
175
+ }
176
  }
177
 
178
  public static function buildTimesArray($schedulingWeeklyDays, $schedulingTimes)
src/Scheduling/SchedulingApi.php CHANGED
@@ -16,13 +16,13 @@ class SchedulingApi
16
 
17
  public function checkConnection()
18
  {
19
- if ( is_multisite() ) {
20
- $siteUrl = get_site_url( get_current_blog_id() );
21
  } else {
22
  $siteUrl = get_site_url();
23
  }
24
 
25
- $pingBackUrl = $this->getApiUrl('connection').'?url='.urlencode($siteUrl);
26
 
27
  $response = wp_remote_request(
28
  $pingBackUrl,
@@ -31,7 +31,7 @@ class SchedulingApi
31
  )
32
  );
33
 
34
- if($response instanceof \WP_Error) {
35
  return false;
36
  }
37
 
@@ -46,16 +46,16 @@ class SchedulingApi
46
  {
47
  $response = wp_remote_request(
48
 
49
- $this->getApiUrl('schedules?forElement='.$elementId.
50
- '&type=' . $elementType.
51
- '&endpoint='.urlencode(get_site_url())),
52
  array(
53
  'method' => 'GET',
54
  'headers' => $this->getHeaders()
55
  )
56
  );
57
 
58
- if($response instanceof \WP_Error) {
59
  return false;
60
  }
61
 
@@ -86,17 +86,29 @@ class SchedulingApi
86
  )
87
  );
88
 
89
- if($response instanceof \WP_Error) {
90
  throw new SchedulingHttpException('There was a problem saving the schedule');
91
  }
92
 
93
  return $response;
94
  }
95
 
96
- public function deleteSchedule($scheduleId)
97
  {
 
98
  wp_remote_request(
99
- $this->getApiUrl('schedules/' . $scheduleId),
 
 
 
 
 
 
 
 
 
 
 
100
  array(
101
  'method' => 'DELETE',
102
  'headers' => $this->getHeaders()
@@ -104,6 +116,19 @@ class SchedulingApi
104
  );
105
  }
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  public function updateSchedule($scheduleId, $scheduleTime)
108
  {
109
 
@@ -115,7 +140,7 @@ class SchedulingApi
115
  'body' => json_encode($scheduleTime)
116
  ));
117
 
118
- if($response instanceof \WP_Error) {
119
  throw new SchedulingHttpException('There was a problem saving the schedule');
120
  }
121
 
16
 
17
  public function checkConnection()
18
  {
19
+ if (is_multisite()) {
20
+ $siteUrl = get_site_url(get_current_blog_id());
21
  } else {
22
  $siteUrl = get_site_url();
23
  }
24
 
25
+ $pingBackUrl = $this->getApiUrl('connection') . '?url=' . urlencode($siteUrl);
26
 
27
  $response = wp_remote_request(
28
  $pingBackUrl,
31
  )
32
  );
33
 
34
+ if ($response instanceof \WP_Error) {
35
  return false;
36
  }
37
 
46
  {
47
  $response = wp_remote_request(
48
 
49
+ $this->getApiUrl('schedules?forElement=' . $elementId .
50
+ '&type=' . $elementType .
51
+ '&endpoint=' . urlencode(get_site_url())),
52
  array(
53
  'method' => 'GET',
54
  'headers' => $this->getHeaders()
55
  )
56
  );
57
 
58
+ if ($response instanceof \WP_Error) {
59
  return false;
60
  }
61
 
86
  )
87
  );
88
 
89
+ if ($response instanceof \WP_Error) {
90
  throw new SchedulingHttpException('There was a problem saving the schedule');
91
  }
92
 
93
  return $response;
94
  }
95
 
96
+ public function deleteSchedule($remoteScheduleId)
97
  {
98
+
99
  wp_remote_request(
100
+ $this->getApiUrl('schedules/' . $remoteScheduleId),
101
+ array(
102
+ 'method' => 'DELETE',
103
+ 'headers' => $this->getHeaders()
104
+ )
105
+ );
106
+ }
107
+
108
+ public function disableSchedule($remoteScheduleId)
109
+ {
110
+ wp_remote_request(
111
+ $this->getApiUrl('schedules/' . $remoteScheduleId . '/disable'),
112
  array(
113
  'method' => 'DELETE',
114
  'headers' => $this->getHeaders()
116
  );
117
  }
118
 
119
+
120
+ public function enableSchedule($scheduleId)
121
+ {
122
+ wp_remote_request(
123
+ $this->getApiUrl('schedules/' . $scheduleId . '/enable'),
124
+ array(
125
+ 'method' => 'POST',
126
+ 'headers' => $this->getHeaders()
127
+ )
128
+ );
129
+ }
130
+
131
+
132
  public function updateSchedule($scheduleId, $scheduleTime)
133
  {
134
 
140
  'body' => json_encode($scheduleTime)
141
  ));
142
 
143
+ if ($response instanceof \WP_Error) {
144
  throw new SchedulingHttpException('There was a problem saving the schedule');
145
  }
146
 
src/Scheduling/views/SchedulingOptions.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
  $scheduling = \Wpae\Scheduling\Scheduling::create();
3
- $post = $export->options;
4
  $hasActiveLicense = $scheduling->checkLicense();
5
  $cron_job_key = PMXE_Plugin::getInstance()->getOption('cron_job_key');
6
  $options = \PMXE_Plugin::getInstance()->getOption();
@@ -153,7 +153,7 @@ $options = \PMXE_Plugin::getInstance()->getOption();
153
 
154
  #add-subscription-field {
155
  position: absolute;
156
- left: -155px;
157
  top: -2px;
158
  height: 46px;
159
  border-radius: 5px;
@@ -392,7 +392,7 @@ $options = \PMXE_Plugin::getInstance()->getOption();
392
  }
393
 
394
  // Don't process scheduling
395
- if (!schedulingEnable) {
396
  var submitEvent = $.Event('wpae-scheduling-options-form:submit');
397
  $(document).trigger(submitEvent);
398
 
@@ -409,7 +409,7 @@ $options = \PMXE_Plugin::getInstance()->getOption();
409
  e.preventDefault();
410
  return false;
411
  }
412
-
413
  var $button = $(this);
414
 
415
  var formData = $('#scheduling-form :input').serializeArray();
@@ -524,22 +524,22 @@ $options = \PMXE_Plugin::getInstance()->getOption();
524
  return false;
525
  });
526
 
527
- <?php if($post['scheduling_timezone'] == 'UTC') {
528
  ?>
529
- var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
530
-
531
- if($('#timezone').find("option:contains('"+ timeZone +"')").length != 0){
532
- $('#timezone').trigger("chosen:updated");
533
- $('#timezone').val(timeZone);
534
- $('#timezone').trigger("chosen:updated");
535
- }else{
536
- var parts = timeZone.split('/');
537
- var lastPart = parts[parts.length-1];
538
- var opt = $('#timezone').find("option:contains('"+ lastPart +"')");
539
-
540
- $('#timezone').val(opt.val());
541
- $('#timezone').trigger("chosen:updated");
542
- }
543
 
544
  <?php
545
  }
@@ -609,7 +609,7 @@ $options = \PMXE_Plugin::getInstance()->getOption();
609
 
610
  setTimeout(function () {
611
  $('#add-subscription-field').animate({width:'140px'}, 225);
612
- $('#add-subscription-field').animate({left:'-155px'}, 225);
613
  }, 300);
614
 
615
  $('#add-subscription-field').val('');
@@ -668,20 +668,20 @@ $options = \PMXE_Plugin::getInstance()->getOption();
668
  <div class="wpallexport-collapsed-content-inner" style="padding-bottom: 0; overflow: auto;">
669
  <div style="margin-bottom: 20px;">
670
  <label>
671
- <input type="radio" name="scheduling_enable" value="0" <?php if(!$post['scheduling_enable']) { ?> checked="checked" <?php } ?>/>
672
  <h4 style="display: inline-block; margin-top:3px; margin-bottom:-2px;"><?php _e('Do Not Schedule'); ?></h4>
673
  </label>
674
  </div>
675
  <div>
676
  <label>
677
- <input type="radio" name="scheduling_enable" value="1" <?php if($post['scheduling_enable'] == 1) {?> checked="checked" <?php }?>/>
678
 
679
  <h4 style="margin: 0; position: relative; display: inline-block;"><?php _e('Automatic Scheduling', PMXE_Plugin::LANGUAGE_DOMAIN); ?>
680
- <span class="connection-icon">
681
  <?php include_once('ConnectionIcon.php'); ?>
682
  </span>
683
  <?php if (!$scheduling->checkConnection()) { ?>
684
- <span class="wpai-license wpai-license-text" style="display: inline-block; font-weight: normal; <?php if(!$hasActiveLicense) { ?> display: none; <?php }?> color: #f2b03d; ">Unable to connect - <a target="_blank" style="text-decoration: underline;" href="http://wpallimport.com/support">please contact support</a>.</span>
685
  <?php } ?>
686
  </h4>
687
  </label>
@@ -692,26 +692,26 @@ $options = \PMXE_Plugin::getInstance()->getOption();
692
  </label>
693
  </div>
694
  <div id="automatic-scheduling"
695
- style="margin-left: 21px; <?php if ($post['scheduling_enable'] != 1) { ?> display: none; <?php } ?>">
696
  <div>
697
  <div class="input">
698
  <label style="color: rgb(68,68,68);">
699
  <input
700
- type="radio" <?php if ($post['scheduling_run_on'] != 'monthly') { ?> checked="checked" <?php } ?>
701
  name="scheduling_run_on" value="weekly"
702
  checked="checked"/> <?php _e('Every week on...', PMXE_Plugin::LANGUAGE_DOMAIN); ?>
703
  </label>
704
  </div>
705
  <input type="hidden" style="width: 500px;" name="scheduling_weekly_days"
706
- value="<?php echo $post['scheduling_weekly_days']; ?>" id="weekly_days"/>
707
  <?php
708
- if (isset($post['scheduling_weekly_days'])) {
709
- $weeklyArray = explode(',', $post['scheduling_weekly_days']);
710
  } else {
711
  $weeklyArray = array();
712
  }
713
  ?>
714
- <ul class="days-of-week" id="weekly" style="<?php if ($post['scheduling_run_on'] == 'monthly') { ?> display: none; <?php } ?>">
715
  <li data-day="0" <?php if (in_array('0', $weeklyArray)) { ?> class="selected" <?php } ?>>
716
  Mon
717
  </li>
@@ -740,21 +740,21 @@ $options = \PMXE_Plugin::getInstance()->getOption();
740
  <div class="input">
741
  <label style="color: rgb(68,68,68);">
742
  <input
743
- type="radio" <?php if ($post['scheduling_run_on'] == 'monthly') { ?> checked="checked" <?php } ?>
744
  name="scheduling_run_on"
745
  value="monthly"/> <?php _e('Every month on the first...', PMXE_Plugin::LANGUAGE_DOMAIN); ?>
746
  </label>
747
  </div>
748
- <input type="hidden" name="scheduling_monthly_days" value="<?php if (isset($post['scheduling_monthly_days'])) echo $post['scheduling_monthly_days']; ?>" id="monthly_days"/>
749
  <?php
750
- if (isset($post['scheduling_monthly_days'])) {
751
- $monthlyArray = explode(',', $post['scheduling_monthly_days']);
752
  } else {
753
  $monthlyArray = array();
754
  }
755
  ?>
756
  <ul class="days-of-week" id="monthly"
757
- style="<?php if ($post['scheduling_run_on'] != 'monthly') { ?> display: none; <?php } ?>">
758
  <li data-day="0" <?php if (in_array('0', $monthlyArray)) { ?> class="selected" <?php } ?>>
759
  Mon
760
  </li>
@@ -786,8 +786,8 @@ $options = \PMXE_Plugin::getInstance()->getOption();
786
  </div>
787
 
788
  <div id="times" style="margin-bottom: 10px;">
789
- <?php if (is_array($post['scheduling_times'])) {
790
- foreach ($post['scheduling_times'] as $time) { ?>
791
 
792
  <?php if ($time) { ?>
793
  <input class="timepicker" type="text" name="scheduling_times[]"
@@ -802,8 +802,8 @@ $options = \PMXE_Plugin::getInstance()->getOption();
802
  <?php
803
 
804
  $timezoneValue = false;
805
- if ($post['scheduling_timezone']) {
806
- $timezoneValue = $post['scheduling_timezone'];
807
  }
808
 
809
  $timezoneSelect = new \Wpae\Scheduling\Timezone\TimezoneSelect();
@@ -818,7 +818,7 @@ $options = \PMXE_Plugin::getInstance()->getOption();
818
  <div class="subscribe" style="margin-left: 5px; margin-top: 65px; margin-bottom: 130px; position: relative;">
819
  <div class="button-container">
820
 
821
- <a href="https://www.wpallimport.com/checkout/?edd_action=add_to_cart&download_id=515704&utm_source=export-plugin-free&utm_medium=upgrade-notice&utm_campaign=automatic-scheduling" target="_blank" id="subscribe-button">
822
  <div class="button button-primary button-hero wpallexport-large-button button-subscribe"
823
  style="background-image: none; width: 140px; text-align: center; position: absolute; z-index: 4;">
824
  <svg class="success" width="30" height="30" viewBox="0 0 1792 1792"
1
  <?php
2
  $scheduling = \Wpae\Scheduling\Scheduling::create();
3
+ $schedulingExportOptions = $export->options;
4
  $hasActiveLicense = $scheduling->checkLicense();
5
  $cron_job_key = PMXE_Plugin::getInstance()->getOption('cron_job_key');
6
  $options = \PMXE_Plugin::getInstance()->getOption();
153
 
154
  #add-subscription-field {
155
  position: absolute;
156
+ left: -152px;
157
  top: -2px;
158
  height: 46px;
159
  border-radius: 5px;
392
  }
393
 
394
  // Don't process scheduling
395
+ if (!hasActiveLicense) {
396
  var submitEvent = $.Event('wpae-scheduling-options-form:submit');
397
  $(document).trigger(submitEvent);
398
 
409
  e.preventDefault();
410
  return false;
411
  }
412
+
413
  var $button = $(this);
414
 
415
  var formData = $('#scheduling-form :input').serializeArray();
524
  return false;
525
  });
526
 
527
+ <?php if($schedulingExportOptions['scheduling_timezone'] == 'UTC') {
528
  ?>
529
+ var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
530
+
531
+ if($('#timezone').find("option:contains('"+ timeZone +"')").length != 0){
532
+ $('#timezone').trigger("chosen:updated");
533
+ $('#timezone').val(timeZone);
534
+ $('#timezone').trigger("chosen:updated");
535
+ }else{
536
+ var parts = timeZone.split('/');
537
+ var lastPart = parts[parts.length-1];
538
+ var opt = $('#timezone').find("option:contains('"+ lastPart +"')");
539
+
540
+ $('#timezone').val(opt.val());
541
+ $('#timezone').trigger("chosen:updated");
542
+ }
543
 
544
  <?php
545
  }
609
 
610
  setTimeout(function () {
611
  $('#add-subscription-field').animate({width:'140px'}, 225);
612
+ $('#add-subscription-field').animate({left:'-152px'}, 225);
613
  }, 300);
614
 
615
  $('#add-subscription-field').val('');
668
  <div class="wpallexport-collapsed-content-inner" style="padding-bottom: 0; overflow: auto;">
669
  <div style="margin-bottom: 20px;">
670
  <label>
671
+ <input type="radio" name="scheduling_enable" value="0" <?php if(!$schedulingExportOptions['scheduling_enable']) { ?> checked="checked" <?php } ?>/>
672
  <h4 style="display: inline-block; margin-top:3px; margin-bottom:-2px;"><?php _e('Do Not Schedule'); ?></h4>
673
  </label>
674
  </div>
675
  <div>
676
  <label>
677
+ <input type="radio" name="scheduling_enable" value="1" <?php if($schedulingExportOptions['scheduling_enable'] == 1) {?> checked="checked" <?php }?>/>
678
 
679
  <h4 style="margin: 0; position: relative; display: inline-block;"><?php _e('Automatic Scheduling', PMXE_Plugin::LANGUAGE_DOMAIN); ?>
680
+ <span class="connection-icon" style="position: absolute; top:-1px; left: 152px;">
681
  <?php include_once('ConnectionIcon.php'); ?>
682
  </span>
683
  <?php if (!$scheduling->checkConnection()) { ?>
684
+ <span class="wpai-license" style="margin-left: 25px; display: inline-block; font-weight: normal; <?php if(!$hasActiveLicense) { ?> display: none; <?php }?> color: #f2b03d; ">Unable to connect - <a target="_blank" style="text-decoration: underline;" href="http://wpallimport.com/support">please contact support</a>.</span>
685
  <?php } ?>
686
  </h4>
687
  </label>
692
  </label>
693
  </div>
694
  <div id="automatic-scheduling"
695
+ style="margin-left: 21px; <?php if ($schedulingExportOptions['scheduling_enable'] != 1) { ?> display: none; <?php } ?>">
696
  <div>
697
  <div class="input">
698
  <label style="color: rgb(68,68,68);">
699
  <input
700
+ type="radio" <?php if ($schedulingExportOptions['scheduling_run_on'] != 'monthly') { ?> checked="checked" <?php } ?>
701
  name="scheduling_run_on" value="weekly"
702
  checked="checked"/> <?php _e('Every week on...', PMXE_Plugin::LANGUAGE_DOMAIN); ?>
703
  </label>
704
  </div>
705
  <input type="hidden" style="width: 500px;" name="scheduling_weekly_days"
706
+ value="<?php echo $schedulingExportOptions['scheduling_weekly_days']; ?>" id="weekly_days"/>
707
  <?php
708
+ if (isset($schedulingExportOptions['scheduling_weekly_days'])) {
709
+ $weeklyArray = explode(',', $schedulingExportOptions['scheduling_weekly_days']);
710
  } else {
711
  $weeklyArray = array();
712
  }
713
  ?>
714
+ <ul class="days-of-week" id="weekly" style="<?php if ($schedulingExportOptions['scheduling_run_on'] == 'monthly') { ?> display: none; <?php } ?>">
715
  <li data-day="0" <?php if (in_array('0', $weeklyArray)) { ?> class="selected" <?php } ?>>
716
  Mon
717
  </li>
740
  <div class="input">
741
  <label style="color: rgb(68,68,68);">
742
  <input
743
+ type="radio" <?php if ($schedulingExportOptions['scheduling_run_on'] == 'monthly') { ?> checked="checked" <?php } ?>
744
  name="scheduling_run_on"
745
  value="monthly"/> <?php _e('Every month on the first...', PMXE_Plugin::LANGUAGE_DOMAIN); ?>
746
  </label>
747
  </div>
748
+ <input type="hidden" name="scheduling_monthly_days" value="<?php if (isset($schedulingExportOptions['scheduling_monthly_days'])) echo $schedulingExportOptions['scheduling_monthly_days']; ?>" id="monthly_days"/>
749
  <?php
750
+ if (isset($schedulingExportOptions['scheduling_monthly_days'])) {
751
+ $monthlyArray = explode(',', $schedulingExportOptions['scheduling_monthly_days']);
752
  } else {
753
  $monthlyArray = array();
754
  }
755
  ?>
756
  <ul class="days-of-week" id="monthly"
757
+ style="<?php if ($schedulingExportOptions['scheduling_run_on'] != 'monthly') { ?> display: none; <?php } ?>">
758
  <li data-day="0" <?php if (in_array('0', $monthlyArray)) { ?> class="selected" <?php } ?>>
759
  Mon
760
  </li>
786
  </div>
787
 
788
  <div id="times" style="margin-bottom: 10px;">
789
+ <?php if (is_array($schedulingExportOptions['scheduling_times'])) {
790
+ foreach ($schedulingExportOptions['scheduling_times'] as $time) { ?>
791
 
792
  <?php if ($time) { ?>
793
  <input class="timepicker" type="text" name="scheduling_times[]"
802
  <?php
803
 
804
  $timezoneValue = false;
805
+ if ($schedulingExportOptions['scheduling_timezone']) {
806
+ $timezoneValue = $schedulingExportOptions['scheduling_timezone'];
807
  }
808
 
809
  $timezoneSelect = new \Wpae\Scheduling\Timezone\TimezoneSelect();
818
  <div class="subscribe" style="margin-left: 5px; margin-top: 65px; margin-bottom: 130px; position: relative;">
819
  <div class="button-container">
820
 
821
+ <a href="https://www.wpallimport.com/checkout/?edd_action=add_to_cart&download_id=515704" target="_blank" id="subscribe-button">
822
  <div class="button button-primary button-hero wpallexport-large-button button-subscribe"
823
  style="background-image: none; width: 140px; text-align: center; position: absolute; z-index: 4;">
824
  <svg class="success" width="30" height="30" viewBox="0 0 1792 1792"
static/css/admin-ie.css CHANGED
File without changes
static/css/admin-wp-3.8.css CHANGED
File without changes
static/css/admin.css CHANGED
File without changes
static/img/date-picker.gif CHANGED
File without changes
static/img/down.gif CHANGED
File without changes
static/img/drag.png CHANGED
File without changes
static/img/ico-add-new.png CHANGED
File without changes
static/img/ico-remove.png CHANGED
File without changes
static/img/loading.png CHANGED
File without changes
static/img/progress_animated.gif CHANGED
File without changes
static/img/screen-options-right-up.gif CHANGED
File without changes
static/img/screen-options-right.gif CHANGED
File without changes
static/img/stars.png CHANGED
File without changes
static/js/admin.js CHANGED
File without changes
static/js/jquery/css/redmond/images/animated-overlay.gif CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-bg_flat_0_aaaaaa_40x100_1.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-bg_flat_55_fbec88_40x100.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-bg_glass_85_dfeffc_1x400.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-bg_glass_95_fef1ec_1x400.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-icons_217bc0_256x240.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-icons_2e83ff_256x240.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-icons_469bdd_256x240.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-icons_6da8d5_256x240.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-icons_cd0a0a_256x240.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-icons_d8e7f3_256x240.png CHANGED
File without changes
static/js/jquery/css/redmond/images/ui-icons_f9bd01_256x240.png CHANGED
File without changes
static/js/jquery/css/redmond/jquery-ui.css CHANGED
File without changes
static/js/jquery/css/select2/select2-bootstrap.css CHANGED
File without changes
static/js/jquery/css/select2/select2-spinner.gif CHANGED
File without changes
static/js/jquery/css/select2/select2.css CHANGED
File without changes
static/js/jquery/css/select2/select2.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/tipsy.gif CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-icons_222222_256x240.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-icons_2e83ff_256x240.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-icons_454545_256x240.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-icons_888888_256x240.png CHANGED
File without changes
static/js/jquery/css/smoothness/images/ui-icons_cd0a0a_256x240.png CHANGED
File without changes
static/js/jquery/css/smoothness/jquery-ui.css CHANGED
File without changes
static/js/jquery/css/smoothness/jquery.tipsy.css CHANGED
File without changes
static/js/jquery/jquery.tipsy.js CHANGED
File without changes
static/js/jquery/select2.min.js CHANGED
File without changes
static/js/jquery/ui.autocomplete.js CHANGED
File without changes
static/js/jquery/ui.datepicker.js CHANGED
File without changes
static/js/pmxe.js CHANGED
File without changes
trunk/actions/admin_head.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ function pmxe_admin_head(){
3
+ $input = new PMXE_Input();
4
+ $export_id = $input->get('id', false);
5
+ $export_action = $input->get('action', false);
6
+ if ($export_id){
7
+ ?>
8
+ <script type="text/javascript">
9
+ var export_id = '<?php echo $export_id; ?>';
10
+ </script>
11
+ <?php
12
+ }
13
+
14
+ $wp_all_export_ajax_nonce = wp_create_nonce("wp_all_export_secure");
15
+
16
+ ?>
17
+ <script type="text/javascript" id="googleMerchantsInit">
18
+ if(typeof GoogleMerchants != 'undefined') {
19
+ GoogleMerchants.constant('NONCE', '<?php echo $wp_all_export_ajax_nonce; ?>');
20
+ }
21
+ var ajaxurl = '<?php echo admin_url( "admin-ajax.php" ); ?>';
22
+ var export_action = '<?php echo $export_action; ?>';
23
+ var wp_all_export_security = '<?php echo $wp_all_export_ajax_nonce; ?>';
24
+ </script>
25
+ <?php
26
+ }
trunk/actions/admin_init.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_admin_init(){
4
+ wp_enqueue_script('pmxe-script', PMXE_ROOT_URL . '/static/js/pmxe.js', array('jquery'), PMXE_VERSION);
5
+ }
trunk/actions/admin_menu.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Register plugin specific admin menu
4
+ */
5
+
6
+ function pmxe_admin_menu() {
7
+ global $menu, $submenu;
8
+
9
+ if (current_user_can( PMXE_Plugin::$capabilities )) { // admin management options
10
+
11
+ add_menu_page(__('WP All Export', 'wp_all_export_plugin'), __('All Export', 'wp_all_export_plugin'), PMXE_Plugin::$capabilities, 'pmxe-admin-home', array(PMXE_Plugin::getInstance(), 'adminDispatcher'), PMXE_Plugin::ROOT_URL . '/static/img/xmlicon.png', 111);
12
+ // workaround to rename 1st option to `Home`
13
+ $submenu['pmxe-admin-home'] = array();
14
+ add_submenu_page('pmxe-admin-home', __('Export to XML', 'wp_all_export_plugin') . ' &lsaquo; ' . __('WP All Export', 'wp_all_export_plugin'), __('New Export', 'wp_all_export_plugin'), PMXE_Plugin::$capabilities, 'pmxe-admin-export', array(PMXE_Plugin::getInstance(), 'adminDispatcher'));
15
+ add_submenu_page('pmxe-admin-home', __('Manage Exports', 'wp_all_export_plugin') . ' &lsaquo; ' . __('WP All Export', 'wp_all_export_plugin'), __('Manage Exports', 'wp_all_export_plugin'), PMXE_Plugin::$capabilities, 'pmxe-admin-manage', array(PMXE_Plugin::getInstance(), 'adminDispatcher'));
16
+ add_submenu_page('pmxe-admin-home', __('Settings', 'wp_all_export_plugin') . ' &lsaquo; ' . __('WP All Export', 'wp_all_export_plugin'), __('Settings', 'wp_all_export_plugin'), PMXE_Plugin::$capabilities, 'pmxe-admin-settings', array(PMXE_Plugin::getInstance(), 'adminDispatcher'));
17
+ // add_submenu_page('pmxe-admin-home', __('Feedback', 'wp_all_export_plugin') . ' &lsaquo; ' . __('WP All Export', 'wp_all_export_plugin'), __('Feedback', 'wp_all_export_plugin'), PMXE_Plugin::$capabilities, 'pmxe-admin-feedback', array(PMXE_Plugin::getInstance(), 'adminDispatcher'));
18
+ //add_submenu_page('pmxe-admin-home', __('Support', 'wp_all_export_plugin') . ' &lsaquo; ' . __('WP All Export', 'wp_all_export_plugin'), __('Support', 'wp_all_export_plugin'), 'manage_options', 'pmxe-admin-help', array(PMXE_Plugin::getInstance(), 'adminDispatcher'));
19
+
20
+ }
21
+ }
22
+
trunk/actions/admin_notices.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_admin_notices() {
4
+
5
+ // notify user if history folder is not writable
6
+ $uploads = wp_upload_dir();
7
+
8
+ $input = new PMXE_Input();
9
+ $messages = $input->get('pmxe_nt', array());
10
+ if ($messages) {
11
+ is_array($messages) or $messages = array($messages);
12
+ foreach ($messages as $type => $m) {
13
+ in_array((string)$type, array('updated', 'error')) or $type = 'updated';
14
+ ?>
15
+ <div class="<?php echo $type ?>"><p><?php echo $m ?></p></div>
16
+ <?php
17
+ }
18
+ }
19
+ }
trunk/actions/init.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_init()
4
+ {
5
+ if(!empty($_GET['check_connection'])) {
6
+ exit(json_encode(array('success' => true)));
7
+ }
8
+ }
trunk/actions/pmxe_after_export.php ADDED
@@ -0,0 +1,417 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_prepend($string, $orig_filename) {
4
+ $context = stream_context_create();
5
+ $orig_file = fopen($orig_filename, 'r', 1, $context);
6
+
7
+ $temp_filename = tempnam(sys_get_temp_dir(), 'php_prepend_');
8
+ file_put_contents($temp_filename, $string);
9
+ file_put_contents($temp_filename, $orig_file, FILE_APPEND);
10
+
11
+ fclose($orig_file);
12
+ unlink($orig_filename);
13
+ rename($temp_filename, $orig_filename);
14
+ }
15
+
16
+ function pmxe_pmxe_after_export($export_id, $export)
17
+ {
18
+ if ( ! empty(PMXE_Plugin::$session) and PMXE_Plugin::$session->has_session() )
19
+ {
20
+ PMXE_Plugin::$session->set('file', '');
21
+ PMXE_Plugin::$session->save_data();
22
+ }
23
+
24
+ if ( ! $export->isEmpty())
25
+ {
26
+
27
+ $export->set(
28
+ array(
29
+ 'registered_on' => current_time( 'mysql', 1 ),
30
+ )
31
+ )->save();
32
+
33
+ $splitSize = $export->options['split_large_exports_count'];
34
+
35
+ $exportOptions = $export->options;
36
+ // remove previously genereted chunks
37
+ if ( ! empty($exportOptions['split_files_list']) and ! $export->options['creata_a_new_export_file'] )
38
+ {
39
+ foreach ($exportOptions['split_files_list'] as $file) {
40
+ @unlink($file);
41
+ }
42
+ }
43
+
44
+ $is_secure_import = PMXE_Plugin::getInstance()->getOption('secure');
45
+
46
+ if ( ! $is_secure_import)
47
+ {
48
+ $filepath = get_attached_file($export->attch_id);
49
+ }
50
+ else
51
+ {
52
+ $filepath = wp_all_export_get_absolute_path($export->options['filepath']);
53
+ }
54
+
55
+ //TODO: Look into what is happening with this variable and what it is used for
56
+ $is_export_csv_headers = apply_filters('wp_all_export_is_csv_headers_enabled', true, $export->id);
57
+
58
+ if ( isset($export->options['include_header_row']) ) {
59
+ $is_export_csv_headers = $export->options['include_header_row'];
60
+ }
61
+
62
+ $removeHeaders = false;
63
+
64
+ $removeHeaders = apply_filters('wp_all_export_remove_csv_headers', $removeHeaders, $export->id);
65
+
66
+ // Remove headers row from CSV file
67
+ if ( (empty($is_export_csv_headers) && @file_exists($filepath) && $export->options['export_to'] == 'csv' && $export->options['export_to_sheet'] == 'csv') || $removeHeaders){
68
+
69
+ $tmp_file = str_replace(basename($filepath), 'iteration_' . basename($filepath), $filepath);
70
+ copy($filepath, $tmp_file);
71
+ $in = fopen($tmp_file, 'r');
72
+ $out = fopen($filepath, 'w');
73
+
74
+ $headers = fgetcsv($in, 0, XmlExportEngine::$exportOptions['delimiter']);
75
+
76
+ if (is_resource($in)) {
77
+ $lineNumber = 0;
78
+ while ( ! feof($in) ) {
79
+ $data = fgetcsv($in, 0, XmlExportEngine::$exportOptions['delimiter']);
80
+ if ( empty($data) ) continue;
81
+ $data_assoc = array_combine($headers, array_values($data));
82
+ $line = array();
83
+ foreach ($headers as $header) {
84
+ $line[$header] = ( isset($data_assoc[$header]) ) ? $data_assoc[$header] : '';
85
+ }
86
+ if ( ! $lineNumber && XmlExportEngine::$exportOptions['include_bom']){
87
+ fwrite($out, chr(0xEF).chr(0xBB).chr(0xBF));
88
+ fputcsv($out, $line, XmlExportEngine::$exportOptions['delimiter']);
89
+ }
90
+ else{
91
+ fputcsv($out, $line, XmlExportEngine::$exportOptions['delimiter']);
92
+ }
93
+ apply_filters('wp_all_export_after_csv_line', $out, XmlExportEngine::$exportID);
94
+ $lineNumber++;
95
+ }
96
+ fclose($in);
97
+ }
98
+ fclose($out);
99
+ @unlink($tmp_file);
100
+ }
101
+
102
+ $preCsvHeaders = false;
103
+ $preCsvHeaders = apply_filters('wp_all_export_pre_csv_headers', $preCsvHeaders, $export->id);
104
+
105
+ if($preCsvHeaders) {
106
+ pmxe_prepend($preCsvHeaders."\n", $filepath);
107
+ }
108
+
109
+ // Split large exports into chunks
110
+ if ( $export->options['split_large_exports'] and $splitSize < $export->exported )
111
+ {
112
+
113
+ $exportOptions['split_files_list'] = array();
114
+
115
+ if ( @file_exists($filepath) )
116
+ {
117
+
118
+ switch ($export->options['export_to'])
119
+ {
120
+ case 'xml':
121
+
122
+ require_once PMXE_ROOT_DIR . '/classes/XMLWriter.php';
123
+
124
+ switch ( $export->options['xml_template_type'])
125
+ {
126
+ case 'XmlGoogleMerchants':
127
+ case 'custom':
128
+ // Determine XML root element
129
+ $main_xml_tag = false;
130
+ preg_match_all("%<[\w]+[\s|>]{1}%", $export->options['custom_xml_template_header'], $matches);
131
+ if ( ! empty($matches[0]) ){
132
+ $main_xml_tag = preg_replace("%[\s|<|>]%","",array_shift($matches[0]));
133
+ }
134
+ // Determine XML recond element
135
+ $record_xml_tag = false;
136
+ preg_match_all("%<[\w]+[\s|>]{1}%", $export->options['custom_xml_template_loop'], $matches);
137
+ if ( ! empty($matches[0]) ){
138
+ $record_xml_tag = preg_replace("%[\s|<|>]%","",array_shift($matches[0]));
139
+ }
140
+
141
+ $xml_header = PMXE_XMLWriter::preprocess_xml($export->options['custom_xml_template_header']);
142
+ $xml_footer = PMXE_XMLWriter::preprocess_xml($export->options['custom_xml_template_footer']);
143
+
144
+ break;
145
+
146
+ default:
147
+ $main_xml_tag = apply_filters('wp_all_export_main_xml_tag', $export->options['main_xml_tag'], $export->id);
148
+ $record_xml_tag = apply_filters('wp_all_export_record_xml_tag', $export->options['record_xml_tag'], $export->id);
149
+ $xml_header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" . "\n" . "<".$main_xml_tag.">";
150
+ $xml_footer = "</".$main_xml_tag.">";
151
+ break;
152
+
153
+ }
154
+
155
+
156
+ $records_count = 0;
157
+ $chunk_records_count = 0;
158
+ $fileCount = 1;
159
+
160
+ $feed = $xml_header;
161
+
162
+ if($export->options['xml_template_type'] == 'custom'){
163
+ $outputFileTemplate = str_replace(basename($filepath), str_replace('.xml', '', basename($filepath)) . '-{FILE_COUNT_PLACEHOLDER}.xml', $filepath);
164
+ $exportOptions['split_files_list'] = wp_all_export_break_into_files($record_xml_tag, -1, $splitSize, file_get_contents($filepath), null, $outputFileTemplate);
165
+
166
+ // Remove first file which just contains the empty data tag
167
+ @unlink($exportOptions['split_files_list'][0]);
168
+ array_shift($exportOptions['split_files_list']);
169
+ }
170
+ else {
171
+ $file = new PMXE_Chunk($filepath, array('element' => $record_xml_tag, 'encoding' => 'UTF-8'));
172
+ // loop through the file until all lines are read
173
+ while ($xml = $file->read()) {
174
+
175
+ if ( ! empty($xml) )
176
+ {
177
+ $records_count++;
178
+ $chunk_records_count++;
179
+ $feed .= $xml;
180
+ }
181
+
182
+ if ( $chunk_records_count == $splitSize or $records_count == $export->exported ){
183
+ $feed .= "\n".$xml_footer;
184
+ $outputFile = str_replace(basename($filepath), str_replace('.xml', '', basename($filepath)) . '-' . $fileCount++ . '.xml', $filepath);
185
+ file_put_contents($outputFile, $feed);
186
+ if ( ! in_array($outputFile, $exportOptions['split_files_list']))
187
+ $exportOptions['split_files_list'][] = $outputFile;
188
+ $chunk_records_count = 0;
189
+ $feed = $xml_header;
190
+ }
191
+ }
192
+ }
193
+
194
+ break;
195
+ case 'csv':
196
+ $in = fopen($filepath, 'r');
197
+
198
+ $rowCount = 0;
199
+ $fileCount = 1;
200
+ $headers = fgetcsv($in);
201
+ while (!feof($in)) {
202
+ $data = fgetcsv($in);
203
+ if (empty($data)) continue;
204
+ if (($rowCount % $splitSize) == 0) {
205
+ if ($rowCount > 0) {
206
+ fclose($out);
207
+ }
208
+ $outputFile = str_replace(basename($filepath), str_replace('.csv', '', basename($filepath)) . '-' . $fileCount++ . '.csv', $filepath);
209
+ if ( ! in_array($outputFile, $exportOptions['split_files_list']))
210
+ $exportOptions['split_files_list'][] = $outputFile;
211
+
212
+ $out = fopen($outputFile, 'w');
213
+ }
214
+ if ($data){
215
+ if (($rowCount % $splitSize) == 0) {
216
+ fputcsv($out, $headers);
217
+ }
218
+ fputcsv($out, $data);
219
+ }
220
+ $rowCount++;
221
+ }
222
+ fclose($in);
223
+ fclose($out);
224
+
225
+ // convert splitted files into XLS format
226
+ if ( ! empty($exportOptions['split_files_list']) && ! empty($export->options['export_to_sheet']) and $export->options['export_to_sheet'] != 'csv' )
227
+ {
228
+ require_once PMXE_Plugin::ROOT_DIR . '/classes/PHPExcel/IOFactory.php';
229
+
230
+ foreach ($exportOptions['split_files_list'] as $key => $file)
231
+ {
232
+ $objReader = PHPExcel_IOFactory::createReader('CSV');
233
+ // If the files uses a delimiter other than a comma (e.g. a tab), then tell the reader
234
+ $objReader->setDelimiter($export->options['delimiter']);
235
+ // If the files uses an encoding other than UTF-8 or ASCII, then tell the reader
236
+ $objPHPExcel = $objReader->load($file);
237
+ switch ($export->options['export_to_sheet']){
238
+ case 'xls':
239
+ $objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
240
+ $objWriter->save(str_replace(".csv", ".xls", $file));
241
+ $exportOptions['split_files_list'][$key] = str_replace(".csv", ".xls", $file);
242
+ break;
243
+ case 'xlsx':
244
+ $objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
245
+ $objWriter->save(str_replace(".csv", ".xlsx", $file));
246
+ $exportOptions['split_files_list'][$key] = str_replace(".csv", ".xlsx", $file);
247
+ break;
248
+ }
249
+ @unlink($file);
250
+ }
251
+ }
252
+
253
+ break;
254
+
255
+ default:
256
+
257
+ break;
258
+ }
259
+
260
+ $export->set(array('options' => $exportOptions))->save();
261
+ }
262
+ }
263
+
264
+ // convert CSV to XLS
265
+ if ( @file_exists($filepath) and $export->options['export_to'] == 'csv' && ! empty($export->options['export_to_sheet']) and $export->options['export_to_sheet'] != 'csv')
266
+ {
267
+
268
+ require_once PMXE_Plugin::ROOT_DIR . '/classes/PHPExcel/IOFactory.php';
269
+
270
+ $objReader = PHPExcel_IOFactory::createReader('CSV');
271
+ // If the files uses a delimiter other than a comma (e.g. a tab), then tell the reader
272
+ $objReader->setDelimiter($export->options['delimiter']);
273
+ // If the files uses an encoding other than UTF-8 or ASCII, then tell the reader
274
+
275
+ $objPHPExcel = $objReader->load($filepath);
276
+
277
+ switch ($export->options['export_to_sheet']) {
278
+ case 'xls':
279
+ $objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
280
+ $objWriter->save(str_replace(".csv", ".xls", $filepath));
281
+ @unlink($filepath);
282
+ $filepath = str_replace(".csv", ".xls", $filepath);
283
+ break;
284
+ case 'xlsx':
285
+ $objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
286
+ $objWriter->save(str_replace(".csv", ".xlsx", $filepath));
287
+ @unlink($filepath);
288
+ $filepath = str_replace(".csv", ".xlsx", $filepath);
289
+ break;
290
+ }
291
+
292
+ $exportOptions = $export->options;
293
+ $exportOptions['filepath'] = wp_all_export_get_relative_path($filepath);
294
+ $export->set(array('options' => $exportOptions))->save();
295
+
296
+ $is_secure_import = PMXE_Plugin::getInstance()->getOption('secure');
297
+
298
+ if ( ! $is_secure_import ){
299
+ $wp_uploads = wp_upload_dir();
300
+ $wp_filetype = wp_check_filetype(basename($filepath), null );
301
+ $attachment_data = array(
302
+ 'guid' => $wp_uploads['baseurl'] . '/' . _wp_relative_upload_path( $filepath ),
303
+ 'post_mime_type' => $wp_filetype['type'],
304
+ 'post_title' => preg_replace('/\.[^.]+$/', '', basename($filepath)),
305
+ 'post_content' => '',
306
+ 'post_status' => 'inherit'
307
+ );
308
+ if ( ! empty($export->attch_id) )
309
+ {
310
+ $attach_id = $export->attch_id;
311
+ $attachment = get_post($attach_id);
312
+ if ($attachment)
313
+ {
314
+ update_attached_file( $attach_id, $filepath );
315
+ wp_update_attachment_metadata( $attach_id, $attachment_data );
316
+ }
317
+ else
318
+ {
319
+ $attach_id = wp_insert_attachment( $attachment_data, PMXE_Plugin::$session->file );
320
+ }
321
+ }
322
+ }
323
+
324
+ }
325
+
326
+ // make a temporary copy of current file
327
+ if ( empty($export->parent_id) and @file_exists($filepath) and @copy($filepath, str_replace(basename($filepath), '', $filepath) . 'current-' . basename($filepath)))
328
+ {
329
+ $exportOptions = $export->options;
330
+ $exportOptions['current_filepath'] = str_replace(basename($filepath), '', $filepath) . 'current-' . basename($filepath);
331
+ $export->set(array('options' => $exportOptions))->save();
332
+ }
333
+
334
+ $generateBundle = apply_filters('wp_all_export_generate_bundle', true);
335
+
336
+ if($generateBundle) {
337
+
338
+ // genereta export bundle
339
+ $export->generate_bundle();
340
+
341
+ if ( ! empty($export->parent_id) )
342
+ {
343
+ $parent_export = new PMXE_Export_Record();
344
+ $parent_export->getById($export->parent_id);
345
+ if ( ! $parent_export->isEmpty() )
346
+ {
347
+ $parent_export->generate_bundle(true);
348
+ }
349
+ }
350
+ }
351
+
352
+
353
+ // send exported data to zapier.com
354
+ $subscriptions = get_option('zapier_subscribe', array());
355
+ if ( ! empty($subscriptions) and empty($export->parent_id))
356
+ {
357
+
358
+ $wp_uploads = wp_upload_dir();
359
+
360
+ $fileurl = str_replace($wp_uploads['basedir'], $wp_uploads['baseurl'], $filepath);
361
+
362
+ $response = array(
363
+ 'website_url' => home_url(),
364
+ 'export_id' => $export->id,
365
+ 'export_name' => $export->friendly_name,
366
+ 'file_name' => basename($filepath),
367
+ 'file_type' => wp_all_export_get_export_format($export->options),
368
+ 'post_types_exported' => empty($export->options['cpt']) ? $export->options['wp_query'] : implode($export->options['cpt'], ','),
369
+ 'export_created_date' => $export->registered_on,
370
+ 'export_last_run_date' => date('Y-m-d H:i:s'),
371
+ 'export_trigger_type' => empty($_GET['export_key']) ? 'manual' : 'cron',
372
+ 'records_exported' => $export->exported,
373
+ 'export_file' => ''
374
+ );
375
+
376
+ if (file_exists($filepath))
377
+ {
378
+ $response['export_file_url'] = $fileurl;
379
+ $response['status'] = 200;
380
+ $response['message'] = 'OK';
381
+ }
382
+ else
383
+ {
384
+ $response['export_file_url'] = '';
385
+ $response['status'] = 300;
386
+ $response['message'] = 'File doesn\'t exist';
387
+ }
388
+
389
+ $response = apply_filters('wp_all_export_zapier_response', $response);
390
+
391
+ foreach ($subscriptions as $zapier)
392
+ {
393
+ if (empty($zapier['target_url'])) continue;
394
+
395
+ wp_remote_post( $zapier['target_url'], array(
396
+ 'method' => 'POST',
397
+ 'timeout' => 45,
398
+ 'redirection' => 5,
399
+ 'httpversion' => '1.0',
400
+ 'blocking' => true,
401
+ 'headers' => array(
402
+ 'Content-Type' => 'application/json'
403
+ ),
404
+ 'body' => "[".json_encode($response)."]",
405
+ 'cookies' => array()
406
+ )
407
+ );
408
+ }
409
+ }
410
+
411
+ // clean session
412
+ if ( ! empty(PMXE_Plugin::$session) and PMXE_Plugin::$session->has_session() )
413
+ {
414
+ PMXE_Plugin::$session->clean_session( $export->id );
415
+ }
416
+ }
417
+ }
trunk/actions/pmxe_before_export.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_pmxe_before_export($export_id)
4
+ {
5
+ $export = new PMXE_Export_Record();
6
+ $export->getById($export_id);
7
+
8
+ if ( ! $export->isEmpty() )
9
+ {
10
+ if ( ! $export->options['export_only_new_stuff'] )
11
+ {
12
+ $postList = new PMXE_Post_List();
13
+ $missingPosts = $postList->getBy(array('export_id' => $export_id, 'iteration !=' => --$export->iteration));
14
+ $missing_ids = array();
15
+ if ( ! $missingPosts->isEmpty() ):
16
+
17
+ foreach ($missingPosts as $missingPost)
18
+ {
19
+ $missing_ids[] = $missingPost['post_id'];
20
+ }
21
+
22
+ endif;
23
+
24
+ if ( ! empty($missing_ids))
25
+ {
26
+ global $wpdb;
27
+ // Delete records form pmxe_posts
28
+ $sql = "DELETE FROM " . PMXE_Plugin::getInstance()->getTablePrefix() . "posts WHERE post_id IN (" . implode(',', $missing_ids) . ") AND export_id = %d";
29
+ $wpdb->query(
30
+ $wpdb->prepare($sql, $export->id)
31
+ );
32
+ }
33
+ }
34
+
35
+ if ( empty($export->parent_id) )
36
+ {
37
+ delete_option( 'wp_all_export_queue_' . $export->id );
38
+ }
39
+
40
+ delete_option( 'wp_all_export_acf_flexible_' . $export->id );
41
+ }
42
+ }
trunk/actions/pmxe_exported_post.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_pmxe_exported_post( $pid, $exportRecord )
4
+ {
5
+ // do not associate exported record with child export
6
+ if ( ! empty($exportRecord->parent_id) ) return;
7
+
8
+ $postRecord = new PMXE_Post_Record();
9
+ $postRecord->getBy(array(
10
+ 'post_id' => $pid,
11
+ 'export_id' => XmlExportEngine::$exportID
12
+ ));
13
+
14
+ if ($postRecord->isEmpty())
15
+ {
16
+ $postRecord->set(array(
17
+ 'post_id' => $pid,
18
+ 'export_id' => XmlExportEngine::$exportID,
19
+ 'iteration' => $exportRecord->iteration
20
+ ))->insert();
21
+ }
22
+ else
23
+ {
24
+ $postRecord->set(array('iteration' => $exportRecord->iteration))->update();
25
+ }
26
+ }
trunk/actions/wp_ajax_dismiss_export_warnings.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_wp_ajax_dismiss_export_warnings(){
4
+
5
+ if ( ! check_ajax_referer( 'wp_all_export_secure', 'security', false )){
6
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
7
+ }
8
+
9
+ if ( ! current_user_can( PMXE_Plugin::$capabilities ) ){
10
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
11
+ }
12
+
13
+ $input = new PMXE_Input();
14
+
15
+ $post = $input->post('data', false);
16
+
17
+ if ( ! empty($post) && ! empty($post['export_id'])){
18
+ $option_name = 'wpae_dismiss_warnings_' . $post['export_id'];
19
+ update_option($option_name, 1);
20
+ }
21
+
22
+ exit(json_encode(array('result' => true)));
23
+ }
trunk/actions/wp_ajax_generate_zapier_api_key.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_wp_ajax_generate_zapier_api_key(){
4
+
5
+ if ( ! check_ajax_referer( 'wp_all_export_secure', 'security', false )){
6
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
7
+ }
8
+
9
+ if ( ! current_user_can( PMXE_Plugin::$capabilities ) ){
10
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
11
+ }
12
+
13
+ $api_key = wp_all_export_rand_char(32);
14
+
15
+ PMXE_Plugin::getInstance()->updateOption('zapier_api_key', $api_key);
16
+
17
+ exit(json_encode(array('api_key' => $api_key)));
18
+ }
trunk/actions/wp_ajax_redirect_after_addon_installed.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_wp_ajax_redirect_after_addon_installed(){
4
+
5
+ if ( ! check_ajax_referer( 'wp_all_export_secure', 'security', false )){
6
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
7
+ }
8
+
9
+ if ( ! current_user_can( PMXE_Plugin::$capabilities ) ){
10
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
11
+ }
12
+
13
+ $input = new PMXE_Input();
14
+
15
+ $addon = $input->post('addon', false);
16
+
17
+ $result = false;
18
+
19
+ if ($addon && defined( 'WP_PLUGIN_DIR' )) {
20
+ $result = file_exists(trailingslashit(WP_PLUGIN_DIR) . $addon);
21
+ }
22
+
23
+ exit(json_encode(array('result' => $result)));
24
+ }
trunk/actions/wp_ajax_save_scheduling.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use Wpae\Scheduling\Interval\ScheduleTime;
4
+ use Wpae\Scheduling\Scheduling;
5
+
6
+ /**
7
+ * @throws Exception
8
+ */
9
+ function pmxe_wp_ajax_save_scheduling()
10
+ {
11
+
12
+ if (!check_ajax_referer('wp_all_export_secure', 'security', false)) {
13
+ exit(__('Security check', 'wp_all_export_plugin'));
14
+ }
15
+
16
+ if (!current_user_can(PMXE_Plugin::$capabilities)) {
17
+ exit(__('Security check', 'wp_all_export_plugin'));
18
+ }
19
+
20
+ $elementId = $_POST['element_id'];
21
+
22
+ $post = $_POST;
23
+
24
+ foreach($post['scheduling_times'] as $schedulingTime) {
25
+ if(!preg_match('/^(0?[1-9]|1[012])(:[0-5]\d)[APap][mM]$/', $schedulingTime) && $schedulingTime != '') {
26
+ header('HTTP/1.1 400 Bad request', true, 400);
27
+ die('Invalid times provided');
28
+ }
29
+ }
30
+
31
+ try{
32
+ $scheduling = Scheduling::create();
33
+ $scheduling->handleScheduling($elementId, $post);
34
+ } catch (\Wpae\Scheduling\Exception\SchedulingHttpException $e) {
35
+ header('HTTP/1.1 503 Service unavailable', true, 503);
36
+ echo json_encode(array('success' => false));
37
+
38
+ die;
39
+ }
40
+
41
+ $export = new PMXE_Export_Record();
42
+ $export->getById($elementId);
43
+ $export->set(array('options' => array_merge($export->options, $post)));
44
+ $export->save();
45
+
46
+ echo json_encode(array('success' => true));
47
+ die;
48
+ }
49
+
50
+ /**
51
+ * @return bool
52
+ */
53
+ function convertStringToBoolean($string)
54
+ {
55
+ return ($string == 'true' || $string == 1 || $string === true) ? true : false;
56
+ }
trunk/actions/wp_ajax_scheduling_dialog_content.php ADDED
@@ -0,0 +1,853 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_wp_ajax_scheduling_dialog_content()
4
+ {
5
+
6
+ if (!check_ajax_referer('wp_all_export_secure', 'security', false)) {
7
+ exit(json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))));
8
+ }
9
+
10
+ if (!current_user_can(PMXE_Plugin::$capabilities)) {
11
+ exit(json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))));
12
+ }
13
+
14
+ $export_id = $_POST['id'];
15
+ $export = new PMXE_Export_Record();
16
+ $export->getById($export_id);
17
+ if (!$export) {
18
+ throw new Exception('Export not found');
19
+ }
20
+ $post = $export->options;
21
+
22
+ $hasActiveLicense = PMXE_Plugin::hasActiveSchedulingLicense();
23
+
24
+ $options = \PMXE_Plugin::getInstance()->getOption();
25
+
26
+ $scheduling = \Wpae\Scheduling\Scheduling::create();
27
+
28
+ $cron_job_key = PMXE_Plugin::getInstance()->getOption('cron_job_key');
29
+
30
+ if (!isset($post['scheduling_enable'])) {
31
+ $post['scheduling_enable'] = 0;
32
+ }
33
+
34
+ if (!isset($post['scheduling_timezone'])) {
35
+ $post['scheduling_timezone'] = 'UTC';
36
+ }
37
+
38
+ if (!isset($post['scheduling_run_on'])) {
39
+ $post['scheduling_run_on'] = 'weekly';
40
+ }
41
+
42
+ if (!isset($post['scheduling_times'])) {
43
+ $post['scheduling_times'] = array();
44
+ }
45
+ ?>
46
+
47
+ <style type="text/css">
48
+ .days-of-week {
49
+ margin-left: 5px;
50
+ }
51
+
52
+ .days-of-week li {
53
+ border: 1px solid #ccc;
54
+ border-radius: 5px;
55
+ padding: 10px 30px;;
56
+ display: inline-block;
57
+ margin-right: 10px;
58
+ cursor: pointer;
59
+ font-weight: bold;
60
+ width: 26px;
61
+ text-align: center;
62
+ height: 16px;
63
+ color: rgb(68, 68, 68);
64
+ float: left;
65
+ }
66
+
67
+ .days-of-week li.selected {
68
+ color: #fff;
69
+ background-color: #425F9A;
70
+ border-color: #585858;
71
+ }
72
+
73
+ #weekly, #monthly {
74
+ height: 20px;
75
+ margin-left: 5px;
76
+ margin-top: 10px;
77
+ }
78
+
79
+ .timepicker {
80
+ padding: 10px;
81
+ border-radius: 5px;
82
+ margin-right: 10px;
83
+ }
84
+
85
+ #times {
86
+ margin-top: 5px;
87
+ width: 766px;
88
+ }
89
+
90
+ #times input {
91
+ margin-top: 10px;
92
+ margin-left: 0;
93
+ float: left;
94
+ width: 88px;
95
+
96
+ }
97
+
98
+ #times input.error {
99
+ border-color: red !important;
100
+ }
101
+
102
+ .subscribe {
103
+
104
+ }
105
+
106
+ .subscribe .button-container {
107
+ float: left;
108
+ width: 150px;
109
+ }
110
+
111
+ .subscribe .text-container {
112
+ float: left;
113
+ width: auto;
114
+ }
115
+
116
+ .subscribe .text-container p {
117
+ margin: 0;
118
+ color: #425F9A;
119
+ font-size: 14px;
120
+ font-weight: bold;
121
+ }
122
+
123
+ .subscribe .text-container p a {
124
+ color: #425F9A;
125
+ text-decoration: underline;
126
+ }
127
+
128
+ .save {
129
+ padding-left: 5px;
130
+ padding-top: 5px;
131
+ width: auto;
132
+ }
133
+
134
+ .ui-timepicker-wrapper {
135
+ width: 86px;
136
+ }
137
+
138
+ .easing-spinner {
139
+ width: 30px;
140
+ height: 30px;
141
+ position: relative;
142
+ display: inline-block;
143
+
144
+ margin-top: 7px;
145
+ margin-left: -25px;
146
+
147
+ float: left;
148
+ }
149
+
150
+ .double-bounce1, .double-bounce2 {
151
+ width: 100%;
152
+ height: 100%;
153
+ border-radius: 50%;
154
+ background-color: #fff;
155
+ opacity: 0.6;
156
+ position: absolute;
157
+ top: 0;
158
+ left: 0;
159
+
160
+ -webkit-animation: sk-bounce 2.0s infinite ease-in-out;
161
+ animation: sk-bounce 2.0s infinite ease-in-out;
162
+ }
163
+
164
+ .double-bounce2 {
165
+ -webkit-animation-delay: -1.0s;
166
+ animation-delay: -1.0s;
167
+ }
168
+
169
+ .wpae-save-button svg {
170
+ margin-top: 7px;
171
+ margin-left: -215px;
172
+ display: inline-block;
173
+ position: relative;
174
+ }
175
+
176
+ @-webkit-keyframes sk-bounce {
177
+ 0%, 100% {
178
+ -webkit-transform: scale(0.0)
179
+ }
180
+ 50% {
181
+ -webkit-transform: scale(1.0)
182
+ }
183
+ }
184
+
185
+ @keyframes sk-bounce {
186
+ 0%, 100% {
187
+ transform: scale(0.0);
188
+ -webkit-transform: scale(0.0);
189
+ }
190
+ 50% {
191
+ transform: scale(1.0);
192
+ -webkit-transform: scale(1.0);
193
+ }
194
+ }
195
+
196
+ #add-subscription-field {
197
+ position: absolute;
198
+ left: -155px;
199
+ top: -1px;
200
+ height: 46px;
201
+ border-radius: 5px;
202
+ font-size: 17px;
203
+ padding-left: 10px;
204
+ display: none;
205
+ width: 140px;
206
+ }
207
+
208
+ #find-subscription-link {
209
+ position: absolute;
210
+ left: 133px;
211
+ top: 14px;
212
+ height: 30px;
213
+ width: 300px;
214
+ display: none;
215
+ }
216
+
217
+ #find-subscription-link a {
218
+ display: block;
219
+ width: 100%;
220
+ height: 46px;
221
+ white-space: nowrap;
222
+ }
223
+
224
+ #weekly li.error, #monthly li.error {
225
+ border-color: red;
226
+ }
227
+
228
+ .chosen-single {
229
+ margin-bottom: 0 !important;
230
+ }
231
+
232
+ .chosen-container.chosen-with-drop .chosen-drop {
233
+ margin-top: -3px;
234
+ }
235
+
236
+ .ui-timepicker-wrapper {
237
+ z-index: 999999;
238
+ }
239
+
240
+ .wpallexport-scheduling-dialog h4 {
241
+ font-size: 14px;
242
+ margin-bottom: 5px;
243
+ color: #40acad;
244
+ text-decoration: none;
245
+ margin-left: 0;
246
+ }
247
+
248
+ .manual-scheduling {
249
+ margin-left: 26px;
250
+ }
251
+ .chosen-container .chosen-results {
252
+
253
+ margin: 0 4px 4px 0 !important;
254
+ }
255
+ </style>
256
+
257
+
258
+ <script type="text/javascript">
259
+ (function ($) {
260
+ $(function () {
261
+
262
+ var hasActiveLicense = <?php echo $hasActiveLicense ? 'true' : 'false'; ?>;
263
+
264
+ $(document).ready(function () {
265
+
266
+ function openSchedulingAccordeonIfClosed() {
267
+ if ($('.wpallexport-file-options').hasClass('closed')) {
268
+ // Open accordion
269
+ $('#scheduling-title').trigger('click');
270
+ }
271
+ }
272
+
273
+ window.pmxeValidateSchedulingForm = function () {
274
+
275
+ var schedulingEnabled = $('input[name="scheduling_enable"]:checked').val() == 1;
276
+
277
+ if (!schedulingEnabled) {
278
+ return {
279
+ isValid: true
280
+ };
281
+ }
282
+
283
+ var runOn = $('input[name="scheduling_run_on"]:checked').val();
284
+
285
+ // Validate weekdays
286
+ if (runOn == 'weekly') {
287
+ var weeklyDays = $('#weekly_days').val();
288
+
289
+ if (weeklyDays == '') {
290
+ $('#weekly li').addClass('error');
291
+ return {
292
+ isValid: false,
293
+ message: 'Please select at least a day on which the export should run'
294
+ }
295
+ }
296
+ } else if (runOn == 'monthly') {
297
+ var monthlyDays = $('#monthly_days').val();
298
+
299
+ if (monthlyDays == '') {
300
+ $('#monthly li').addClass('error');
301
+ return {
302
+ isValid: false,
303
+ message: 'Please select at least a day on which the export should run'
304
+ }
305
+ }
306
+ }
307
+
308
+ // Validate times
309
+ var timeValid = true;
310
+ var timeMessage = 'Please select at least a time for the export to run';
311
+ var timeInputs = $('.timepicker');
312
+ var timesHasValues = false;
313
+
314
+ timeInputs.each(function (key, $elem) {
315
+
316
+ if ($(this).val() !== '') {
317
+ timesHasValues = true;
318
+ }
319
+
320
+ if (!$(this).val().match(/^(0?[1-9]|1[012])(:[0-5]\d)[APap][mM]$/) && $(this).val() != '') {
321
+ $(this).addClass('error');
322
+ timeValid = false;
323
+ } else {
324
+ $(this).removeClass('error');
325
+ }
326
+ });
327
+
328
+ if (!timesHasValues) {
329
+ timeValid = false;
330
+ $('.timepicker').addClass('error');
331
+ }
332
+
333
+ if (!timeValid) {
334
+ return {
335
+ isValid: false,
336
+ message: timeMessage
337
+ };
338
+ }
339
+
340
+ return {
341
+ isValid: true
342
+ };
343
+ };
344
+
345
+ $('#weekly li').click(function () {
346
+
347
+ $('#weekly li').removeClass('error');
348
+
349
+ if ($(this).hasClass('selected')) {
350
+ $(this).removeClass('selected');
351
+ } else {
352
+ $(this).addClass('selected');
353
+ }
354
+
355
+ $('#weekly_days').val('');
356
+
357
+ $('#weekly li.selected').each(function () {
358
+ var val = $(this).data('day');
359
+ $('#weekly_days').val($('#weekly_days').val() + val + ',');
360
+ });
361
+
362
+ $('#weekly_days').val($('#weekly_days').val().slice(0, -1));
363
+
364
+ });
365
+
366
+ $('#monthly li').click(function () {
367
+
368
+ $('#monthly li').removeClass('error');
369
+ $(this).parent().parent().find('.days-of-week li').removeClass('selected');
370
+ $(this).addClass('selected');
371
+
372
+ $('#monthly_days').val($(this).data('day'));
373
+ });
374
+
375
+ $('input[name="scheduling_run_on"]').change(function () {
376
+ var val = $('input[name="scheduling_run_on"]:checked').val();
377
+ if (val == "weekly") {
378
+
379
+ $('#weekly').slideDown({
380
+ queue: false
381
+ });
382
+ $('#monthly').slideUp({
383
+ queue: false
384
+ });
385
+
386
+ } else if (val == "monthly") {
387
+
388
+ $('#weekly').slideUp({
389
+ queue: false
390
+ });
391
+ $('#monthly').slideDown({
392
+ queue: false
393
+ });
394
+ }
395
+ });
396
+
397
+ $('.timepicker').timepicker();
398
+
399
+ var selectedTimes = [];
400
+
401
+ var onTimeSelected = function () {
402
+
403
+ selectedTimes.push([$(this).val(), $(this).val() + 1]);
404
+
405
+ var isLastChild = $(this).is(':last-child');
406
+ if (isLastChild) {
407
+ $(this).parent().append('<input class="timepicker" name="scheduling_times[]" style="display: none;" type="text" />');
408
+ $('.timepicker:last-child').timepicker({
409
+ 'disableTimeRanges': selectedTimes
410
+ });
411
+ $('.timepicker:last-child').fadeIn('fast');
412
+ $('.timepicker').on('changeTime', onTimeSelected);
413
+ }
414
+ };
415
+
416
+ $('.timepicker').on('changeTime', onTimeSelected);
417
+
418
+ $('#timezone').chosen({width: '284px'});
419
+
420
+ $('.wpae-save-button').click(function (e) {
421
+
422
+ var initialValue = $(this).find('.save-text').html();
423
+ var schedulingEnable = $('input[name="scheduling_enable"]:checked').val();
424
+ console.log(schedulingEnable);
425
+
426
+ if (!hasActiveLicense) {
427
+ if (!$(this).data('iunderstand') && schedulingEnable) {
428
+ $('#no-subscription').slideDown();
429
+ $(this).find('.save-text').html('<?php echo _e('I Understand');?>');
430
+ $(this).find('.save-text').css('left', '100px');
431
+ $(this).data('iunderstand', 1);
432
+
433
+ openSchedulingAccordeonIfClosed();
434
+ e.preventDefault();
435
+ return;
436
+ } else {
437
+ var submitEvent = $.Event('wpae-scheduling-options-form:submit');
438
+ $(document).trigger(submitEvent);
439
+
440
+ return;
441
+ }
442
+ }
443
+
444
+ // Don't process scheduling
445
+ if (!schedulingEnable) {
446
+ var submitEvent = $.Event('wpae-scheduling-options-form:submit');
447
+ $(document).trigger(submitEvent);
448
+
449
+ return;
450
+ }
451
+
452
+ var validationResponse = pmxeValidateSchedulingForm();
453
+ if (!validationResponse.isValid) {
454
+
455
+ openSchedulingAccordeonIfClosed();
456
+ e.preventDefault();
457
+ return false;
458
+ }
459
+
460
+ $(this).find('.easing-spinner').toggle();
461
+
462
+ var $button = $(this);
463
+
464
+ var formData = $('#scheduling-form :input').serializeArray();
465
+
466
+ formData.push({name: 'security', value: wp_all_export_security});
467
+ formData.push({name: 'action', value: 'save_scheduling'});
468
+ formData.push({name: 'element_id', value: <?php echo $export_id; ?>});
469
+ formData.push({name: 'scheduling_enable', value: schedulingEnable});
470
+
471
+ $.ajax({
472
+ type: 'POST',
473
+ url: ajaxurl,
474
+ data: formData,
475
+ success: function (response) {
476
+ $button.find('.easing-spinner').toggle();
477
+ $button.find('.save-text').html(initialValue);
478
+ $button.find('svg').show();
479
+
480
+ setTimeout(function () {
481
+ var submitEvent = $.Event('wpae-scheduling-options-form:submit');
482
+ $(document).trigger(submitEvent);
483
+ }, 1000);
484
+
485
+ },
486
+ error: function () {
487
+ $button.find('.easing-spinner').toggle();
488
+ $button.find('.save-text').html(initialValue);
489
+ }
490
+ });
491
+ });
492
+
493
+ <?php if($post['scheduling_timezone'] == 'UTC') {
494
+ ?>
495
+ var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
496
+
497
+ if($('#timezone').find("option:contains('"+ timeZone +"')").length != 0){
498
+ $('#timezone').trigger("chosen:updated");
499
+ $('#timezone').val(timeZone);
500
+ $('#timezone').trigger("chosen:updated");
501
+ }else{
502
+ var parts = timeZone.split('/');
503
+ var lastPart = parts[parts.length-1];
504
+ var opt = $('#timezone').find("option:contains('"+ lastPart +"')");
505
+
506
+ $('#timezone').val(opt.val());
507
+ $('#timezone').trigger("chosen:updated");
508
+ }
509
+
510
+ <?php
511
+ }
512
+ ?>
513
+
514
+ var saveSubscription = false;
515
+
516
+ $('#add-subscription').click(function () {
517
+
518
+ $('#add-subscription-field').show();
519
+ $('#add-subscription-field').animate({width: '400px'}, 225);
520
+ $('#add-subscription-field').animate({left: '-1px'}, 225);
521
+ $('#subscribe-button .button-subscribe').css('background-color', '#46ba69');
522
+ $('.text-container p').fadeOut();
523
+
524
+ setTimeout(function () {
525
+ $('#find-subscription-link').show();
526
+ $('#find-subscription-link').animate({left: '410px'}, 300, 'swing');
527
+ }, 225);
528
+ $('.subscribe-button-text').html('<?php _e('Activate'); ?>');
529
+ saveSubscription = true;
530
+ return false;
531
+ });
532
+
533
+ $('#subscribe-button').click(function () {
534
+
535
+ if (saveSubscription) {
536
+ $('#subscribe-button .easing-spinner').show();
537
+
538
+ var license = $('#add-subscription-field').val();
539
+ $.ajax({
540
+ url: ajaxurl + '?action=wpae_api&q=schedulingLicense/saveSchedulingLicense&security=<?php echo wp_create_nonce("wp_all_export_secure");?>',
541
+ type: "POST",
542
+ data: {
543
+ license: license
544
+ },
545
+ dataType: "json",
546
+ success: function (response) {
547
+
548
+ $('#subscribe-button .button-subscribe').css('background-color', '#425f9a');
549
+
550
+ if (response.success) {
551
+ hasActiveLicense = true;
552
+ $('#subscribe-button .easing-spinner').hide();
553
+ $('#subscribe-button svg.success').show();
554
+ $('#subscribe-button svg.success').fadeOut(3000, function () {
555
+ $('.subscribe').hide({queue: false});
556
+ $('#subscribe-filler').show({queue: false});
557
+ });
558
+
559
+ $('.save-changes').removeClass('disabled');
560
+ window.pmxeHasSchedulingSubscription = true;
561
+
562
+ $('.wpai-no-license').hide();
563
+ $('.wpai-license').show();
564
+ } else {
565
+
566
+ $('#subscribe-button .easing-spinner').hide();
567
+ $('#subscribe-button svg.error').show();
568
+ $('.subscribe-button-text').html('<?php _e('Subscribe'); ?>');
569
+
570
+ $('#subscribe-button svg.error').fadeOut(3000, function () {
571
+ $('#subscribe-button svg.error').hide({queue: false});
572
+
573
+ });
574
+
575
+ $('#add-subscription').html('<?php _e('Invalid license, try again?');?>');
576
+ $('.text-container p').fadeIn();
577
+
578
+ $('#find-subscription-link').animate({width: 'toggle'}, 300, 'swing');
579
+
580
+ setTimeout(function () {
581
+ $('#add-subscription-field').animate({width: '140px'}, 225);
582
+ $('#add-subscription-field').animate({left: '-155px'}, 225);
583
+ }, 300);
584
+
585
+ $('#add-subscription-field').val('');
586
+
587
+ $('#subscribe-button-text').html('<?php _e('Subscribe'); ?>');
588
+ saveSubscription = false;
589
+ }
590
+ }
591
+ });
592
+
593
+ return false;
594
+ }
595
+ });
596
+ });
597
+ });
598
+ // help scheduling template
599
+ $('.help_scheduling').click(function () {
600
+
601
+ $('.wp-all-export-scheduling-help').css('left', ($(document).width() / 2) - 255).show();
602
+ $('#wp-all-export-scheduling-help-inner').css('max-height', $(window).height() - 150).show();
603
+ $('.wpallexport-super-overlay').show();
604
+ $('.wpallexport-overlay').hide();
605
+ $('.wp-pointer').hide();
606
+ return false;
607
+ });
608
+
609
+ $('.wp_all_export_scheduling_help').find('h3').unbind('click').click(function () {
610
+ var $action = $(this).find('span').html();
611
+ $('.wp_all_export_scheduling_help').find('h3').each(function () {
612
+ $(this).find('span').html("+");
613
+ });
614
+ if ($action == "+") {
615
+ $('.wp_all_export_help_tab').slideUp();
616
+ $('.wp_all_export_help_tab[rel=' + $(this).attr('id') + ']').slideDown();
617
+ $(this).find('span').html("-");
618
+ }
619
+ else {
620
+ $('.wp_all_export_help_tab[rel=' + $(this).attr('id') + ']').slideUp();
621
+ $(this).find('span').html("+");
622
+ }
623
+ });
624
+
625
+ $('.wpallexport-super-overlay').click(function () {
626
+ $('.wp-all-export-scheduling-help, .wp-all-export-scheduling-help-inner').hide();
627
+ $('.wp-pointer').show();
628
+ $('.wpallexport-overlay').show();
629
+
630
+ $(this).hide();
631
+ });
632
+
633
+ })(jQuery);
634
+
635
+ </script>
636
+ <?php require __DIR__ . '/../src/Scheduling/views/CommonJs.php'; ?>
637
+ <div id="post-preview" class="wpallexport-preview wpallexport-scheduling-dialog">
638
+ <p class="wpallexport-preview-title"><strong>Scheduling Options</strong></p>
639
+ <div class="wpallexport-preview-content" style="max-height: 700px; overflow: visible;">
640
+
641
+ <div style="margin-bottom: 20px;">
642
+ <label>
643
+ <input type="radio" name="scheduling_enable"
644
+ value="0" <?php if ((isset($post['scheduling_enable']) && $post['scheduling_enable'] == 0) || !isset($post['scheduling_enable'])) { ?> checked="checked" <?php } ?>/>
645
+ <h4 style="display: inline-block;"><?php _e('Do Not Schedule'); ?></h4>
646
+ </label>
647
+ </div>
648
+ <div>
649
+ <label>
650
+ <input type="radio" name="scheduling_enable"
651
+ value="1" <?php if ($post['scheduling_enable'] == 1) { ?> checked="checked" <?php } ?>/>
652
+ <h4 style="margin: 0; position: relative; display: inline-block;"><?php _e('Automatic Scheduling', PMXE_Plugin::LANGUAGE_DOMAIN); ?>
653
+ <span class="connection-icon">
654
+ <?php include __DIR__ . '/../src/Scheduling/views/ConnectionIcon.php'; ?>
655
+ </span>
656
+ <?php if (!$scheduling->checkConnection()) { ?>
657
+ <span class="wpai-license wpai-license-text" style="display: inline-block; font-weight: normal; <?php if(!$hasActiveLicense) { ?> display: none; <?php }?> color: #f2b03d; ">Unable to connect - <a target="_blank" style="text-decoration: underline;" href="http://wpallimport.com/support">please contact support</a>.</span>
658
+ <?php } ?>
659
+ </h4>
660
+ </label>
661
+ </div>
662
+ <form id="scheduling-form">
663
+ <div style="margin-bottom: 10px; margin-left:26px;">
664
+ <label style="font-size: 13px;">
665
+ <?php _e('Run this export on a schedule.'); ?>
666
+ </label>
667
+ </div>
668
+ <div id="automatic-scheduling"
669
+ style="margin-left: 21px; <?php if ($post['scheduling_enable'] != 1) { ?> display: none; <?php } ?>">
670
+ <div>
671
+ <div class="input">
672
+ <label style="color: rgb(68,68,68);">
673
+ <input
674
+ type="radio" <?php if (isset($post['scheduling_run_on']) && $post['scheduling_run_on'] != 'monthly') { ?> checked="checked" <?php } ?>
675
+ name="scheduling_run_on" value="weekly"
676
+ checked="checked"/> <?php _e('Every week on...', PMXE_Plugin::LANGUAGE_DOMAIN); ?>
677
+ </label>
678
+ </div>
679
+ <input type="hidden" style="width: 500px;" name="scheduling_weekly_days"
680
+ value="<?php echo $post['scheduling_weekly_days']; ?>" id="weekly_days"/>
681
+ <?php
682
+ if (isset($post['scheduling_weekly_days'])) {
683
+ $weeklyArray = explode(',', $post['scheduling_weekly_days']);
684
+ } else {
685
+ $weeklyArray = array();
686
+ }
687
+ ?>
688
+ <ul class="days-of-week" id="weekly"
689
+ style="<?php if ($post['scheduling_run_on'] == 'monthly') { ?> display: none; <?php } ?>">
690
+ <li data-day="0" <?php if (in_array('0', $weeklyArray)) { ?> class="selected" <?php } ?>>
691
+ Mon
692
+ </li>
693
+ <li data-day="1" <?php if (in_array('1', $weeklyArray)) { ?> class="selected" <?php } ?>>
694
+ Tue
695
+ </li>
696
+ <li data-day="2" <?php if (in_array('2', $weeklyArray)) { ?> class="selected" <?php } ?>>
697
+ Wed
698
+ </li>
699
+ <li data-day="3" <?php if (in_array('3', $weeklyArray)) { ?> class="selected" <?php } ?>>
700
+ Thu
701
+ </li>
702
+ <li data-day="4" <?php if (in_array('4', $weeklyArray)) { ?> class="selected" <?php } ?>>
703
+ Fri
704
+ </li>
705
+ <li data-day="5" <?php if (in_array('5', $weeklyArray)) { ?> class="selected" <?php } ?>>
706
+ Sat
707
+ </li>
708
+ <li data-day="6" <?php if (in_array('6', $weeklyArray)) { ?> class="selected" <?php } ?>>
709
+ Sun
710
+ </li>
711
+ </ul>
712
+ </div>
713
+ <div style="clear: both;"></div>
714
+ <div>
715
+ <div class="input">
716
+ <label style="color: rgb(68,68,68); margin-top: 5px;">
717
+ <input
718
+ type="radio" <?php if (isset($post['scheduling_run_on']) && $post['scheduling_run_on'] == 'monthly') { ?> checked="checked" <?php } ?>
719
+ name="scheduling_run_on"
720
+ value="monthly"/> <?php _e('Every month on the first...', PMXE_Plugin::LANGUAGE_DOMAIN); ?>
721
+ </label>
722
+ </div>
723
+ <input type="hidden" name="scheduling_monthly_days"
724
+ value="<?php if(isset($post['scheduling_monthly_days'])) echo $post['scheduling_monthly_days']; ?>" id="monthly_days"/>
725
+ <?php
726
+ if (isset($post['scheduling_monthly_days'])) {
727
+ $monthlyArray = explode(',', $post['scheduling_monthly_days']);
728
+ } else {
729
+ $monthlyArray = array();
730
+ }
731
+ ?>
732
+ <ul class="days-of-week" id="monthly"
733
+ style="<?php if ($post['scheduling_run_on'] != 'monthly') { ?> display: none; <?php } ?>">
734
+ <li data-day="0" <?php if (in_array('0', $monthlyArray)) { ?> class="selected" <?php } ?>>
735
+ Mon
736
+ </li>
737
+ <li data-day="1" <?php if (in_array('1', $monthlyArray)) { ?> class="selected" <?php } ?>>
738
+ Tue
739
+ </li>
740
+ <li data-day="2" <?php if (in_array('2', $monthlyArray)) { ?> class="selected" <?php } ?>>
741
+ Wed
742
+ </li>
743
+ <li data-day="3" <?php if (in_array('3', $monthlyArray)) { ?> class="selected" <?php } ?>>
744
+ Thu
745
+ </li>
746
+ <li data-day="4" <?php if (in_array('4', $monthlyArray)) { ?> class="selected" <?php } ?>>
747
+ Fri
748
+ </li>
749
+ <li data-day="5" <?php if (in_array('5', $monthlyArray)) { ?> class="selected" <?php } ?>>
750
+ Sat
751
+ </li>
752
+ <li data-day="6" <?php if (in_array('6', $monthlyArray)) { ?> class="selected" <?php } ?>>
753
+ Sun
754
+ </li>
755
+ </ul>
756
+ </div>
757
+ <div style="clear: both;"></div>
758
+
759
+ <div id="times-container" style="margin-left: 5px;">
760
+ <div style="margin-top: 10px; margin-bottom: 5px;">
761
+ What times do you want this export to run?
762
+ </div>
763
+
764
+ <div id="times" style="margin-bottom: 10px;">
765
+ <?php if (isset($post['scheduling_times']) && is_array($post['scheduling_times'])) {
766
+ foreach ($post['scheduling_times'] as $time) { ?>
767
+
768
+ <?php if ($time) { ?>
769
+ <input class="timepicker" type="text" name="scheduling_times[]"
770
+ value="<?php echo $time; ?>"/>
771
+ <?php } ?>
772
+ <?php } ?>
773
+ <input class="timepicker" type="text" name="scheduling_times[]"/>
774
+ <?php } ?>
775
+ </div>
776
+ <div style="clear: both;"></div>
777
+ <div class="timezone-select" style="position:absolute; margin-top: 10px;">
778
+ <?php
779
+
780
+ $timezoneValue = false;
781
+ if ($post['scheduling_timezone']) {
782
+ $timezoneValue = $post['scheduling_timezone'];
783
+ }
784
+
785
+ $timezoneSelect = new \Wpae\Scheduling\Timezone\TimezoneSelect();
786
+ echo $timezoneSelect->getTimezoneSelect($timezoneValue);
787
+ ?>
788
+ </div>
789
+ </div>
790
+ <div style="height: 60px; margin-top: 30px; <?php if (!$hasActiveLicense) { ?>display: none; <?php } ?>"
791
+ id="subscribe-filler">&nbsp;
792
+ </div>
793
+ <?php
794
+ if (!$hasActiveLicense) {
795
+ ?>
796
+ <div class="subscribe"
797
+ style="margin-left: 5px; margin-top: 65px; margin-bottom: 130px; position: relative;">
798
+ <div class="button-container">
799
+
800
+ <a href="https://www.wpallimport.com/checkout/?edd_action=add_to_cart&download_id=515704&utm_source=export-plugin-free&utm_medium=upgrade-notice&utm_campaign=automatic-scheduling"
801
+ target="_blank" id="subscribe-button">
802
+ <div class="button button-primary button-hero wpallexport-large-button button-subscribe"
803
+ style="background-image: none; width: 140px; text-align: center; position: absolute; z-index: 4;">
804
+ <svg class="success" width="30" height="30" viewBox="0 0 1792 1792"
805
+ xmlns="http://www.w3.org/2000/svg"
806
+ style="fill: white; position: absolute; top: 7px; left: 9px; display: none;">
807
+ <path
808
+ d="M1671 566q0 40-28 68l-724 724-136 136q-28 28-68 28t-68-28l-136-136-362-362q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 295 656-657q28-28 68-28t68 28l136 136q28 28 28 68z"
809
+ fill="white"/>
810
+ </svg>
811
+ <svg class="error" width="30" height="30" viewBox="0 0 1792 1792"
812
+ xmlns="http://www.w3.org/2000/svg"
813
+ style="fill: red; position: absolute; top: 2px; left: 7px; display: none;">
814
+ <path d="M1490 1322q0 40-28 68l-136 136q-28 28-68 28t-68-28l-294-294-294 294q-28 28-68 28t-68-28l-136-136q-28-28-28-68t28-68l294-294-294-294q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 294 294-294q28-28 68-28t68 28l136 136q28 28 28 68t-28 68l-294 294 294 294q28 28 28 68z"/>
815
+ </svg>
816
+ <div class="easing-spinner"
817
+ style=" position: absolute; left: 37px; display: none;">
818
+ <div class="double-bounce1"></div>
819
+ <div class="double-bounce2"></div>
820
+ </div>
821
+
822
+ <span class="subscribe-button-text">
823
+ <?php _e('Subscribe'); ?>
824
+ </span>
825
+ </div>
826
+ </a>
827
+ </div>
828
+ <div class="text-container" style="position: absolute; left: 150px; top: 2px;">
829
+ <p><?php _e('Get automatic scheduling for unlimited sites, just $9/mo.'); ?></p>
830
+ <p><?php _e('Have a license?'); ?>
831
+ <a href="#"
832
+ id="add-subscription"><?php _e('Register this site.'); ?></a> <?php _e('Questions?'); ?>
833
+ <a href="#" class="help_scheduling">Read more.</a>
834
+ </p>
835
+ <input type="password" id="add-subscription-field"
836
+ style="position: absolute; z-index: 2; top: -4px; font-size:14px;"
837
+ placeholder="<?php _e('Enter your license', PMXE_Plugin::LANGUAGE_DOMAIN); ?>"/>
838
+ <div style="position: absolute;" id="find-subscription-link"><a
839
+ href="http://www.wpallimport.com/portal/automatic-scheduling/"
840
+ target="_blank"><?php _e('Find your license.'); ?></a></div>
841
+ </div>
842
+ </div>
843
+ <?php
844
+ } ?>
845
+ </div>
846
+
847
+ </form>
848
+ <?php require __DIR__ . '/../src/Scheduling/views/ManualScheduling.php'; ?>
849
+ </div>
850
+ </div>
851
+ <?php
852
+ wp_die();
853
+ }
trunk/actions/wp_ajax_wpae_available_rules.php ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_wp_ajax_wpae_available_rules(){
4
+
5
+ if ( ! check_ajax_referer( 'wp_all_export_secure', 'security', false )){
6
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
7
+ }
8
+
9
+ if ( ! current_user_can( PMXE_Plugin::$capabilities ) ){
10
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
11
+ }
12
+
13
+ ob_start();
14
+
15
+ $input = new PMXE_Input();
16
+
17
+ $post = $input->post('data', array());
18
+
19
+ ?>
20
+ <select id="wp_all_export_rule">
21
+ <option value=""><?php _e('Select Rule', 'wp_all_export_plugin'); ?></option>
22
+ <?php
23
+ if (strpos($post['selected'], 'tx_') === 0 || strpos($post['selected'], 'product_tx') === 0){
24
+ ?>
25
+
26
+ <!-- Taxonomies -->
27
+ <option value="in"><?php echo __('In', 'wp_all_export_plugin') . ' ' . ucwords(str_replace(array("product_tx", "tx_", "_"), array("", "", " "), $post['selected'])); ?></option>
28
+ <option value="not_in"><?php echo __('Not In', 'wp_all_export_plugin') . ' ' . ucwords(str_replace(array("product_tx", "tx_", "_"), array("", "", " "), $post['selected'])); ?></option>
29
+
30
+ <!-- Custom Fields -->
31
+ <!--option value="between">BETWEEN</option-->
32
+
33
+ <?php
34
+ }
35
+ elseif( in_array($post['selected'], array('post_date', 'post_modified', 'user_registered', 'comment_date', 'cf__completed_date')) )
36
+ {
37
+ ?>
38
+ <option value="equals"><?php _e('equals', 'wp_all_export_plugin'); ?></option>
39
+ <option value="not_equals"><?php _e("doesn't equal", 'wp_all_export_plugin'); ?></option>
40
+ <option value="greater"><?php _e('newer than', 'wp_all_export_plugin');?></option>
41
+ <option value="equals_or_greater"><?php _e('equal to or newer than', 'wp_all_export_plugin'); ?></option>
42
+ <option value="less"><?php _e('older than', 'wp_all_export_plugin'); ?></option>
43
+ <option value="equals_or_less"><?php _e('equal to or older than', 'wp_all_export_plugin'); ?></option>
44
+
45
+ <option value="contains"><?php _e('contains', 'wp_all_export_plugin'); ?></option>
46
+ <option value="not_contains"><?php _e("doesn't contain", 'wp_all_export_plugin'); ?></option>
47
+ <option value="is_empty"><?php _e('is empty', 'wp_all_export_plugin'); ?></option>
48
+ <option value="is_not_empty"><?php _e('is not empty', 'wp_all_export_plugin'); ?></option>
49
+ <?php
50
+ }
51
+ elseif( in_array($post['selected'], array('wp_capabilities')))
52
+ {
53
+ ?>
54
+ <option value="contains"><?php _e('contains', 'wp_all_export_plugin'); ?></option>
55
+ <option value="not_contains"><?php _e("doesn't contain", 'wp_all_export_plugin'); ?></option>
56
+ <?php
57
+ }
58
+ elseif ( in_array($post['selected'], array('user_login', 'user_nicename', 'user_role', 'user_email', 'display_name', 'first_name', 'last_name', 'nickname', 'description',
59
+ 'post_status', 'post_title', 'post_content', 'comment_author_email', 'comment_author_url', 'comment_author_IP', 'comment_agent',
60
+ 'comment_type', 'comment_content') ) )
61
+ {
62
+ ?>
63
+ <option value="equals"><?php _e('equals', 'wp_all_export_plugin'); ?></option>
64
+ <option value="not_equals"><?php _e("doesn't equal", 'wp_all_export_plugin'); ?></option>
65
+ <option value="contains"><?php _e('contains', 'wp_all_export_plugin'); ?></option>
66
+ <option value="not_contains"><?php _e("doesn't contain", 'wp_all_export_plugin'); ?></option>
67
+ <option value="is_empty"><?php _e('is empty', 'wp_all_export_plugin'); ?></option>
68
+ <option value="is_not_empty"><?php _e('is not empty', 'wp_all_export_plugin'); ?></option>
69
+ <?php
70
+ }
71
+ elseif ( in_array($post['selected'], array('term_parent_slug') ) )
72
+ {
73
+ ?>
74
+ <option value="equals"><?php _e('equals', 'wp_all_export_plugin'); ?></option>
75
+ <option value="not_equals"><?php _e("doesn't equal", 'wp_all_export_plugin'); ?></option>
76
+ <option value="greater"><?php _e('greater than', 'wp_all_export_plugin');?></option>
77
+ <option value="equals_or_greater"><?php _e('equal to or greater than', 'wp_all_export_plugin'); ?></option>
78
+ <option value="less"><?php _e('less than', 'wp_all_export_plugin'); ?></option>
79
+ <option value="equals_or_less"><?php _e('equal to or less than', 'wp_all_export_plugin'); ?></option>
80
+ <option value="is_empty"><?php _e('is empty', 'wp_all_export_plugin'); ?></option>
81
+ <option value="is_not_empty"><?php _e('is not empty', 'wp_all_export_plugin'); ?></option>
82
+ <?php
83
+ }
84
+ else
85
+ {
86
+ ?>
87
+ <option value="equals"><?php _e('equals', 'wp_all_export_plugin'); ?></option>
88
+ <option value="not_equals"><?php _e("doesn't equal", 'wp_all_export_plugin'); ?></option>
89
+ <option value="greater"><?php _e('greater than', 'wp_all_export_plugin');?></option>
90
+ <option value="equals_or_greater"><?php _e('equal to or greater than', 'wp_all_export_plugin'); ?></option>
91
+ <option value="less"><?php _e('less than', 'wp_all_export_plugin'); ?></option>
92
+ <option value="equals_or_less"><?php _e('equal to or less than', 'wp_all_export_plugin'); ?></option>
93
+
94
+ <option value="contains"><?php _e('contains', 'wp_all_export_plugin'); ?></option>
95
+ <option value="not_contains"><?php _e("doesn't contain", 'wp_all_export_plugin'); ?></option>
96
+ <option value="is_empty"><?php _e('is empty', 'wp_all_export_plugin'); ?></option>
97
+ <option value="is_not_empty"><?php _e('is not empty', 'wp_all_export_plugin'); ?></option>
98
+ <?php
99
+ }
100
+ ?>
101
+ </select>
102
+ <?php
103
+
104
+ exit(json_encode(array('html' => ob_get_clean()))); die;
105
+
106
+ }
trunk/actions/wp_ajax_wpae_filtering.php ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_wp_ajax_wpae_filtering(){
4
+
5
+ if ( ! check_ajax_referer( 'wp_all_export_secure', 'security', false )){
6
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
7
+ }
8
+
9
+ if ( ! current_user_can( PMXE_Plugin::$capabilities ) ){
10
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
11
+ }
12
+
13
+ $addons = new \Wpae\App\Service\Addons\AddonService();
14
+ $response = array(
15
+ 'html' => '',
16
+ 'btns' => ''
17
+ );
18
+
19
+ ob_start();
20
+
21
+ $errors = new WP_Error();
22
+
23
+ $input = new PMXE_Input();
24
+
25
+ $post = $input->post('data', array());
26
+
27
+ if ( ! empty($post['cpt'])):
28
+
29
+ $engine = new XmlExportEngine($post, $errors);
30
+
31
+ $engine->init_available_data();
32
+
33
+ ?>
34
+ <div class="wpallexport-content-section">
35
+ <div class="wpallexport-collapsed-header">
36
+ <h3><?php _e('Add Filtering Options', 'wp_all_export_plugin'); ?></h3>
37
+ </div>
38
+ <div class="wpallexport-collapsed-content">
39
+ <?php include_once PMXE_ROOT_DIR . '/views/admin/export/blocks/filters.php'; ?>
40
+ </div>
41
+ </div>
42
+
43
+ <?php
44
+
45
+ endif;
46
+
47
+ $response['html'] = ob_get_clean();
48
+
49
+ if ( (XmlExportEngine::$is_user_export && $post['cpt'] != 'shop_customer' && !$addons->isUserAddonActive()) || XmlExportEngine::$is_comment_export || XmlExportEngine::$is_taxonomy_export || $post['cpt'] == 'shop_customer' )
50
+ {
51
+ $response['btns'] = '';
52
+ exit(json_encode($response)); die;
53
+ }
54
+
55
+ ob_start();
56
+
57
+ if ( XmlExportEngine::$is_auto_generate_enabled ):
58
+ ?>
59
+ <div class="wpallexport-free-edition-notice" id="migrate-orders-notice" style="padding: 20px; margin-bottom: 10px; display: none;">
60
+ <a class="upgrade_link" target="_blank" href="https://www.wpallimport.com/checkout/?edd_action=add_to_cart&download_id=118611&edd_options%5Bprice_id%5D=1&utm_source=export-plugin-free&utm_medium=upgrade-notice&utm_campaign=migrate-orders"><?php _e('Upgrade to the Pro edition of WP All Export to Migrate Orders', PMXE_Plugin::LANGUAGE_DOMAIN);?></a>
61
+ <p><?php _e('If you already own it, remove the free edition and install the Pro edition.', PMXE_Plugin::LANGUAGE_DOMAIN);?></p>
62
+ </div>
63
+
64
+ <div class="wpallexport-free-edition-notice" id="migrate-users-notice" style="padding: 20px; margin-bottom: 10px; display: none;">
65
+ <a class="upgrade_link" target="_blank" href="https://www.wpallimport.com/checkout/?edd_action=add_to_cart&download_id=118611&edd_options%5Bprice_id%5D=1&utm_source=export-plugin-free&utm_medium=upgrade-notice&utm_campaign=migrate-users"><?php _e('Upgrade to the Pro edition of WP All Export to Migrate Users', PMXE_Plugin::LANGUAGE_DOMAIN);?></a>
66
+ <p><?php _e('If you already own it, remove the free edition and install the Pro edition.', PMXE_Plugin::LANGUAGE_DOMAIN);?></p>
67
+ </div>
68
+
69
+ <?php if(isset($post['cpt'])) { ?>
70
+ <span class="wp_all_export_btn_with_note">
71
+ <a href="javascript:void(0);" class="back rad3 auto-generate-template" style="float:none; background: #425f9a; padding: 0 50px; margin-right: 10px; color: #fff; font-weight: normal;"><?php printf(__('Migrate %s', 'wp_all_export_plugin'), wp_all_export_get_cpt_name(array($post['cpt']), 2, $post)); ?></a>
72
+ <span class="auto-generate-template">&nbsp;</span>
73
+ </span>
74
+ <?php } ?>
75
+ <span class="wp_all_export_btn_with_note">
76
+ <input type="submit" class="button button-primary button-hero wpallexport-large-button" value="<?php _e('Customize Export File', 'wp_all_export_plugin') ?>"/>
77
+ <span class="auto-generate-template">&nbsp;</span>
78
+ </span>
79
+ <?php
80
+ else:
81
+ ?>
82
+ <span class="wp_all_export_btn_with_note">
83
+ <input type="submit" class="button button-primary button-hero wpallexport-large-button" value="<?php _e('Customize Export File', 'wp_all_export_plugin') ?>"/>
84
+ </span>
85
+ <?php
86
+ endif;
87
+ $response['btns'] = ob_get_clean();
88
+
89
+ exit(json_encode($response)); die;
90
+
91
+ }
trunk/actions/wp_ajax_wpae_filtering_count.php ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_wp_ajax_wpae_filtering_count(){
4
+
5
+ if ( ! check_ajax_referer( 'wp_all_export_secure', 'security', false )){
6
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
7
+ }
8
+
9
+ if ( ! current_user_can( PMXE_Plugin::$capabilities ) ){
10
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
11
+ }
12
+
13
+ ob_start();
14
+
15
+ $hasVariations = false;
16
+
17
+ $input = new PMXE_Input();
18
+
19
+ $post = $input->post('data', array());
20
+
21
+ $filter_args = array(
22
+ 'filter_rules_hierarhy' => empty($post['filter_rules_hierarhy']) ? array() : $post['filter_rules_hierarhy'],
23
+ 'product_matching_mode' => empty($post['product_matching_mode']) ? 'strict' : $post['product_matching_mode'],
24
+ 'taxonomy_to_export' => empty($post['taxonomy_to_export']) ? '' : $post['taxonomy_to_export']
25
+ );
26
+
27
+ $input = new PMXE_Input();
28
+ $export_id = $input->get('id', 0);
29
+ if (empty($export_id))
30
+ {
31
+ $export_id = ( ! empty(PMXE_Plugin::$session->update_previous)) ? PMXE_Plugin::$session->update_previous : 0;
32
+ }
33
+
34
+ $export = new PMXE_Export_Record();
35
+ $export->getById($export_id);
36
+ if ( ! $export->isEmpty() )
37
+ {
38
+ XmlExportEngine::$exportRecord = $export;
39
+ XmlExportEngine::$exportOptions = $export->options + PMXE_Plugin::get_default_import_options();
40
+ XmlExportEngine::$exportOptions['export_only_new_stuff'] = $post['export_only_new_stuff'];
41
+ XmlExportEngine::$exportOptions['export_only_modified_stuff'] = $post['export_only_modified_stuff'];
42
+ if ( ! empty($post['wpml_lang']) ){
43
+ XmlExportEngine::$exportOptions['wpml_lang'] = $post['wpml_lang'];
44
+ $export->set(array('options' => XmlExportEngine::$exportOptions))->save();
45
+ }
46
+ }
47
+ else{
48
+ $sessionLang = empty(PMXE_Plugin::$session->wpml_lang) ? 'all' : PMXE_Plugin::$session->wpml_lang;
49
+ XmlExportEngine::$exportOptions['wpml_lang'] = empty($post['wpml_lang']) ? $sessionLang : $post['wpml_lang'];
50
+ }
51
+
52
+ if (class_exists('SitePress') && !empty(XmlExportEngine::$exportOptions['wpml_lang'])){
53
+ PMXE_Plugin::$session->set('wpml_lang', XmlExportEngine::$exportOptions['wpml_lang']);
54
+ do_action( 'wpml_switch_language', XmlExportEngine::$exportOptions['wpml_lang'] );
55
+ }
56
+
57
+ XmlExportEngine::$is_user_export = ( 'users' == $post['cpt'] or 'shop_customer' == $post['cpt'] ) ? true : false;
58
+ XmlExportEngine::$is_comment_export = ( 'comments' == $post['cpt'] ) ? true : false;
59
+ XmlExportEngine::$is_taxonomy_export = ( 'taxonomies' == $post['cpt'] ) ? true : false;
60
+ XmlExportEngine::$post_types = array($post['cpt']);
61
+ XmlExportEngine::$exportOptions['export_variations'] = empty($post['export_variations']) ? XmlExportEngine::VARIABLE_PRODUCTS_EXPORT_PARENT_AND_VARIATION : $post['export_variations'];
62
+
63
+ $filters = \Wpae\Pro\Filtering\FilteringFactory::getFilterEngine();
64
+ $filters->init($filter_args);
65
+ $filters->parse();
66
+
67
+ PMXE_Plugin::$session->set('whereclause', $filters->get('queryWhere'));
68
+ PMXE_Plugin::$session->set('joinclause', $filters->get('queryJoin'));
69
+ PMXE_Plugin::$session->save_data();
70
+
71
+ $foundRecords = 0;
72
+ $total_records = 0;
73
+
74
+ $cpt = array($post['cpt']);
75
+
76
+ $is_products_export = ($post['cpt'] == 'product' and class_exists('WooCommerce'));
77
+
78
+ if ($post['export_type'] == 'advanced')
79
+ {
80
+ if (XmlExportEngine::$is_user_export)
81
+ {
82
+ // get total users
83
+ $totalQuery = eval('return new WP_User_Query(array(' . PMXE_Plugin::$session->get('wp_query') . ', \'offset\' => 0, \'number\' => 10 ));');
84
+ if ( ! empty($totalQuery->results)){
85
+ $total_records = $totalQuery->get_total();
86
+ }
87
+
88
+ ob_start();
89
+ // get users depends on filters
90
+ add_action('pre_user_query', 'wp_all_export_pre_user_query', 10, 1);
91
+ $exportQuery = eval('return new WP_User_Query(array(' . PMXE_Plugin::$session->get('wp_query') . ', \'offset\' => 0, \'number\' => 10 ));');
92
+ if ( ! empty($exportQuery->results)){
93
+ $foundRecords = $exportQuery->get_total();
94
+ }
95
+ remove_action('pre_user_query', 'wp_all_export_pre_user_query');
96
+ ob_get_clean();
97
+ }
98
+ elseif(XmlExportEngine::$is_comment_export)
99
+ {
100
+ // get total comments
101
+ $totalQuery = eval('return new WP_Comment_Query(array(' . PMXE_Plugin::$session->get('wp_query') . ', \'number\' => 10, \'count\' => true ));');
102
+ $total_records = $totalQuery->get_comments();
103
+
104
+ ob_start();
105
+ // get comments depends on filters
106
+ add_action('comments_clauses', 'wp_all_export_comments_clauses', 10, 1);
107
+ $exportQuery = eval('return new WP_Comment_Query(array(' . PMXE_Plugin::$session->get('wp_query') . '));');
108
+ $foundRecords = $exportQuery->get_comments();
109
+ remove_action('comments_clauses', 'wp_all_export_comments_clauses');
110
+ ob_get_clean();
111
+ }
112
+ else
113
+ {
114
+ remove_all_actions('parse_query');
115
+ remove_all_actions('pre_get_posts');
116
+ remove_all_filters('posts_clauses');
117
+
118
+ // get total custom post type records
119
+ $totalQuery = eval('return new WP_Query(array(' . PMXE_Plugin::$session->get('wp_query') . ', \'offset\' => 0, \'posts_per_page\' => 10 ));');
120
+ if ( ! empty($totalQuery->found_posts)){
121
+ $total_records = $totalQuery->found_posts;
122
+ }
123
+
124
+ wp_reset_postdata();
125
+
126
+ ob_start();
127
+ // get custom post type records depends on filters
128
+ add_filter('posts_where', 'wp_all_export_posts_where', 10, 1);
129
+ add_filter('posts_join', 'wp_all_export_posts_join', 10, 1);
130
+
131
+ $exportQuery = eval('return new WP_Query(array(' . PMXE_Plugin::$session->get('wp_query') . ', \'offset\' => 0, \'posts_per_page\' => 10 ));');
132
+ if ( ! empty($exportQuery->found_posts)){
133
+ $foundRecords = $exportQuery->found_posts;
134
+ }
135
+ remove_filter('posts_join', 'wp_all_export_posts_join');
136
+ remove_filter('posts_where', 'wp_all_export_posts_where');
137
+ ob_get_clean();
138
+ }
139
+ }
140
+ else
141
+ {
142
+ if ( 'users' == $post['cpt'] or 'shop_customer' == $post['cpt'] )
143
+ {
144
+ // get total users
145
+ $totalQuery = new WP_User_Query( array( 'orderby' => 'ID', 'order' => 'ASC', 'number' => 10 ));
146
+ if ( ! empty($totalQuery->results)){
147
+ $total_records = $totalQuery->get_total();
148
+ }
149
+
150
+ ob_start();
151
+ // get users depends on filters
152
+ add_action('pre_user_query', 'wp_all_export_pre_user_query', 10, 1);
153
+ $exportQuery = new WP_User_Query( array( 'orderby' => 'ID', 'order' => 'ASC', 'number' => 10 ));
154
+ if ( ! empty($exportQuery->results)){
155
+ $foundRecords = $exportQuery->get_total();
156
+ }
157
+ remove_action('pre_user_query', 'wp_all_export_pre_user_query');
158
+ ob_get_clean();
159
+ }
160
+ elseif( 'comments' == $post['cpt'] )
161
+ {
162
+ // get total comments
163
+ global $wp_version;
164
+
165
+ if ( version_compare($wp_version, '4.2.0', '>=') )
166
+ {
167
+ $totalQuery = new WP_Comment_Query( array( 'orderby' => 'comment_ID', 'order' => 'ASC', 'number' => 10, 'count' => true));
168
+ $total_records = $totalQuery->get_comments();
169
+ }
170
+ else
171
+ {
172
+ $total_records = get_comments( array( 'orderby' => 'comment_ID', 'order' => 'ASC', 'number' => 10, 'count' => true));
173
+ }
174
+
175
+ ob_start();
176
+ // get comments depends on filters
177
+ add_action('comments_clauses', 'wp_all_export_comments_clauses', 10, 1);
178
+
179
+ if ( version_compare($wp_version, '4.2.0', '>=') )
180
+ {
181
+ $exportQuery = new WP_Comment_Query( array( 'orderby' => 'comment_ID', 'order' => 'ASC'));
182
+ $foundRecords = count($exportQuery->get_comments());
183
+ }
184
+ else
185
+ {
186
+ $foundRecords = count(get_comments( array( 'orderby' => 'comment_ID', 'order' => 'ASC')));
187
+ }
188
+ remove_action('comments_clauses', 'wp_all_export_comments_clauses');
189
+ ob_get_clean();
190
+ }
191
+ elseif( 'taxonomies' == $post['cpt'] )
192
+ {
193
+ global $wp_version;
194
+
195
+ if ( version_compare($wp_version, '4.6.0', '>=') ) {
196
+ $totalQuery = new WP_Term_Query(array(
197
+ 'taxonomy' => $post['taxonomy_to_export'],
198
+ 'orderby' => 'name',
199
+ 'order' => 'ASC',
200
+ 'number' => 10,
201
+ 'hide_empty' => FALSE
202
+ ));
203
+ $total_records = count($totalQuery->get_terms());
204
+
205
+ ob_start();
206
+ // get comments depends on filters
207
+ add_filter('terms_clauses', 'wp_all_export_terms_clauses', 10, 3);
208
+ $exportQuery = new WP_Term_Query(array(
209
+ 'taxonomy' => $post['taxonomy_to_export'],
210
+ 'orderby' => 'name',
211
+ 'order' => 'ASC',
212
+ 'hide_empty' => FALSE
213
+ ));
214
+ $foundRecords = count($exportQuery->get_terms());
215
+ remove_filter('terms_clauses', 'wp_all_export_terms_clauses');
216
+ ob_get_clean();
217
+ }
218
+ else{
219
+ ?>
220
+ <div class="founded_records">
221
+ <h3><?php _e('Unable to Export', 'wp_all_export_plugin'); ?></h3>
222
+ <h4><?php printf(__("Exporting taxonomies requires WordPress 4.6 or greater", "wp_all_export_plugin"), wp_all_export_get_cpt_name($cpt, 2, $post)); ?></h4>
223
+ </div>
224
+ <?php
225
+ exit(json_encode(array('html' => ob_get_clean(), 'found_records' => 0, 'hasVariations' => $hasVariations))); die;
226
+ }
227
+
228
+ }
229
+ else
230
+ {
231
+ remove_all_actions('parse_query');
232
+ remove_all_actions('pre_get_posts');
233
+ remove_all_filters('posts_clauses');
234
+
235
+ $cpt = ($is_products_export) ? array('product', 'product_variation') : array($post['cpt']);
236
+
237
+ // get total custom post type records
238
+ $totalQuery = new WP_Query( array( 'post_type' => $cpt, 'post_status' => 'any', 'orderby' => 'ID', 'order' => 'ASC', 'posts_per_page' => 10 ));
239
+
240
+ if ( ! empty($totalQuery->found_posts)){
241
+ $total_records = $totalQuery->found_posts;
242
+ }
243
+
244
+ wp_reset_postdata();
245
+
246
+ ob_start();
247
+ // get custom post type records depends on filters
248
+ add_filter('posts_where', 'wp_all_export_posts_where', 10, 1);
249
+ add_filter('posts_where', 'wp_all_export_numbering_where', 15, 1);
250
+
251
+ add_filter('posts_join', 'wp_all_export_posts_join', 10, 1);
252
+
253
+
254
+ if($is_products_export) {
255
+
256
+ add_filter('posts_where', 'wp_all_export_numbering_where', 15, 1);
257
+
258
+ $productsQuery = new WP_Query( array( 'post_type' => array('product', 'product_variation'), 'post_status' => 'any', 'orderby' => 'ID', 'order' => 'ASC', 'posts_per_page' => 10));
259
+ $variationsQuery = new WP_Query( array( 'post_type' => 'product_variation', 'post_status' => 'any', 'orderby' => 'ID', 'order' => 'ASC', 'posts_per_page' => 10));
260
+
261
+ $foundProducts = $productsQuery->found_posts;
262
+
263
+ $foundVariations = $variationsQuery->found_posts;
264
+
265
+ $foundRecords = $foundProducts;
266
+ $hasVariations = !!$foundVariations;
267
+
268
+ remove_filter('posts_where', 'wp_all_export_numbering_where');
269
+
270
+ } else {
271
+ $exportQuery = new WP_Query( array( 'post_type' => $cpt, 'post_status' => 'any', 'orderby' => 'ID', 'order' => 'ASC', 'posts_per_page' => 10));
272
+ if ( ! empty($exportQuery->found_posts))
273
+ {
274
+ $foundRecords = $exportQuery->found_posts;
275
+ }
276
+ }
277
+
278
+ remove_filter('posts_where', 'wp_all_export_posts_where');
279
+ remove_filter('posts_where', 'wp_all_export_numbering_where');
280
+
281
+ remove_filter('posts_join', 'wp_all_export_posts_join');
282
+
283
+ ob_end_clean();
284
+
285
+ }
286
+ }
287
+
288
+ if(isset($exportQuery)) {
289
+ PMXE_Plugin::$session->set('exportQuery', $exportQuery);
290
+ PMXE_Plugin::$session->save_data();
291
+ }
292
+
293
+ if ( $post['is_confirm_screen'] )
294
+ {
295
+ ?>
296
+
297
+ <?php if ($foundRecords > 0) :?>
298
+ <h3><?php _e('Your export is ready to run.', 'wp_all_export_plugin'); ?></h3>
299
+ <h4><?php printf(__('WP All Export will export %d %s.', 'wp_all_export_plugin'), $foundRecords, wp_all_export_get_cpt_name($cpt, $foundRecords, $post)); ?></h4>
300
+ <?php else: ?>
301
+ <?php if (! $export->isEmpty() and ($export->options['export_only_new_stuff'] or $export->options['export_only_modified_stuff'])): ?>
302
+ <h3><?php _e('Nothing to export.', 'wp_all_export_plugin'); ?></h3>
303
+ <h4><?php printf(__("All %s have already been exported.", "wp_all_export_plugin"), wp_all_export_get_cpt_name($cpt, 2, $post)); ?></h4>
304
+ <?php elseif ($total_records > 0): ?>
305
+ <h3><?php _e('Nothing to export.', 'wp_all_export_plugin'); ?></h3>
306
+ <h4><?php printf(__("No matching %s found for selected filter rules.", "wp_all_export_plugin"), wp_all_export_get_cpt_name($cpt, 2, $post)); ?></h4>
307
+ <?php else: ?>
308
+ <h3><?php _e('Nothing to export.', 'wp_all_export_plugin'); ?></h3>
309
+ <h4><?php printf(__("There aren't any %s to export.", "wp_all_export_plugin"), wp_all_export_get_cpt_name($cpt, 2, $post)); ?></h4>
310
+ <?php endif; ?>
311
+ <?php endif; ?>
312
+
313
+ <?php
314
+ }
315
+ elseif( $post['is_template_screen'] )
316
+ {
317
+ ?>
318
+
319
+ <?php if ($foundRecords > 0) :?>
320
+ <h3><span class="matches_count"><?php echo $foundRecords; ?></span> <strong><?php echo wp_all_export_get_cpt_name($cpt, $foundRecords, $post); ?></strong> will be exported</h3>
321
+ <h4><?php _e("Drag &amp; drop data to include in the export file.", "wp_all_export_plugin"); ?></h4>
322
+ <?php else: ?>
323
+ <?php if (! $export->isEmpty() and ($export->options['export_only_new_stuff'] or $export->options['export_only_modified_stuff'])): ?>
324
+ <h3><?php _e('Nothing to export.', 'wp_all_export_plugin'); ?></h3>
325
+ <h4><?php printf(__("All %s have already been exported.", "wp_all_export_plugin"), wp_all_export_get_cpt_name($cpt, 2, $post)); ?></h4>
326
+ <?php elseif ($total_records > 0): ?>
327
+ <h3><?php _e('Nothing to export.', 'wp_all_export_plugin'); ?></h3>
328
+ <h4><?php printf(__("No matching %s found for selected filter rules.", "wp_all_export_plugin"), wp_all_export_get_cpt_name($cpt, 2, $post)); ?></h4>
329
+ <?php else: ?>
330
+ <h3><?php _e('Nothing to export.', 'wp_all_export_plugin'); ?></h3>
331
+ <h4><?php printf(__("There aren't any %s to export.", "wp_all_export_plugin"), wp_all_export_get_cpt_name($cpt, 2, $post)); ?></h4>
332
+ <?php endif; ?>
333
+ <?php endif; ?>
334
+
335
+ <?php
336
+ }
337
+ else
338
+ {
339
+ ?>
340
+ <div class="founded_records">
341
+ <?php if ($foundRecords > 0) :?>
342
+ <h3><span class="matches_count"><?php echo $foundRecords; ?></span> <strong><?php echo wp_all_export_get_cpt_name($cpt, $foundRecords, $post); ?></strong> will be exported</h3>
343
+ <h4><?php _e("Continue to configure and run your export.", "wp_all_export_plugin"); ?></h4>
344
+ <?php elseif ($total_records > 0): ?>
345
+ <h4 style="line-height:60px;"><?php printf(__("No matching %s found for selected filter rules.", "wp_all_export_plugin"), wp_all_export_get_cpt_name($cpt, 2, $post)); ?></h4>
346
+ <?php else: ?>
347
+ <h4 style="line-height:60px;"><?php printf(__("There aren't any %s to export.", "wp_all_export_plugin"), wp_all_export_get_cpt_name($cpt, 2, $post)); ?></h4>
348
+ <?php endif; ?>
349
+ </div>
350
+ <?php
351
+ }
352
+
353
+ exit(json_encode(array('html' => ob_get_clean(), 'found_records' => $foundRecords, 'hasVariations' => $hasVariations))); die;
354
+
355
+ }
356
+
357
+ function wp_all_export_numbering_where($where)
358
+ {
359
+ global $wpdb;
360
+
361
+ $excludeVariationsSql = " AND $wpdb->posts.ID NOT IN (SELECT o.ID FROM $wpdb->posts o
362
+ LEFT OUTER JOIN $wpdb->posts r ON o.post_parent = r.ID WHERE ((r.post_status = 'trash' OR r.ID IS NULL) AND o.post_type = 'product_variation'))";
363
+
364
+ $groupSql = "GROUP BY $wpdb->posts.ID";
365
+ if(strpos($where, $groupSql) !== false ){
366
+ $where = str_replace($groupSql, $excludeVariationsSql." ".$groupSql, $where);
367
+ } else {
368
+ $where = $where.$excludeVariationsSql;
369
+ }
370
+
371
+ return $where;
372
+ }
trunk/actions/wp_ajax_wpae_preview.php ADDED
@@ -0,0 +1,436 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * AJAX action for preview export row
4
+ */
5
+
6
+ function pmxe_wp_ajax_wpae_preview(){
7
+
8
+ if ( ! check_ajax_referer( 'wp_all_export_secure', 'security', false )){
9
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
10
+ }
11
+
12
+ if ( ! current_user_can( PMXE_Plugin::$capabilities ) ){
13
+ exit( json_encode(array('html' => __('Security check', 'wp_all_export_plugin'))) );
14
+ }
15
+
16
+ XmlExportEngine::$is_preview = true;
17
+
18
+ $custom_xml_valid = true;
19
+
20
+ ob_start();
21
+
22
+ $values = array();
23
+
24
+ parse_str($_POST['data'], $values);
25
+
26
+ if(is_array($values['cc_options'])) {
27
+
28
+ foreach ($values['cc_options'] as &$value) {
29
+ $value = stripslashes($value);
30
+ }
31
+ }
32
+
33
+ $export_id = (isset($_GET['id'])) ? stripcslashes($_GET['id']) : 0;
34
+
35
+ $exportOptions = $values + (PMXE_Plugin::$session->has_session() ? PMXE_Plugin::$session->get_clear_session_data() : array()) + PMXE_Plugin::get_default_import_options();
36
+
37
+ $exportOptions['custom_xml_template'] = (isset($_POST['custom_xml'])) ? stripcslashes($_POST['custom_xml']) : '';
38
+ $exportOptions['custom_xml_template'] = str_replace('<ID>','<id>', $exportOptions['custom_xml_template'] );
39
+ $exportOptions['custom_xml_template'] = str_replace('</ID>','</id>', $exportOptions['custom_xml_template'] );
40
+
41
+ if ( ! empty($exportOptions['custom_xml_template'])) {
42
+ $custom_xml_template_line_count = substr_count($exportOptions['custom_xml_template'], "\n");
43
+ }
44
+
45
+ $errors = new WP_Error();
46
+
47
+ $engine = new XmlExportEngine($exportOptions, $errors);
48
+
49
+ XmlExportEngine::$exportOptions = $exportOptions;
50
+ XmlExportEngine::$is_user_export = $exportOptions['is_user_export'];
51
+ XmlExportEngine::$is_comment_export = $exportOptions['is_comment_export'];
52
+ XmlExportEngine::$is_taxonomy_export = $exportOptions['is_taxonomy_export'];
53
+ XmlExportEngine::$exportID = $export_id;
54
+
55
+ if ( class_exists('SitePress') && ! empty(XmlExportEngine::$exportOptions['wpml_lang'])){
56
+ do_action( 'wpml_switch_language', XmlExportEngine::$exportOptions['wpml_lang'] );
57
+ }
58
+
59
+ if (XmlExportEngine::$exportOptions['export_to'] == XmlExportEngine::EXPORT_TYPE_XML && in_array(XmlExportEngine::$exportOptions['xml_template_type'], array('custom', 'XmlGoogleMerchants')) ){
60
+
61
+ if ( empty(XmlExportEngine::$exportOptions['custom_xml_template']) )
62
+ {
63
+ $errors->add('form-validation', __('XML template is empty.', 'wp_all_export_plugin'));
64
+ }
65
+
66
+ if ( ! empty(XmlExportEngine::$exportOptions['custom_xml_template'])){
67
+
68
+ $engine->init_additional_data();
69
+
70
+ $engine->init_available_data();
71
+
72
+ $result = $engine->parse_custom_xml_template();
73
+ $line_numbers = $result['line_numbers'];
74
+ if ( ! $errors->get_error_codes()) {
75
+ XmlExportEngine::$exportOptions = array_merge(XmlExportEngine::$exportOptions, $result);
76
+ }
77
+
78
+ $originalXmlTemplate = $exportOptions['custom_xml_template'];
79
+ libxml_use_internal_errors(true);
80
+ libxml_clear_errors();
81
+
82
+ //Add root se we make sure there is a root tag
83
+ $result['original_post_loop'] = '<root>'.$result['original_post_loop'].'</root>';
84
+
85
+ $custom_xml_template = simplexml_load_string($result['original_post_loop']);
86
+
87
+ if ($custom_xml_template === false) {
88
+ $custom_xml_template_errors = libxml_get_errors();
89
+ libxml_clear_errors();
90
+ $custom_xml_valid = false;
91
+ // Remove one line because we added root
92
+ $line_difference = $custom_xml_template_line_count - $line_numbers - 1;
93
+ }
94
+ $exportOptions['custom_xml_template'] = str_replace("<!-- BEGIN POST LOOP -->", "<!-- BEGIN LOOP -->", $exportOptions['custom_xml_template']);
95
+ $exportOptions['custom_xml_template'] = str_replace("<!-- END POST LOOP -->", "<!-- END LOOP -->", $exportOptions['custom_xml_template']);
96
+
97
+ }
98
+ }
99
+
100
+ if(isset($_GET['show_cdata'])) {
101
+ XmlExportEngine::$exportOptions['show_cdata_in_preview'] = (bool)$_GET['show_cdata'];
102
+ } else {
103
+ XmlExportEngine::$exportOptions['show_cdata_in_preview'] = false;
104
+ }
105
+
106
+ if ( $errors->get_error_codes()) {
107
+ $msgs = $errors->get_error_messages();
108
+ if ( ! is_array($msgs)) {
109
+ $msgs = array($msgs);
110
+ }
111
+ foreach ($msgs as $msg): ?>
112
+ <div class="error"><p><?php echo $msg ?></p></div>
113
+ <?php endforeach;
114
+ exit( json_encode(array('html' => ob_get_clean())) );
115
+ }
116
+
117
+ if ( 'advanced' == $exportOptions['export_type'] )
118
+ {
119
+ if ( XmlExportEngine::$is_user_export ) {
120
+ $exportQuery = eval('return new WP_User_Query(array(' . $exportOptions['wp_query'] . ', \'offset\' => 0, \'number\' => 10));');
121
+ }
122
+ elseif ( XmlExportEngine::$is_comment_export ) {
123
+ $exportQuery = eval('return new WP_Comment_Query(array(' . $exportOptions['wp_query'] . ', \'offset\' => 0, \'number\' => 10));');
124
+ }
125
+ else {
126
+ remove_all_actions('parse_query');
127
+ remove_all_actions('pre_get_posts');
128
+ remove_all_filters('posts_clauses');
129
+
130
+ $exportQuery = eval('return new WP_Query(array(' . $exportOptions['wp_query'] . ', \'offset\' => 0, \'posts_per_page\' => 10));');
131
+ }
132
+ }
133
+ else
134
+ {
135
+ XmlExportEngine::$post_types = $exportOptions['cpt'];
136
+
137
+ if ( in_array('users', $exportOptions['cpt']) or in_array('shop_customer', $exportOptions['cpt']))
138
+ {
139
+ add_action('pre_user_query', 'wp_all_export_pre_user_query', 10, 1);
140
+ $exportQuery = new WP_User_Query( array( 'orderby' => 'ID', 'order' => 'ASC', 'number' => 10 ));
141
+ remove_action('pre_user_query', 'wp_all_export_pre_user_query');
142
+ }
143
+ elseif ( in_array('taxonomies', $exportOptions['cpt']))
144
+ {
145
+ add_filter('terms_clauses', 'wp_all_export_terms_clauses', 10, 3);
146
+ $exportQuery = new WP_Term_Query( array( 'taxonomy' => $exportOptions['taxonomy_to_export'], 'orderby' => 'term_id', 'order' => 'ASC', 'number' => 10, 'hide_empty' => false ));
147
+ remove_filter('terms_clauses', 'wp_all_export_terms_clauses');
148
+ }
149
+ elseif( in_array('comments', $exportOptions['cpt']))
150
+ {
151
+ add_action('comments_clauses', 'wp_all_export_comments_clauses', 10, 1);
152
+
153
+ global $wp_version;
154
+
155
+ if ( version_compare($wp_version, '4.2.0', '>=') )
156
+ {
157
+ $exportQuery = new WP_Comment_Query( array( 'orderby' => 'comment_ID', 'order' => 'ASC', 'number' => 10 ));
158
+ }
159
+ else
160
+ {
161
+ $exportQuery = get_comments( array( 'orderby' => 'comment_ID', 'order' => 'ASC', 'number' => 10 ));
162
+ }
163
+ remove_action('comments_clauses', 'wp_all_export_comments_clauses');
164
+ }
165
+ else
166
+ {
167
+ remove_all_actions('parse_query');
168
+ remove_all_actions('pre_get_posts');
169
+ remove_all_filters('posts_clauses');
170
+
171
+ add_filter('posts_join', 'wp_all_export_posts_join', 10, 1);
172
+ add_filter('posts_where', 'wp_all_export_posts_where', 10, 1);
173
+ $exportQuery = new WP_Query( array( 'post_type' => $exportOptions['cpt'], 'post_status' => 'any', 'orderby' => 'title', 'order' => 'ASC', 'posts_per_page' => 10 ));
174
+
175
+ remove_filter('posts_where', 'wp_all_export_posts_where');
176
+ remove_filter('posts_join', 'wp_all_export_posts_join');
177
+ }
178
+ }
179
+
180
+ XmlExportEngine::$exportQuery = $exportQuery;
181
+
182
+ $engine->init_additional_data();
183
+
184
+ ?>
185
+
186
+ <div id="post-preview" class="wpallexport-preview">
187
+
188
+ <p class="wpallexport-preview-title"><?php echo sprintf("Preview first 10 %s", wp_all_export_get_cpt_name($exportOptions['cpt'], 10, $exportOptions)); ?></p>
189
+
190
+ <div class="wpallexport-preview-content">
191
+
192
+ <?php
193
+
194
+ if(!$custom_xml_valid) {
195
+ $error_msg = '<strong class="error">' . __('Invalid XML', 'wp_all_import_plugin') . '</strong><ul class="error">';
196
+ foreach($custom_xml_template_errors as $error) {
197
+ $error_msg .= '<li>';
198
+ $error_msg .= __('Line', 'wp_all_import_plugin') . ' ' . ($error->line + $line_difference) . ', ';
199
+ $error_msg .= __('Column', 'wp_all_import_plugin') . ' ' . $error->column . ', ';
200
+ $error_msg .= __('Code', 'wp_all_import_plugin') . ' ' . $error->code . ': ';
201
+ $error_msg .= '<em>' . trim(esc_html($error->message)) . '</em>';
202
+ $error_msg .= '</li>';
203
+ }
204
+ $error_msg .= '</ul>';
205
+ echo $error_msg;
206
+ exit( json_encode(array('html' => ob_get_clean())) );
207
+ }
208
+
209
+ switch ($exportOptions['export_to']) {
210
+
211
+ case 'xml':
212
+
213
+ $dom = new DOMDocument('1.0', $exportOptions['encoding']);
214
+ libxml_use_internal_errors(true);
215
+ try{
216
+ $xml = XmlCsvExport::export_xml(true);
217
+ } catch (WpaeMethodNotFoundException $e) {
218
+ // Find the line where the function is
219
+ $errorMessage = '';
220
+ $functionName = $e->getMessage();
221
+ $txtParts = explode("\n",$originalXmlTemplate);
222
+ for ($i=0, $length = count($txtParts);$i<$length;$i++)
223
+ {
224
+ $tmp = strstr($txtParts[$i], $functionName);
225
+ if ($tmp) {
226
+ $errorMessage .= 'Error parsing XML feed: Call to undefined function <em>"'.$functionName.'"</em> on Line '.($i+1);
227
+ }
228
+ }
229
+
230
+ $error_msg = '<span class="error">'.__($errorMessage, 'wp_all_import_plugin').'</span>';
231
+ echo $error_msg;
232
+ exit( json_encode(array('html' => ob_get_clean())) );
233
+ } catch (WpaeInvalidStringException $e) {
234
+ // Find the line where the function is
235
+ $errorMessage = '';
236
+ $functionName = $e->getMessage();
237
+ $txtParts = explode("\n",$originalXmlTemplate);
238
+ for ($i=0, $length = count($txtParts);$i<$length;$i++)
239
+ {
240
+ $tmp = strstr($txtParts[$i], $functionName);
241
+ if ($tmp) {
242
+ $errorMessage .= 'Error parsing XML feed: Unterminated string on line '.($i+1);
243
+ }
244
+ }
245
+
246
+ $error_msg = '<span class="error">'.__($errorMessage, 'wp_all_import_plugin').'</span>';
247
+ echo $error_msg;
248
+ exit( json_encode(array('html' => ob_get_clean())) );
249
+ } catch (WpaeTooMuchRecursionException $e) {
250
+ $errorMessage = __('There was a problem parsing the custom XML template');
251
+ $error_msg = '<span class="error">'.__($errorMessage, 'wp_all_import_plugin').'</span>';
252
+ echo $error_msg;
253
+ exit( json_encode(array('html' => ob_get_clean())) );
254
+ }
255
+
256
+ $xml_errors = false;
257
+
258
+ $main_xml_tag = '';
259
+
260
+ switch ( XmlExportEngine::$exportOptions['xml_template_type'] ){
261
+
262
+ case 'custom':
263
+ case 'XmlGoogleMerchants':
264
+
265
+ require_once PMXE_ROOT_DIR . '/classes/XMLWriter.php';
266
+
267
+ $preview_xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" . "\n<Preview>\n" . $xml . "\n</Preview>";
268
+
269
+ $preview_xml = str_replace('<![CDATA[', 'CDATABEGIN', $preview_xml);
270
+ $preview_xml = str_replace(']]>', 'CDATACLOSE', $preview_xml);
271
+ $preview_xml = str_replace('&amp;', '&', $preview_xml);
272
+ $preview_xml = str_replace('&', '&amp;', $preview_xml);
273
+
274
+ $xml = PMXE_XMLWriter::preprocess_xml( XmlExportEngine::$exportOptions['custom_xml_template_header'] ) . "\n" . $xml . "\n" . PMXE_XMLWriter::preprocess_xml( XmlExportEngine::$exportOptions['custom_xml_template_footer'] );
275
+
276
+ $xml = str_replace('<![CDATA[', 'CDATABEGIN', $xml);
277
+ $xml = str_replace(']]>', 'CDATACLOSE', $xml);
278
+ $xml = str_replace('&amp;', '&', $xml);
279
+ $xml = str_replace('&', '&amp;', $xml);
280
+
281
+ // Determine XML root element
282
+ preg_match_all("%<[\w]+[\s|>]{1}%", XmlExportEngine::$exportOptions['custom_xml_template_header'], $matches);
283
+
284
+ if ( ! empty($matches[0]) ){
285
+ $main_xml_tag = preg_replace("%[\s|<|>]%","",array_shift($matches[0]));
286
+ }
287
+
288
+ libxml_clear_errors();
289
+ $dom->loadXML($xml);
290
+ $xml_errors = libxml_get_errors();
291
+ libxml_clear_errors();
292
+ if (! $xml_errors ){
293
+ $xpath = new DOMXPath($dom);
294
+ if (($elements = @$xpath->query('/' . $main_xml_tag)) and $elements->length){
295
+ pmxe_render_xml_element($elements->item( 0 ), true);
296
+ }
297
+ else{
298
+ $xml_errors = true;
299
+ }
300
+ }
301
+
302
+ break;
303
+
304
+ default:
305
+
306
+ libxml_clear_errors();
307
+ $dom->loadXML($xml);
308
+ $xml_errors = libxml_get_errors();
309
+ libxml_clear_errors();
310
+
311
+ $xpath = new DOMXPath($dom);
312
+
313
+ // Determine XML root element
314
+ $main_xml_tag = apply_filters('wp_all_export_main_xml_tag', $exportOptions['main_xml_tag'], XmlExportEngine::$exportID);
315
+ $elements = @$xpath->query('/' . $main_xml_tag);
316
+ if ($elements->length){
317
+ pmxe_render_xml_element($elements->item( 0 ), true);
318
+ $xml_errors = false;
319
+ }
320
+ else{
321
+ $error_msg = '<strong>' . __('Can\'t preview the document.', 'wp_all_import_plugin') . '</strong><ul>';
322
+ $error_msg .= '<li>';
323
+ $error_msg .= __('You can continue export or try to use &lt;data&gt; tag as root element.', 'wp_all_import_plugin');
324
+ $error_msg .= '</li>';
325
+ $error_msg .= '</ul>';
326
+ echo $error_msg;
327
+ exit( json_encode(array('html' => ob_get_clean())) );
328
+ }
329
+ break;
330
+
331
+ }
332
+
333
+ if ( $xml_errors ){
334
+
335
+ $preview_dom = new DOMDocument('1.0', $exportOptions['encoding']);
336
+ libxml_clear_errors();
337
+ $preview_dom->loadXML($preview_xml);
338
+ $preview_xml_errors = libxml_get_errors();
339
+ libxml_clear_errors();
340
+
341
+ if ($preview_xml_errors){
342
+ $error_msg = '<strong class="error">' . __('Invalid XML', 'wp_all_import_plugin') . '</strong><ul class="error">';
343
+ foreach($preview_xml_errors as $error) {
344
+ $error_msg .= '<li>';
345
+ $error_msg .= __('Line', 'wp_all_import_plugin') . ' ' . $error->line . ', ';
346
+ $error_msg .= __('Column', 'wp_all_import_plugin') . ' ' . $error->column . ', ';
347
+ $error_msg .= __('Code', 'wp_all_import_plugin') . ' ' . $error->code . ': ';
348
+ $error_msg .= '<em>' . trim(esc_html($error->message)) . '</em>';
349
+ $error_msg .= '</li>';
350
+ }
351
+ $error_msg .= '</ul>';
352
+ echo $error_msg;
353
+ exit( json_encode(array('html' => ob_get_clean())) );
354
+ }
355
+ else{
356
+ $xpath = new DOMXPath($preview_dom);
357
+ if (($elements = @$xpath->query('/Preview')) and $elements->length){
358
+ pmxe_render_xml_element($elements->item( 0 ), true);
359
+ }
360
+ else{
361
+ $error_msg = '<strong>' . __('Can\'t preview the document. Root element is not detected.', 'wp_all_import_plugin') . '</strong><ul>';
362
+ $error_msg .= '<li>';
363
+ $error_msg .= __('You can continue export or try to use &lt;data&gt; tag as root element.', 'wp_all_import_plugin');
364
+ $error_msg .= '</li>';
365
+ $error_msg .= '</ul>';
366
+ echo $error_msg;
367
+ exit( json_encode(array('html' => ob_get_clean())) );
368
+ }
369
+ }
370
+ }
371
+
372
+ break;
373
+
374
+ case 'csv':
375
+ ?>
376
+ <small>
377
+ <?php
378
+
379
+ $csv = XmlCsvExport::export_csv( true );
380
+
381
+ if (!empty($csv)){
382
+ $csv_rows = array_filter(explode("\n", $csv));
383
+ if ($csv_rows){
384
+ ?>
385
+ <table class="pmxe_preview" cellpadding="0" cellspacing="0">
386
+ <?php
387
+ foreach ($csv_rows as $rkey => $row) {
388
+ $cells = str_getcsv($row, $exportOptions['delimiter']);
389
+ if ($cells){
390
+ ?>
391
+ <tr>
392
+ <?php
393
+ foreach ($cells as $key => $value) {
394
+ ?>
395
+ <td>
396
+ <?php if (!$rkey):?><strong><?php endif;?>
397
+ <?php echo $value; ?>
398
+ <?php if (!$rkey):?></strong><?php endif;?>
399
+ </td>
400
+ <?php
401
+ }
402
+ ?>
403
+ </tr>
404
+ <?php
405
+ }
406
+ }
407
+ ?>
408
+ </table>
409
+ <?php
410
+ }
411
+ }
412
+ else{
413
+ _e('Data not found.', 'wp_all_export_plugin');
414
+ }
415
+ ?>
416
+ </small>
417
+ <?php
418
+ break;
419
+
420
+ default:
421
+
422
+ _e('This format is not supported.', 'wp_all_export_plugin');
423
+
424
+ break;
425
+ }
426
+ wp_reset_postdata();
427
+ ?>
428
+
429
+ </div>
430
+
431
+ </div>
432
+
433
+ <?php
434
+
435
+ exit(json_encode(array('html' => ob_get_clean()))); die;
436
+ }
trunk/actions/wp_ajax_wpallexport.php ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * AJAX action export processing
4
+ */
5
+ function pmxe_wp_ajax_wpallexport()
6
+ {
7
+
8
+ if (!check_ajax_referer('wp_all_export_secure', 'security', false)) {
9
+ exit(__('Security check', 'wp_all_export_plugin'));
10
+ }
11
+
12
+ if (!current_user_can(PMXE_Plugin::$capabilities)) {
13
+ exit(__('Security check', 'wp_all_export_plugin'));
14
+ }
15
+
16
+ $input = new PMXE_Input();
17
+ $export_id = $input->get('id', 0);
18
+ if (empty($export_id)) {
19
+ $export_id = (!empty(PMXE_Plugin::$session->update_previous)) ? PMXE_Plugin::$session->update_previous : 0;
20
+ }
21
+
22
+ $wp_uploads = wp_upload_dir();
23
+
24
+ $export = new PMXE_Export_Record();
25
+
26
+ $export->getById($export_id);
27
+
28
+ if ($export->isEmpty()) {
29
+ exit(__('Export is not defined.', 'wp_all_export_plugin'));
30
+ }
31
+
32
+ $exportOptions = $export->options + PMXE_Plugin::get_default_import_options();
33
+
34
+ wp_reset_postdata();
35
+
36
+ XmlExportEngine::$exportOptions = $exportOptions;
37
+ XmlExportEngine::$is_user_export = $exportOptions['is_user_export'];
38
+ XmlExportEngine::$is_comment_export = $exportOptions['is_comment_export'];
39
+ XmlExportEngine::$is_taxonomy_export = empty($exportOptions['is_taxonomy_export']) ? false : $exportOptions['is_taxonomy_export'];
40
+ XmlExportEngine::$exportID = $export_id;
41
+ XmlExportEngine::$exportRecord = $export;
42
+
43
+ if (class_exists('SitePress') && !empty(XmlExportEngine::$exportOptions['wpml_lang'])) {
44
+ do_action('wpml_switch_language', XmlExportEngine::$exportOptions['wpml_lang']);
45
+ }
46
+
47
+ $errors = new WP_Error();
48
+ $engine = new XmlExportEngine($exportOptions, $errors);
49
+
50
+ $posts_per_page = $exportOptions['records_per_iteration'];
51
+
52
+ if ($exportOptions['export_type'] == 'advanced') {
53
+ if (XmlExportEngine::$is_user_export) {
54
+ add_action('pre_user_query', 'wp_all_export_pre_user_query', 10, 1);
55
+ $exportQuery = eval('return new WP_User_Query(array(' . $exportOptions['wp_query'] . ', \'offset\' => ' . $export->exported . ', \'number\' => ' . $posts_per_page . ' ));');
56
+ remove_action('pre_user_query', 'wp_all_export_pre_user_query');
57
+ } elseif (XmlExportEngine::$is_comment_export) {
58
+ add_action('comments_clauses', 'wp_all_export_comments_clauses', 10, 1);
59
+ $exportQuery = eval('return new WP_Comment_Query(array(' . $exportOptions['wp_query'] . ', \'offset\' => ' . $export->exported . ', \'number\' => ' . $posts_per_page . ' ));');
60
+ remove_action('comments_clauses', 'wp_all_export_comments_clauses');
61
+ } else {
62
+ remove_all_actions('parse_query');
63
+ remove_all_actions('pre_get_posts');
64
+ remove_all_filters('posts_clauses');
65
+
66
+ add_filter('posts_join', 'wp_all_export_posts_join', 10, 1);
67
+ add_filter('posts_where', 'wp_all_export_posts_where', 10, 1);
68
+ $code = 'return new WP_Query(array(' . $exportOptions['wp_query'] . ', \'offset\' => ' . $export->exported . ', \'posts_per_page\' => ' . $posts_per_page . ' ));';
69
+ $exportQuery = eval($code);
70
+
71
+ remove_filter('posts_where', 'wp_all_export_posts_where');
72
+ remove_filter('posts_join', 'wp_all_export_posts_join');
73
+ }
74
+ } else {
75
+ XmlExportEngine::$post_types = $exportOptions['cpt'];
76
+
77
+ // $is_products_export = ($exportOptions['cpt'] == 'product' and class_exists('WooCommerce'));
78
+
79
+ if (in_array('users', $exportOptions['cpt']) or in_array('shop_customer', $exportOptions['cpt'])) {
80
+ add_action('pre_user_query', 'wp_all_export_pre_user_query', 10, 1);
81
+ $exportQuery = new WP_User_Query(array('orderby' => 'ID', 'order' => 'ASC', 'number' => $posts_per_page, 'offset' => $export->exported));
82
+ remove_action('pre_user_query', 'wp_all_export_pre_user_query');
83
+ } elseif (in_array('taxonomies', $exportOptions['cpt'])) {
84
+ add_filter('terms_clauses', 'wp_all_export_terms_clauses', 10, 3);
85
+ $exportQuery = new WP_Term_Query(array('taxonomy' => $exportOptions['taxonomy_to_export'], 'orderby' => 'term_id', 'order' => 'ASC', 'number' => $posts_per_page, 'offset' => $export->exported, 'hide_empty' => false));
86
+ $postCount = count($exportQuery->get_terms());
87
+ remove_filter('terms_clauses', 'wp_all_export_terms_clauses');
88
+ } elseif (in_array('comments', $exportOptions['cpt'])) {
89
+ add_action('comments_clauses', 'wp_all_export_comments_clauses', 10, 1);
90
+
91
+ global $wp_version;
92
+
93
+ if (version_compare($wp_version, '4.2.0', '>=')) {
94
+ $exportQuery = new WP_Comment_Query(array('orderby' => 'comment_ID', 'order' => 'ASC', 'number' => $posts_per_page, 'offset' => $export->exported));
95
+ } else {
96
+ $exportQuery = get_comments(array('orderby' => 'comment_ID', 'order' => 'ASC', 'number' => $posts_per_page, 'offset' => $export->exported));
97
+ }
98
+ remove_action('comments_clauses', 'wp_all_export_comments_clauses');
99
+ } else {
100
+ remove_all_actions('parse_query');
101
+ remove_all_actions('pre_get_posts');
102
+ remove_all_filters('posts_clauses');
103
+ remove_all_filters('posts_orderby');
104
+
105
+ add_filter('posts_join', 'wp_all_export_posts_join', 10, 1);
106
+ add_filter('posts_where', 'wp_all_export_posts_where', 10, 1);
107
+ $exportQuery = new WP_Query(array('post_type' => $exportOptions['cpt'], 'post_status' => 'any', 'orderby' => 'ID', 'order' => 'ASC', 'offset' => $export->exported, 'posts_per_page' => $posts_per_page));
108
+ remove_filter('posts_where', 'wp_all_export_posts_where');
109
+ remove_filter('posts_join', 'wp_all_export_posts_join');
110
+ }
111
+ }
112
+
113
+ XmlExportEngine::$exportQuery = $exportQuery;
114
+
115
+ $engine->init_additional_data();
116
+
117
+ // get total founded records
118
+ if (XmlExportEngine::$is_comment_export) {
119
+ global $wp_version;
120
+
121
+ if (version_compare($wp_version, '4.2.0', '>=')) {
122
+ $postCount = count($exportQuery->get_comments());
123
+ add_action('comments_clauses', 'wp_all_export_comments_clauses', 10, 1);
124
+ $result = new WP_Comment_Query(array('orderby' => 'comment_ID', 'order' => 'ASC', 'number' => 10, 'count' => true));
125
+ $foundPosts = $result->get_comments();
126
+ remove_action('comments_clauses', 'wp_all_export_comments_clauses');
127
+ } else {
128
+ $postCount = count($exportQuery);
129
+ add_action('comments_clauses', 'wp_all_export_comments_clauses', 10, 1);
130
+ $foundPosts = get_comments(array('orderby' => 'comment_ID', 'order' => 'ASC', 'number' => 10, 'count' => true));
131
+ remove_action('comments_clauses', 'wp_all_export_comments_clauses');
132
+ }
133
+ } elseif (XmlExportEngine::$is_taxonomy_export) {
134
+ add_filter('terms_clauses', 'wp_all_export_terms_clauses', 10, 3);
135
+ $result = new WP_Term_Query(array('taxonomy' => $exportOptions['taxonomy_to_export'], 'orderby' => 'term_id', 'order' => 'ASC', 'hide_empty' => false));
136
+ $foundPosts = count($result->get_terms());
137
+ remove_filter('terms_clauses', 'wp_all_export_terms_clauses');
138
+ } else {
139
+
140
+ if(XmlExportEngine::$is_user_export) {
141
+ $foundPosts = $exportQuery->get_total();
142
+ $postCount = count($exportQuery->get_results());
143
+ } else {
144
+ $foundPosts = $exportQuery->found_posts;
145
+ $postCount = $exportQuery->post_count;
146
+ }
147
+ }
148
+ // [ \get total founded records ]
149
+
150
+ if (!$export->exported) {
151
+ $attachment_list = $export->options['attachment_list'];
152
+ if (!empty($attachment_list)) {
153
+ foreach ($attachment_list as $attachment) {
154
+ if (!is_numeric($attachment)) {
155
+ @unlink($attachment);
156
+ }
157
+ }
158
+ }
159
+ $exportOptions['attachment_list'] = array();
160
+ $export->set(array(
161
+ 'options' => $exportOptions
162
+ ))->save();
163
+
164
+ $is_secure_import = PMXE_Plugin::getInstance()->getOption('secure');
165
+
166
+ if ($is_secure_import and !empty($exportOptions['filepath'])) {
167
+
168
+ $exportOptions['filepath'] = '';
169
+ }
170
+
171
+ PMXE_Plugin::$session->set('count', $foundPosts);
172
+ PMXE_Plugin::$session->save_data();
173
+ }
174
+
175
+ // Export posts
176
+ XmlCsvExport::export();
177
+
178
+ $export->set(array(
179
+ 'exported' => $export->exported + $postCount,
180
+ 'last_activity' => date('Y-m-d H:i:s')
181
+ ))->save();
182
+
183
+
184
+ if ($posts_per_page != -1 && $postCount && !isAdvancedSingleItemExport($postCount, $foundPosts)) {
185
+
186
+ $percentage = ceil(($export->exported / $foundPosts) * 100);
187
+
188
+ $responseArray = array(
189
+ 'export_id' => $export->id,
190
+ 'queue_export' => false,
191
+ 'exported' => $export->exported,
192
+ 'percentage' => $percentage,
193
+ 'done' => false,
194
+ 'posts' => $postCount,
195
+ 'records_per_request' => $exportOptions['records_per_iteration']
196
+ );
197
+
198
+ if(isset($code)){
199
+ $responseArray['code'] = $code;
200
+ } else {
201
+ $responseArray['code'] = '';
202
+ }
203
+
204
+ wp_send_json($responseArray);
205
+ } else {
206
+ if (file_exists(PMXE_Plugin::$session->file)) {
207
+
208
+ if ($exportOptions['export_to'] == 'xml') {
209
+ switch (XmlExportEngine::$exportOptions['xml_template_type']) {
210
+ case 'custom':
211
+ require_once PMXE_ROOT_DIR . '/classes/XMLWriter.php';
212
+ file_put_contents(PMXE_Plugin::$session->file, PMXE_XMLWriter::preprocess_xml(XmlExportEngine::$exportOptions['custom_xml_template_footer']), FILE_APPEND);
213
+ break;
214
+ default:
215
+
216
+ break;
217
+ }
218
+
219
+ if (!in_array(XmlExportEngine::$exportOptions['xml_template_type'], array('custom', 'XmlGoogleMerchants'))) {
220
+ $main_xml_tag = apply_filters('wp_all_export_main_xml_tag', $exportOptions['main_xml_tag'], $export->id);
221
+
222
+ file_put_contents(PMXE_Plugin::$session->file, '</' . $main_xml_tag . '>', FILE_APPEND);
223
+
224
+ $xml_footer = apply_filters('wp_all_export_xml_footer', '', $export->id);
225
+
226
+ if (!empty($xml_footer)) file_put_contents(PMXE_Plugin::$session->file, $xml_footer, FILE_APPEND);
227
+ }
228
+ }
229
+
230
+ $is_secure_import = PMXE_Plugin::getInstance()->getOption('secure');
231
+
232
+ if (!$is_secure_import) {
233
+
234
+ if (!$export->isEmpty()) {
235
+
236
+ $wp_filetype = wp_check_filetype(basename(PMXE_Plugin::$session->file), null);
237
+ $attachment_data = array(
238
+ 'guid' => $wp_uploads['baseurl'] . '/' . _wp_relative_upload_path(PMXE_Plugin::$session->file),
239
+ 'post_mime_type' => $wp_filetype['type'],
240
+ 'post_title' => preg_replace('/\.[^.]+$/', '', basename(PMXE_Plugin::$session->file)),
241
+ 'post_content' => '',
242
+ 'post_status' => 'inherit'
243
+ );
244
+
245
+ if (empty($export->attch_id)) {
246
+ $attach_id = wp_insert_attachment($attachment_data, PMXE_Plugin::$session->file);
247
+ } elseif ($export->options['creata_a_new_export_file']) {
248
+ $attach_id = wp_insert_attachment($attachment_data, PMXE_Plugin::$session->file);
249
+ } else {
250
+ $attach_id = $export->attch_id;
251
+ $attachment = get_post($attach_id);
252
+ if ($attachment) {
253
+ update_attached_file($attach_id, PMXE_Plugin::$session->file);
254
+ wp_update_attachment_metadata($attach_id, $attachment_data);
255
+ } else {
256
+ $attach_id = wp_insert_attachment($attachment_data, PMXE_Plugin::$session->file);
257
+ }
258
+ }
259
+
260
+ if (!in_array($attach_id, $exportOptions['attachment_list'])) $exportOptions['attachment_list'][] = $attach_id;
261
+
262
+ $export->set(array(
263
+ 'attch_id' => $attach_id,
264
+ 'options' => $exportOptions
265
+ ))->save();
266
+ }
267
+
268
+ } else {
269
+ $exportOptions['filepath'] = wp_all_export_get_relative_path(PMXE_Plugin::$session->file);
270
+
271
+ if (!$export->isEmpty()) {
272
+ $export->set(array(
273
+ 'options' => $exportOptions
274
+ ))->save();
275
+ }
276
+
277
+ }
278
+
279
+ PMXE_Wpallimport::generateImportTemplate($export, PMXE_Plugin::$session->file, PMXE_Plugin::$session->count);
280
+
281
+ }
282
+
283
+ $export->set(array(
284
+ 'executing' => 0,
285
+ 'canceled' => 0,
286
+ 'iteration' => ++$export->iteration
287
+ ))->save();
288
+
289
+ do_action('pmxe_after_export', $export->id, $export);
290
+
291
+ $queue_exports = empty($export->parent_id) ? array() : get_option('wp_all_export_queue_' . $export->parent_id);
292
+
293
+ // update child exports with correct WHERE & JOIN filters
294
+ if (!empty($export->options['cpt']) and class_exists('WooCommerce') and in_array('shop_order', $export->options['cpt']) and empty($export->parent_id)) {
295
+ $queue_exports = XmlExportWooCommerceOrder::prepare_child_exports($export);
296
+ }
297
+
298
+ if (!empty($queue_exports) and !empty($export->parent_id)) {
299
+ array_shift($queue_exports);
300
+ }
301
+
302
+ if (empty($queue_exports)) {
303
+ delete_option('wp_all_export_queue_' . (empty($export->parent_id) ? $export->id : $export->parent_id));
304
+ } else {
305
+ update_option('wp_all_export_queue_' . (empty($export->parent_id) ? $export->id : $export->parent_id), $queue_exports);
306
+ }
307
+
308
+ wp_send_json(array(
309
+ 'export_id' => $export->id,
310
+ 'queue_export' => empty($queue_exports) ? false : $queue_exports[0],
311
+ 'exported' => $export->exported,
312
+ 'percentage' => 100,
313
+ 'done' => true,
314
+ 'records_per_request' => $exportOptions['records_per_iteration']
315
+ ));
316
+ }
317
+ }
318
+
319
+ /**
320
+ * @param $postCount
321
+ * @param $foundPosts
322
+ * @return bool
323
+ */
324
+ function isAdvancedSingleItemExport($postCount, $foundPosts)
325
+ {
326
+ return ($postCount == 1 && $foundPosts == 1);
327
+ }
trunk/actions/wp_loaded.php ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_wp_loaded() {
4
+
5
+ @ini_set("max_input_time", PMXE_Plugin::getInstance()->getOption('max_input_time'));
6
+
7
+ $maxExecutionTime = PMXE_Plugin::getInstance()->getOption('max_execution_time');
8
+ if($maxExecutionTime == -1) {
9
+ $maxExecutionTime = 0;
10
+ }
11
+
12
+ @ini_set("max_execution_time", $maxExecutionTime);
13
+
14
+ $scheduledExport = new \Wpae\Scheduling\Export();
15
+
16
+ if ( ! empty($_GET['zapier_subscribe']) and ! empty($_GET['api_key']) )
17
+ {
18
+ $zapier_api_key = PMXE_Plugin::getInstance()->getOption('zapier_api_key');
19
+
20
+ if ( ! empty($zapier_api_key) and $zapier_api_key == $_GET['api_key'] )
21
+ {
22
+ $subscriptions = get_option('zapier_subscribe', array());
23
+
24
+ $body = json_decode(file_get_contents("php://input"), true);
25
+
26
+ if ( ! empty($body))
27
+ {
28
+ $subscriptions[basename($body['target_url'])] = $body;
29
+ }
30
+
31
+ update_option('zapier_subscribe', $subscriptions);
32
+
33
+ exit(json_encode(array('status' => 200)));
34
+ }
35
+ else
36
+ {
37
+ http_response_code(401);
38
+ exit(json_encode(array('status' => 'error')));
39
+ }
40
+ }
41
+
42
+ if ( ! empty($_GET['zapier_unsubscribe']) and ! empty($_GET['api_key']) )
43
+ {
44
+ $zapier_api_key = PMXE_Plugin::getInstance()->getOption('zapier_api_key');
45
+
46
+ if ( ! empty($zapier_api_key) and $zapier_api_key == $_GET['api_key'] )
47
+ {
48
+ $subscriptions = get_option('zapier_subscribe', array());
49
+
50
+ $body = json_decode(file_get_contents("php://input"), true);
51
+
52
+ if ( ! empty($subscriptions[basename($body['target_url'])]) ) unset($subscriptions[basename($body['target_url'])]);
53
+
54
+ update_option('zapier_subscribe', $subscriptions);
55
+
56
+ exit(json_encode(array('status' => 200)));
57
+ }
58
+ else
59
+ {
60
+ http_response_code(401);
61
+ exit(json_encode(array('status' => 'error')));
62
+ }
63
+ }
64
+
65
+ if ( ! empty($_GET['export_completed']) and ! empty($_GET['api_key']))
66
+ {
67
+
68
+ $zapier_api_key = PMXE_Plugin::getInstance()->getOption('zapier_api_key');
69
+
70
+ if ( ! empty($zapier_api_key) and $zapier_api_key == $_GET['api_key'] )
71
+ {
72
+
73
+ global $wpdb;
74
+
75
+ $table_prefix = PMXE_Plugin::getInstance()->getTablePrefix();
76
+
77
+ $export = $wpdb->get_row("SELECT * FROM {$table_prefix}exports ORDER BY `id` DESC LIMIT 1");
78
+
79
+ if ( ! empty($export) and ! is_wp_error($export) )
80
+ {
81
+ $child_export = new PMXE_Export_Record();
82
+ $child_export->getById( $export->id );
83
+
84
+ if ( ! $child_export->isEmpty())
85
+ {
86
+ $wp_uploads = wp_upload_dir();
87
+
88
+ $is_secure_import = PMXE_Plugin::getInstance()->getOption('secure');
89
+
90
+ if ( ! $is_secure_import)
91
+ {
92
+ $filepath = get_attached_file($child_export->attch_id);
93
+ }
94
+ else
95
+ {
96
+ $filepath = wp_all_export_get_absolute_path($child_export->options['filepath']);
97
+ }
98
+
99
+ $fileurl = str_replace($wp_uploads['basedir'], $wp_uploads['baseurl'], $filepath);
100
+
101
+ $response = array(
102
+ 'website_url' => home_url(),
103
+ 'export_id' => $child_export->id,
104
+ 'export_name' => $child_export->friendly_name,
105
+ 'file_name' => basename($filepath),
106
+ 'file_type' => $child_export->options['export_to'],
107
+ 'post_types_exported' => empty($child_export->options['cpt']) ? $child_export->options['wp_query'] : implode($child_export->options['cpt'], ','),
108
+ 'export_created_date' => $child_export->registered_on,
109
+ 'export_last_run_date' => date('Y-m-d H:i:s'),
110
+ 'export_trigger_type' => 'manual',
111
+ 'records_exported' => $child_export->exported,
112
+ 'export_file' => ''
113
+ );
114
+
115
+ if (file_exists($filepath))
116
+ {
117
+ $response['export_file_url'] = $fileurl;
118
+ $response['status'] = 200;
119
+ $response['message'] = 'OK';
120
+ }
121
+ else
122
+ {
123
+ $response['export_file_url'] = '';
124
+ $response['status'] = 300;
125
+ $response['message'] = 'File doesn\'t exist';
126
+ }
127
+
128
+ $response = apply_filters('wp_all_export_zapier_response', $response);
129
+
130
+ wp_send_json(array($response));
131
+ }
132
+ }
133
+
134
+ }
135
+ else
136
+ {
137
+ http_response_code(401);
138
+ exit(json_encode(array('status' => 'error')));
139
+ }
140
+ }
141
+
142
+ /* Check if cron is manualy, then execute export */
143
+ $cron_job_key = PMXE_Plugin::getInstance()->getOption('cron_job_key');
144
+
145
+ if ( ! empty($_GET['action']) && ! empty($_GET['export_id']) && (!empty($_GET['export_hash']) || !empty($_GET['security_token'])))
146
+ {
147
+
148
+ if(empty($_GET['export_hash'])) {
149
+ $securityToken = $_GET['security_token'];
150
+ } else {
151
+ $securityToken = $_GET['export_hash'];
152
+ }
153
+
154
+ if ( $securityToken == substr(md5($cron_job_key . $_GET['export_id']), 0, 16) )
155
+ {
156
+ $export = new PMXE_Export_Record();
157
+
158
+ $export->getById($_GET['export_id']);
159
+
160
+ if ( ! $export->isEmpty())
161
+ {
162
+ switch ($_GET['action'])
163
+ {
164
+ case 'get_data':
165
+
166
+ if ( ! empty($export->options['current_filepath']) and @file_exists($export->options['current_filepath']))
167
+ {
168
+ $filepath = $export->options['current_filepath'];
169
+ }
170
+ else
171
+ {
172
+ $is_secure_import = PMXE_Plugin::getInstance()->getOption('secure');
173
+
174
+ if ( ! $is_secure_import)
175
+ {
176
+ $filepath = get_attached_file($export->attch_id);
177
+ }
178
+ else
179
+ {
180
+ $filepath = wp_all_export_get_absolute_path($export->options['filepath']);
181
+ }
182
+ }
183
+
184
+ if ( ! empty($_GET['part']) and is_numeric($_GET['part'])) $filepath = str_replace(basename($filepath), str_replace('.' . $export->options['export_to'], '', basename($filepath)) . '-' . $_GET['part'] . '.' . $export->options['export_to'], $filepath);
185
+
186
+ break;
187
+
188
+ case 'get_bundle':
189
+
190
+ $filepath = wp_all_export_get_absolute_path($export->options['bundlepath']);
191
+
192
+ break;
193
+ }
194
+
195
+ if (file_exists($filepath))
196
+ {
197
+ $uploads = wp_upload_dir();
198
+ $fileurl = $uploads['baseurl'] . str_replace($uploads['basedir'], '', str_replace(basename($filepath), rawurlencode(basename($filepath)), $filepath));
199
+
200
+ if($export['options']['export_to'] == XmlExportEngine::EXPORT_TYPE_XML && $export['options']['xml_template_type'] == XmlExportEngine::EXPORT_TYPE_GOOLE_MERCHANTS) {
201
+
202
+ // If we are doing a google merchants export, send the file as a download.
203
+ header("Content-type: text/plain");
204
+ header("Content-Disposition: attachment; filename=".basename($filepath));
205
+ readfile($filepath);
206
+
207
+ die;
208
+ }
209
+
210
+ if(apply_filters('wp_all_export_no_cache', false)) {
211
+
212
+ // If we are doing a google merchants export, send the file as a download.
213
+ header("Content-type: " . mime_content_type($filepath));
214
+ header("Content-Disposition: attachment; filename=" . basename($filepath));
215
+ header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
216
+ header("Cache-Control: post-check=0, pre-check=0", false);
217
+ header("Pragma: no-cache");
218
+
219
+ readfile($filepath);
220
+
221
+ die;
222
+ }
223
+
224
+ $fileurl = str_replace( "\\", "/", $fileurl );
225
+
226
+ wp_redirect($fileurl);
227
+ }
228
+ else
229
+ {
230
+ wp_send_json(array(
231
+ 'status' => 403,
232
+ 'message' => __('File doesn\'t exist', 'wp_all_export_plugin')
233
+ ));
234
+ }
235
+ }
236
+ }
237
+ else
238
+ {
239
+ wp_send_json(array(
240
+ 'status' => 403,
241
+ 'message' => __('Export hash is not valid.', 'wp_all_export_plugin')
242
+ ));
243
+ }
244
+ }
245
+
246
+ if(isset($_GET['action']) && $_GET['action'] == 'wpae_public_api') {
247
+ $router = new \Wpae\Http\Router();
248
+ $router->route($_GET['q'], false);
249
+ }
250
+ }
251
+
trunk/actions/wpmu_new_blog.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function pmxe_wpmu_new_blog($blog_id, $user_id, $domain, $path, $site_id, $meta)
4
+ {
5
+ // create/update required database tables
6
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
7
+ require PMXE_Plugin::ROOT_DIR . '/schema.php';
8
+ global $wpdb;
9
+
10
+ if (function_exists('is_multisite') && is_multisite()) {
11
+ // check if it is a network activation - if so, run the activation function for each blog id
12
+ $old_blog = $wpdb->blogid;
13
+
14
+ switch_to_blog($blog_id);
15
+ require PMXE_Plugin::ROOT_DIR . '/schema.php';
16
+ dbDelta($plugin_queries);
17
+
18
+ switch_to_blog($old_blog);
19
+ return;
20
+ }
21
+ }
trunk/banner-772x250.png ADDED
Binary file
trunk/classes/CdataStrategy.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+
4
+ interface CdataStrategy
5
+ {
6
+ public function should_cdata_be_applied($field, $hasSnippets = false);
7
+ }
trunk/classes/CdataStrategyAlways.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once('CdataStrategy.php');
4
+
5
+ class CdataStrategyAlways implements CdataStrategy
6
+ {
7
+ public function should_cdata_be_applied($field, $hasSnippets = false)
8
+ {
9
+ return true;
10
+ }
11
+
12
+ }
trunk/classes/CdataStrategyFactory.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once(__DIR__.'/CdataStrategyAlways.php');
4
+ require_once(__DIR__.'/CdataStrategyIllegalCharacters.php');
5
+ require_once(__DIR__.'/CdataStrategyNever.php');
6
+
7
+
8
+ class CdataStrategyFactory
9
+ {
10
+ public function create_strategy($strategy) {
11
+
12
+ if($strategy == 'all') {
13
+ return new CdataStrategyAlways();
14
+ } else if($strategy == 'never') {
15
+ return new CdataStrategyNever();
16
+ } else if($strategy == 'auto') {
17
+ return new CdataStrategyIllegalCharacters();
18
+ } else {
19
+ return new CdataStrategyIllegalCharacters();
20
+ }
21
+ }
22
+ }
trunk/classes/CdataStrategyIllegalCharacters.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once('CdataStrategy.php');
4
+
5
+ class CdataStrategyIllegalCharacters implements CdataStrategy
6
+ {
7
+ private $illegalCharacters = array('<','>','&', '\'', '"','**LT**', '**GT**');
8
+
9
+ public function should_cdata_be_applied($field, $hasSnippets = false)
10
+ {
11
+ if($hasSnippets) {
12
+ $this->illegalCharacters = array('<','>','&', '**LT**', '**GT**');
13
+ }
14
+
15
+ foreach($this->illegalCharacters as $character) {
16
+ if(strpos($field, $character) !== false) {
17
+ return true;
18
+ }
19
+ }
20
+
21
+ return false;
22
+ }
23
+
24
+ }
trunk/classes/CdataStrategyIllegalCharactersHtmlEntities.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once('CdataStrategy.php');
4
+
5
+ class CdataStrategyIllegalCharactersHtmlEntities implements CdataStrategy
6
+ {
7
+ public function should_cdata_be_applied($field, $hasSnippets = false)
8
+ {
9
+ return strlen($field) != strlen(htmlentities($field));
10
+ }
11
+
12
+ }
trunk/classes/CdataStrategyNever.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once('CdataStrategy.php');
4
+
5
+ class CdataStrategyNever implements CdataStrategy
6
+ {
7
+ public function should_cdata_be_applied($field, $hasSnippets = false)
8
+ {
9
+ return false;
10
+ }
11
+
12
+ }
trunk/classes/XMLWriter.php ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // Handle eval errors that cause the script to finish
4
+ $wpaeErrorHandler = new WpaePhpInterpreterErrorHandler();
5
+ register_shutdown_function(array($wpaeErrorHandler, 'handle'));
6
+
7
+ /**
8
+ * Class PMXE_XMLWriter
9
+ */
10
+ class PMXE_XMLWriter extends XMLWriter
11
+ {
12
+ /**
13
+ * @var array
14
+ */
15
+ public $articles = array();
16
+
17
+
18
+ /**
19
+ * @param array $articles
20
+ */
21
+ public function writeArticle($articles = array())
22
+ {
23
+
24
+ $article = array_shift($articles);
25
+
26
+ if (!empty($articles)) {
27
+
28
+ $keys = array();
29
+ foreach ($articles as $a) {
30
+
31
+ foreach ($a as $key => $value) {
32
+
33
+ if (!isset($article[$key])) {
34
+ $article[$key] = array($value);
35
+ } else {
36
+ if (is_array($article[$key])){
37
+ array_push($article[$key], $value);
38
+ }
39
+ else{
40
+ $article[$key] = array($article[$key], $value);
41
+ }
42
+ }
43
+
44
+ if (!in_array($key, $keys)) $keys[] = $key;
45
+ }
46
+ }
47
+ }
48
+
49
+ if (!empty($article)) {
50
+ foreach ($article as $key => $value) {
51
+ if (!is_array($value) && strpos($value, '#delimiter#') !== FALSE) {
52
+ $article[$key] = explode('#delimiter#', $value);
53
+ }
54
+ }
55
+ }
56
+
57
+ $this->articles[] = $article;
58
+ }
59
+
60
+ /**
61
+ * @param $ns
62
+ * @param $element
63
+ * @param $uri
64
+ * @param $value
65
+ * @return bool
66
+ */
67
+ public function putElement($ns, $element, $uri, $value)
68
+ {
69
+ if (in_array(XmlExportEngine::$exportOptions['xml_template_type'], array('custom', 'XmlGoogleMerchants'))) return true;
70
+
71
+ if (empty($ns)) {
72
+ return $this->writeElement($element, $value);
73
+ } else {
74
+ return $this->writeElementNS($ns, $element, $uri, $value);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * @param $ns
80
+ * @param $element
81
+ * @param $uri
82
+ * @return bool
83
+ */
84
+ public function beginElement($ns, $element, $uri)
85
+ {
86
+ if (in_array(XmlExportEngine::$exportOptions['xml_template_type'], array('custom', 'XmlGoogleMerchants'))) return true;
87
+
88
+ if (empty($ns)) {
89
+ return $this->startElement($element);
90
+ } else {
91
+ return $this->startElementNS($ns, $element, $uri);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * @return bool
97
+ */
98
+ public function closeElement()
99
+ {
100
+
101
+ if (in_array(XmlExportEngine::$exportOptions['xml_template_type'], array('custom', 'XmlGoogleMerchants'))) return true;
102
+
103
+ return $this->endElement();
104
+ }
105
+
106
+ /**
107
+ * @param $value
108
+ * @param $element_name
109
+ *
110
+ * @return bool
111
+ */
112
+ public function writeData($value, $element_name)
113
+ {
114
+ if (in_array(XmlExportEngine::$exportOptions['xml_template_type'], array('custom', 'XmlGoogleMerchants'))) return true;
115
+
116
+ $cdataStrategyFactory = new CdataStrategyFactory();
117
+
118
+ if (!isset(XmlExportEngine::$exportOptions['custom_xml_cdata_logic'])) {
119
+ XmlExportEngine::$exportOptions['custom_xml_cdata_logic'] = 'auto';
120
+ }
121
+ $cdataStrategy = $cdataStrategyFactory->create_strategy(XmlExportEngine::$exportOptions['custom_xml_cdata_logic']);
122
+ $is_wrap_into_cdata = $cdataStrategy->should_cdata_be_applied($value);
123
+
124
+ $wrap_value_into_cdata = apply_filters('wp_all_export_is_wrap_value_into_cdata', $is_wrap_into_cdata, $value, $element_name);
125
+
126
+ if ($wrap_value_into_cdata === false) {
127
+ $this->writeRaw($value);
128
+ } else {
129
+ if (XmlExportEngine::$is_preview && XmlExportEngine::$exportOptions['show_cdata_in_preview']) {
130
+ $this->text('CDATABEGIN' . $value . 'CDATACLOSE');
131
+ } else if (XmlExportEngine::$is_preview && !XmlExportEngine::$exportOptions['show_cdata_in_preview']) {
132
+ return $this->text($value);
133
+ } else {
134
+ $this->writeCdata($value);
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * @return mixed|string
141
+ */
142
+ public function wpae_flush()
143
+ {
144
+ if (!in_array(XmlExportEngine::$exportOptions['xml_template_type'], array('custom', 'XmlGoogleMerchants'))) return $this->flush(true);
145
+
146
+ $xml = '';
147
+ foreach ($this->articles as $article) {
148
+
149
+ $founded_values = array_keys($article);
150
+ $node_tpl = XmlExportEngine::$exportOptions['custom_xml_template_loop'];
151
+
152
+ // clean up XPaths for not found values
153
+ preg_match_all("%(\{[^\}\{]*\})%", $node_tpl, $matches);
154
+ $xpaths = array_unique($matches[0]);
155
+
156
+ if (!empty($xpaths)) {
157
+ foreach ($xpaths as $xpath) {
158
+ if (!in_array(preg_replace("%[\{\}]%", "", $xpath), $founded_values)) {
159
+ $node_tpl = str_replace($xpath, "", $node_tpl);
160
+ }
161
+ }
162
+ }
163
+
164
+ foreach ($article as $key => $value) {
165
+ switch ($key) {
166
+ case 'id':
167
+ $node_tpl = str_replace('{'.$key.'}', '{' . $value . '}', $node_tpl);
168
+ break;
169
+ default:
170
+ // replace [ and ]
171
+ $v = str_replace(']', 'CLOSEBRAKET', str_replace('[', 'OPENBRAKET', $value));
172
+ // replace { and }
173
+ $v = str_replace('}', 'CLOSECURVE', str_replace('{', 'OPENCURVE', $v));
174
+ // replace ( and )
175
+ $v = str_replace(')', 'CLOSECIRCLE', str_replace('(', 'OPENCIRCLE', $v));
176
+
177
+ $originalValue = $v;
178
+
179
+ if (is_array($v)) {
180
+ foreach($v as &$val) {
181
+ $val = str_replace("\"","**DOUBLEQUOT**",$val);
182
+ $val = str_replace("'","**SINGLEQUOT**",$val);
183
+ }
184
+
185
+ $delimiter = uniqid();
186
+ $node_tpl = preg_replace('%\[(.*)\{'.$key.'\}([^\[]*)\]%', "[$1explode('" . $delimiter . "', '" . implode($delimiter, $v) . "')$2]", $node_tpl);
187
+ $v = "[explode('" . $delimiter . "', '" . implode($delimiter, $v) . "')]";
188
+ $v = str_replace('<','**LT**', $v);
189
+ $v = str_replace('>','**GT**', $v);
190
+ } else {
191
+ $v = '{' . $v . '}';
192
+ }
193
+
194
+ $arrayTypes = array(
195
+ 'Product Tags', 'Tags', 'Product Categories', 'Categories', 'Image URL', 'Image Filename', 'Image Path', 'Image ID', 'Image Title', 'Image Caption', 'Image Description', 'Image Alt Text', 'Product Type', 'Categories'
196
+ );
197
+
198
+ // We have an empty array, which is transformed into {}
199
+ if(in_array($key, $arrayTypes) && $v == "{}") {
200
+ $delimiter = uniqid();
201
+ $node_tpl = preg_replace('%\[(.*)\{'.$key.'\}([^\[]*)\]%', "[$1explode('" . $delimiter . "', '" . implode($delimiter, array()) . "')$2]", $node_tpl);
202
+ $v = "[explode('" . $delimiter . "', '" . implode($delimiter, array()) . "')]";
203
+ }
204
+
205
+ // We have an array with just one value (Which is transformed into a string)
206
+ if(in_array($key, $arrayTypes) && count($originalValue) == 1) {
207
+ $delimiter = uniqid();
208
+ $node_tpl = preg_replace('%\[(.*)\{'.$key.'\}([^\[]*)\]%', "[$1explode('" . $delimiter . "', '" . implode($delimiter, array($originalValue)) . "')$2]", $node_tpl);
209
+ $v = "[explode('" . $delimiter . "', '" . implode($delimiter, array($originalValue)) . "')]";
210
+ }
211
+
212
+ $node_tpl = str_replace('{' . $key . '}', $v, $node_tpl);
213
+
214
+ break;
215
+ }
216
+ }
217
+
218
+ $xml .= $node_tpl;
219
+
220
+ }
221
+
222
+ $this->articles = array();
223
+ $wpaeString = new WpaeString();
224
+ $xmlPrepreocesor = new WpaeXmlProcessor($wpaeString);
225
+ return $xmlPrepreocesor->process($xml);
226
+ }
227
+
228
+ public static function getIndentationCount($content, $str)
229
+ {
230
+ $lines = explode("\r", $content);
231
+ foreach ($lines as $lineNumber => $line) {
232
+ if (strpos($line, $str) !== false) {
233
+ return substr_count($line, "\t");
234
+ }
235
+ }
236
+
237
+ return -1;
238
+ }
239
+
240
+ public static function indentTag($tag, $indentationCount, $index)
241
+ {
242
+ if($index == 0) {
243
+ $indentationString = "";
244
+ } else {
245
+ $indentationString = str_repeat("\t", $indentationCount);
246
+ }
247
+
248
+ return $indentationString . $tag;
249
+ }
250
+
251
+ /**
252
+ * @param string $xml
253
+ * @return mixed|string
254
+ *
255
+ * @throws WpaeInvalidStringException
256
+ * @throws WpaeMethodNotFoundException
257
+ */
258
+ public static function preprocess_xml($xml = '')
259
+ {
260
+ $xml = str_replace('<![CDATA[', 'DOMCdataSection', $xml);
261
+
262
+ preg_match_all("%(\[[^\]\[]*\])%", $xml, $matches);
263
+ $snipets = empty($matches) ? array() : array_unique($matches[0]);
264
+ $simple_snipets = array();
265
+ preg_match_all("%(\{[^\}\{]*\})%", $xml, $matches);
266
+ $xpaths = array_unique($matches[0]);
267
+
268
+ if (!empty($xpaths)) {
269
+ foreach ($xpaths as $xpath) {
270
+ if (!in_array($xpath, $snipets)) $simple_snipets[] = $xpath;
271
+ }
272
+ }
273
+
274
+ if (!empty($snipets)) {
275
+ foreach ($snipets as $snipet) {
276
+ // if function is found
277
+ if (preg_match("%\w+\(.*\)%", $snipet)) {
278
+
279
+ $filtered = trim(trim(trim($snipet, "]"), "["));
280
+ $filtered = preg_replace("%[\{\}]%", "\"", $filtered);
281
+ $filtered = str_replace('CLOSEBRAKET', ']', str_replace('OPENBRAKET', '[', $filtered));
282
+ $filtered = str_replace('CLOSECURVE', '}', str_replace('OPENCURVE', '{', $filtered));
283
+ $filtered = str_replace('CLOSECIRCLE', ')', str_replace('OPENCIRCLE', '(', $filtered));
284
+
285
+ $functionName = self::sanitizeFunctionName($filtered);
286
+
287
+ $numberOfSingleQuotes = substr_count($filtered, "'");
288
+ $numberOfDoubleQuotes = substr_count($filtered, "\"");
289
+
290
+ if ($numberOfSingleQuotes % 2 || $numberOfDoubleQuotes % 2) {
291
+ throw new WpaeInvalidStringException($functionName);
292
+ }
293
+
294
+ if (!function_exists($functionName)) {
295
+ throw new WpaeMethodNotFoundException($functionName);
296
+ }
297
+
298
+ $values = eval("return " . $filtered . ";");
299
+
300
+ $v = '';
301
+ if (is_array($values)) {
302
+ $tag = false;
303
+
304
+ preg_match_all("%(<[\w]+[\s|>]{1})" . preg_quote($snipet) . "%", $xml, $matches);
305
+
306
+ if (!empty($matches[1])) {
307
+ $tag = array_shift($matches[1]);
308
+ }
309
+ if (empty($tag)) $tag = "<item>";
310
+
311
+ $indentationCount = self::getIndentationCount($xml, $tag);
312
+
313
+ $i = 0;
314
+ foreach ($values as $number => $value) {
315
+ $v .= self::indentTag($tag . self::maybe_cdata($value) . str_replace("<", "</", $tag) . "\n", $indentationCount, $i);
316
+ $i++;
317
+ }
318
+
319
+ $xml = str_replace($tag . $snipet . str_replace("<", "</", $tag), $v, $xml);
320
+ } else {
321
+ $xml = str_replace($snipet, self::maybe_cdata($values), $xml);
322
+ }
323
+ }
324
+ }
325
+ }
326
+
327
+ if (!empty($simple_snipets)) {
328
+ foreach ($simple_snipets as $snipet) {
329
+ $filtered = preg_replace("%[\{\}]%", "", $snipet);
330
+
331
+ $is_attribute = false;
332
+
333
+ //Encode data in attributes
334
+ if (strpos($xml, "\"$snipet\"") !== false || strpos($xml, "'$snipet'") !== false) {
335
+ $is_attribute = true;
336
+ $filtered = str_replace('&amp;', '&', $filtered);
337
+ $filtered = str_replace('&', '&amp;', $filtered);
338
+ $filtered = str_replace('\'', '&#x27;', $filtered);
339
+ $filtered = str_replace('"', '&quot;', $filtered);
340
+ $filtered = str_replace('<', '&lt;', $filtered);
341
+ $filtered = str_replace('>', '&gt;', $filtered);
342
+ }
343
+
344
+ $filtered = str_replace('CLOSEBRAKET', ']', str_replace('OPENBRAKET', '[', $filtered));
345
+ $filtered = str_replace('CLOSECURVE', '}', str_replace('OPENCURVE', '{', $filtered));
346
+ $filtered = str_replace('CLOSECIRCLE', ')', str_replace('OPENCIRCLE', '(', $filtered));
347
+
348
+ if ($is_attribute) {
349
+ $xml = str_replace($snipet, $filtered, $xml);
350
+ } else {
351
+ $xml = str_replace($snipet, self::maybe_cdata($filtered), $xml);
352
+ }
353
+ }
354
+ }
355
+
356
+ $xml = str_replace('DOMCdataSection', '<![CDATA[', $xml);
357
+
358
+ return $xml;
359
+ }
360
+
361
+ /**
362
+ * @param $v
363
+ * @return string
364
+ */
365
+ public static function maybe_cdata($v)
366
+ {
367
+
368
+ if (XmlExportEngine::$is_preview) {
369
+ $v = str_replace('&amp;', '&', $v);
370
+ $v = htmlspecialchars($v);
371
+ }
372
+
373
+ if (XmlExportEngine::$is_preview && !XmlExportEngine::$exportOptions['show_cdata_in_preview']) {
374
+ return $v;
375
+ }
376
+
377
+ $cdataStrategyFactory = new CdataStrategyFactory();
378
+
379
+ if (!isset(XmlExportEngine::$exportOptions['custom_xml_cdata_logic'])) {
380
+ XmlExportEngine::$exportOptions['custom_xml_cdata_logic'] = 'auto';
381
+ }
382
+
383
+ $cdataStrategy = $cdataStrategyFactory->create_strategy(XmlExportEngine::$exportOptions['custom_xml_cdata_logic']);
384
+ $is_wrap_into_cdata = $cdataStrategy->should_cdata_be_applied($v);
385
+
386
+ if ($is_wrap_into_cdata === false) {
387
+ return $v;
388
+ } else {
389
+ if (XmlExportEngine::$is_preview && XmlExportEngine::$exportOptions['show_cdata_in_preview']) {
390
+ return 'CDATABEGIN' . $v . 'CDATACLOSE';
391
+ } else {
392
+ return "<![CDATA[" . $v . "]]>";
393
+ }
394
+ }
395
+ }
396
+
397
+ /**
398
+ * @param $filtered
399
+ * @return mixed
400
+ */
401
+ private static function sanitizeFunctionName($filtered)
402
+ {
403
+ $functionName = preg_replace('/"[^"]+"/', '', $filtered);
404
+ $functionName = preg_replace('/\'[^\']+\'/', '', $functionName);
405
+
406
+ $firstSingleQuote = strpos($functionName, '\'');
407
+ $firstDoubleQuote = strpos($functionName, '"');
408
+
409
+ if ($firstDoubleQuote < $firstSingleQuote && $firstDoubleQuote != 0) {
410
+ $functionName = explode('"', $functionName);
411
+ $functionName = $functionName[0];
412
+ } else if ($firstSingleQuote != 0) {
413
+ $functionName = explode('\'', $functionName);
414
+ $functionName = $functionName[0];
415
+ }
416
+ $functionName = str_replace(array('(', ')', ',', ' ', '\'', '"'), '', $functionName);
417
+
418
+ return $functionName;
419
+ }
420
+ }
trunk/classes/chunk.php ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Chunk
4
+ *
5
+ * Reads a large file in as chunks for easier parsing.
6
+ *
7
+ *
8
+ * @package default
9
+ * @author Max Tsiplyakov
10
+ */
11
+ class PMXE_Chunk {
12
+ /**
13
+ * options
14
+ *
15
+ * @var array Contains all major options
16
+ * @access public
17
+ */
18
+ public $options = array(
19
+ 'path' => './', // string The path to check for $file in
20
+ 'element' => '', // string The XML element to return
21
+ 'type' => 'upload',
22
+ 'encoding' => 'UTF-8',
23
+ 'pointer' => 1,
24
+ 'chunkSize' => 1024,
25
+ 'filter' => true,
26
+ 'get_cloud' => false
27
+ );
28
+
29
+ /**
30
+ * file
31
+ *
32
+ * @var string The filename being read
33
+ * @access public
34
+ */
35
+ public $file = '';
36
+ /**
37
+ * pointer
38
+ *
39
+ * @var integer The current position the file is being read from
40
+ * @access public
41
+ */
42
+ public $reader;
43
+ public $cloud = array();
44
+ public $loop = 1;
45
+
46
+ /**
47
+ * handle
48
+ *
49
+ * @var resource The fopen() resource
50
+ * @access private
51
+ */
52
+ private $handle = null;
53
+ /**
54
+ * reading
55
+ *
56
+ * @var boolean Whether the script is currently reading the file
57
+ * @access private
58
+ */
59
+
60
+ /**
61
+ * __construct
62
+ *
63
+ * Builds the Chunk object
64
+ *
65
+ * @param string $file The filename to work with
66
+ * @param array $options The options with which to parse the file
67
+ * @author Dom Hastings
68
+ * @access public
69
+ */
70
+ public function __construct($file, $options = array()) {
71
+
72
+ // merge the options together
73
+ $this->options = array_merge($this->options, (is_array($options) ? $options : array()));
74
+
75
+ $this->options['chunkSize'] *= 32;
76
+
77
+ // set the filename
78
+ $this->file = $file;
79
+
80
+ $is_html = false;
81
+ $f = @fopen($file, "rb");
82
+ while (!@feof($f)) {
83
+ $chunk = @fread($f, 1024);
84
+ if (strpos($chunk, "<!DOCTYPE") === 0) $is_html = true;
85
+ break;
86
+ }
87
+ @fclose($f);
88
+
89
+ if ($is_html) return;
90
+
91
+ if (empty($this->options['element']) or $this->options['get_cloud'])
92
+ {
93
+ if (function_exists('stream_filter_register') and $this->options['filter']){
94
+ stream_filter_register('preprocessxml', 'wpae_preprocessXml_filter');
95
+ $path = 'php://filter/read=preprocessxml/resource=' . $this->file;
96
+ }
97
+ else $path = $this->file;
98
+
99
+ $reader = new XMLReader();
100
+ $reader->open($path);
101
+ $reader->setParserProperty(XMLReader::VALIDATE, false);
102
+ while ( @$reader->read()) {
103
+ switch ($reader->nodeType) {
104
+ case (XMLREADER::ELEMENT):
105
+ $localName = str_replace("_colon_", ":", $reader->localName);
106
+ if (array_key_exists(str_replace(":", "_", $localName), $this->cloud))
107
+ $this->cloud[str_replace(":", "_", $localName)]++;
108
+ else
109
+ $this->cloud[str_replace(":", "_", $localName)] = 1;
110
+ break;
111
+ default:
112
+
113
+ break;
114
+ }
115
+ }
116
+ unset($reader);
117
+
118
+ if ( ! empty($this->cloud) and empty($this->options['element']) ){
119
+
120
+ arsort($this->cloud);
121
+
122
+ $main_elements = array('node', 'product', 'job', 'deal', 'entry', 'item', 'property', 'listing', 'hotel', 'record', 'article', 'post', 'book');
123
+
124
+ foreach ($this->cloud as $element_name => $value) {
125
+ if ( in_array(strtolower($element_name), $main_elements) ){
126
+ $this->options['element'] = $element_name;
127
+ break;
128
+ }
129
+ }
130
+
131
+ if (empty($this->options['element'])){
132
+ foreach ($this->cloud as $el => $count) {
133
+ $this->options['element'] = $el;
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ if (function_exists('stream_filter_register') and $this->options['filter']){
141
+ stream_filter_register('preprocessxml', 'wpae_preprocessXml_filter');
142
+ $path = 'php://filter/read=preprocessxml/resource=' . $this->file;
143
+ }
144
+ else $path = $this->file;
145
+
146
+ $this->reader = new XMLReader();
147
+ @$this->reader->open($path);
148
+ @$this->reader->setParserProperty(XMLReader::VALIDATE, false);
149
+
150
+
151
+ }
152
+
153
+ /**
154
+ * __destruct
155
+ *
156
+ * Cleans up
157
+ *
158
+ * @return void
159
+ * @author Dom Hastings
160
+ * @access public
161
+ */
162
+ public function __destruct() {
163
+ // close the file resource
164
+ unset($this->reader);
165
+ }
166
+
167
+ /**
168
+ * read
169
+ *
170
+ * Reads the first available occurence of the XML element $this->options['element']
171
+ *
172
+ * @return string The XML string from $this->file
173
+ * @author Dom Hastings
174
+ * @access public
175
+ */
176
+ public function read($debug = false) {
177
+
178
+ // trim it
179
+ $element = trim($this->options['element']);
180
+
181
+ $xml = '';
182
+
183
+ try {
184
+ while ( @$this->reader->read() ) {
185
+ switch ($this->reader->nodeType) {
186
+ case (XMLREADER::ELEMENT):
187
+ $localName = str_replace("_colon_", ":", $this->reader->localName);
188
+
189
+ if ( strtolower(str_replace(":", "_", $localName)) == strtolower($element) ) {
190
+
191
+ if ($this->loop < $this->options['pointer']){
192
+ $this->loop++;
193
+ continue;
194
+ }
195
+
196
+ $xml = @$this->reader->readOuterXML();
197
+
198
+ break(2);
199
+ }
200
+ break;
201
+ default:
202
+ // code ...
203
+ break;
204
+ }
205
+ }
206
+ } catch (XmlImportException $e) {
207
+ $xml = false;
208
+ }
209
+
210
+ return ( ! empty($xml) ) ? $this->removeColonsFromRSS(preg_replace('%xmlns.*=\s*([\'"&quot;]).*\1%sU', '', $xml)) : false;
211
+
212
+ }
213
+
214
+ function removeColonsFromRSS($feed) {
215
+
216
+ $feed = str_replace("_colon_", ":", $feed);
217
+
218
+ // pull out colons from start tags
219
+ // (<\w+):(\w+>)
220
+ $pattern = '/(<\w+):([\w+|\.|-]+[ |>]{1})/i';
221
+ $replacement = '$1_$2';
222
+ $feed = preg_replace($pattern, $replacement, $feed);
223
+ // pull out colons from end tags
224
+ // (<\/\w+):(\w+>)
225
+ $pattern = '/(<\/\w+):([\w+|\.|-]+>)/i';
226
+ $replacement = '$1_$2';
227
+ $feed = preg_replace($pattern, $replacement, $feed);
228
+ // pull out colons from attributes
229
+ $pattern = '/(\s+\w+):(\w+[=]{1})/i';
230
+ $replacement = '$1_$2';
231
+ $feed = preg_replace($pattern, $replacement, $feed);
232
+ // pull colons from single element
233
+ // (<\w+):(\w+\/>)
234
+ $pattern = '/(<\w+):([\w+|\.|-]+\/>)/i';
235
+ $replacement = '$1_$2';
236
+ $feed = preg_replace($pattern, $replacement, $feed);
237
+
238
+ $is_preprocess_enabled = apply_filters('is_xml_preprocess_enabled', true);
239
+ if ($is_preprocess_enabled)
240
+ {
241
+ // replace temporary word _ampersand_ back to & symbol
242
+ $feed = str_replace("_ampersand_", "&", $feed);
243
+ }
244
+
245
+ return $feed;
246
+
247
+ }
248
+
249
+ }
250
+
251
+ class wpae_preprocessXml_filter extends php_user_filter {
252
+
253
+ function filter($in, $out, &$consumed, $closing)
254
+ {
255
+ while ($bucket = stream_bucket_make_writeable($in)) {
256
+ $is_preprocess_enabled = apply_filters('is_xml_preprocess_enabled', true);
257
+ if ($is_preprocess_enabled)
258
+ {
259
+ // the & symbol is not valid in XML, so replace it with temporary word _ampersand_
260
+ $bucket->data = str_replace("&", "_ampersand_", $bucket->data);
261
+ $bucket->data = preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $this->replace_colons($bucket->data));
262
+ }
263
+ $consumed += $bucket->datalen;
264
+ stream_bucket_append($out, $bucket);
265
+ }
266
+ return PSFS_PASS_ON;
267
+ }
268
+
269
+ function replace_colons($data)
270
+ {
271
+ return str_replace(":", "_colon_", $data);
272
+ }
273
+
274
+ }
trunk/classes/config.php ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class to load config files
5
+ *
6
+ * @author Pavel Kulbakin <p.kulbakin@gmail.com>
7
+ */
8
+ class PMXE_Config implements IteratorAggregate {
9
+
10
+ /**
11
+ * Config variables stored
12
+ * @var array
13
+ */
14
+ protected $config = array();
15
+
16
+ /**
17
+ * List of loaded files in order to avoid loading same file several times
18
+ * @var array
19
+ */
20
+ protected $loaded = array();
21
+
22
+ /**
23
+ * Static method to create config instance from file on disc
24
+ * @param string $filePath
25
+ * @param string[optional] $section
26
+ * @return PMXE_Config
27
+ */
28
+ public static function createFromFile($filePath, $section = NULL) {
29
+ $config = new self();
30
+ return $config->loadFromFile($filePath, $section);
31
+ }
32
+
33
+ /**
34
+ * Load config file
35
+ * @param string $filePath
36
+ * @param string [optional] $section
37
+ * @return PMXE_Config
38
+ */
39
+ public function loadFromFile($filePath, $section = NULL)
40
+ {
41
+ if (!is_null($section)) {
42
+ $this->config[$section] = self::createFromFile($filePath);
43
+ } else {
44
+ $filePath = realpath($filePath);
45
+ if ($filePath and !in_array($filePath, $this->loaded)) {
46
+
47
+ require $filePath;
48
+
49
+ if (!isset($config)) {
50
+ $config = array();
51
+ }
52
+ }
53
+ $this->loaded[] = $filePath;
54
+ $this->config = array_merge($this->config, $config);
55
+ }
56
+ return $this;
57
+ }
58
+
59
+ /**
60
+ * Return value of setting with specified name
61
+ * @param string $field Setting name
62
+ * @param string[optional] $section Section name to look setting in
63
+ * @return mixed
64
+ */
65
+ public function get($field, $section = NULL) {
66
+ return ! is_null($section) ? $this->config[$section]->get($field) : $this->config[$field];
67
+ }
68
+
69
+ /**
70
+ * Magic method for checking whether some config option are set
71
+ * @param string $field
72
+ * @return bool
73
+ */
74
+ public function __isset($field) {
75
+ return isset($this->config[$field]);
76
+ }
77
+
78
+ /**
79
+ * Magic method to implement object-like access to config parameters
80
+ * @param string $field
81
+ * @return mixed
82
+ */
83
+ public function __get($field) {
84
+ return $this->config[$field];
85
+ }
86
+
87
+ /**
88
+ * Return all config options as array
89
+ * @return array
90
+ */
91
+ public function toArray($section = NULL) {
92
+ return ! is_null($section) ? $this->config[$section]->toArray() : $this->config;
93
+ }
94
+
95
+ public function getIterator() {
96
+ return new ArrayIterator($this->config);
97
+ }
98
+
99
+ }
trunk/classes/download.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class PMXE_Download
4
+ {
5
+
6
+ static public function zip($file_name)
7
+ {
8
+ $uploads = wp_upload_dir();
9
+ $bundle_url = $uploads['baseurl'] . str_replace($uploads['basedir'], '', $file_name);
10
+ $bundle_url = str_replace( "\\", "/", $bundle_url );
11
+ wp_redirect($bundle_url);
12
+ die;
13
+ }
14
+
15
+ static public function xls($file_name)
16
+ {
17
+ self::sendFile("Content-Type: application/vnd.ms-excel; charset=UTF-8", $file_name);
18
+ }
19
+
20
+ static public function xlsx($file_name)
21
+ {
22
+ self::sendFile("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8", $file_name);
23
+ }
24
+
25
+ static public function csv($file_name)
26
+ {
27
+ self::sendFile("Content-Type: text/plain; charset=UTF-8", $file_name);
28
+ }
29
+
30
+ static public function txt($file_name)
31
+ {
32
+ self::sendFile("Content-Type: text/plain; charset=UTF-8", $file_name);
33
+ }
34
+
35
+ static public function xml($file_name)
36
+ {
37
+ self::sendFile("Content-Type: application/xhtml+xml; charset=UTF-8", $file_name);
38
+ }
39
+
40
+ static public function sendFile($header, $file_name)
41
+ {
42
+ // If we ar testing don't sent it as attachment
43
+ if (php_sapi_name() != 'cli-server') {
44
+ header($header);
45
+ header("Content-Disposition: attachment; filename=\"" . basename($file_name) . "\"");
46
+ header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
47
+ header("Cache-Control: post-check=0, pre-check=0", false);
48
+ header("Pragma: no-cache");
49
+ }
50
+ while (ob_get_level()) {
51
+ ob_end_clean();
52
+ }
53
+
54
+ readfile($file_name);
55
+ die;
56
+ }
57
+
58
+ }
trunk/classes/handler.php ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class PMXE_Handler extends PMXE_Session
4
+ {
5
+ /** cookie name */
6
+ private $_cookie;
7
+
8
+ /** session due to expire timestamp */
9
+ private $_session_expiring;
10
+
11
+ /** session expiration timestamp */
12
+ private $_session_expiration;
13
+
14
+ /** Bool based on whether a cookie exists **/
15
+ private $_has_cookie = false;
16
+
17
+ /**
18
+ * Constructor for the session class.
19
+ *
20
+ * @access public
21
+ * @return void
22
+ */
23
+ public function __construct()
24
+ {
25
+
26
+ $this->set_session_expiration();
27
+
28
+ $this->_import_id = $this->generate_import_id();
29
+
30
+ $this->_data = $this->get_session_data();
31
+
32
+ }
33
+
34
+ /**
35
+ * Return true if the current user has an active session, i.e. a cookie to retrieve values
36
+ * @return boolean
37
+ */
38
+ public function has_session()
39
+ {
40
+ return isset( $_COOKIE[ $this->_cookie ] ) || $this->_has_cookie || is_user_logged_in();
41
+ }
42
+
43
+ /**
44
+ * set_session_expiration function.
45
+ *
46
+ * @access public
47
+ * @return void
48
+ */
49
+ public function set_session_expiration()
50
+ {
51
+ $this->_session_expiring = time() + intval( apply_filters( 'wpallexport_session_expiring', 60 * 60 * 47 ) ); // 47 Hours
52
+ $this->_session_expiration = time() + intval( apply_filters( 'wpallexport_session_expiration', 60 * 60 * 48 ) ); // 48 Hours
53
+ }
54
+
55
+ public function generate_import_id()
56
+ {
57
+ $input = new PMXE_Input();
58
+ $import_id = $input->get('id', 'new');
59
+
60
+ return $import_id;
61
+ }
62
+
63
+ /**
64
+ * get_session_data function.
65
+ *
66
+ * @access public
67
+ * @return array
68
+ */
69
+ public function get_session_data()
70
+ {
71
+ global $wpdb;
72
+
73
+ $session = $wpdb->get_row( $wpdb->prepare("SELECT option_name, option_value FROM $wpdb->options WHERE option_name = %s", '_wpallexport_session_' . $this->_import_id . '_'), ARRAY_A );
74
+
75
+ return empty($session) ? array() : maybe_unserialize($session['option_value']);
76
+ }
77
+
78
+ /**
79
+ * get_session_data function.
80
+ *
81
+ * @access public
82
+ * @return array
83
+ */
84
+ public function get_clear_session_data()
85
+ {
86
+ $this->_data = $this->get_session_data();
87
+ $clear_data = array();
88
+ foreach ($this->_data as $key => $value) {
89
+ $ckey = sanitize_key( $key );
90
+ $clear_data[ $ckey ] = maybe_unserialize( $value );
91
+ }
92
+
93
+ return $clear_data;
94
+ }
95
+
96
+ /**
97
+ * save_data function.
98
+ *
99
+ * @access public
100
+ * @return void
101
+ */
102
+ public function save_data()
103
+ {
104
+ // Dirty if something changed - prevents saving nothing new
105
+ if ( $this->_dirty && $this->has_session() )
106
+ {
107
+ $session_option = '_wpallexport_session_' . $this->_import_id . '_';
108
+ $session_expiry_option = '_wpallexport_session_expires_' . $this->_import_id . '_';
109
+
110
+ global $wpdb;
111
+
112
+ $session = $wpdb->get_row( $wpdb->prepare("SELECT option_name, option_value FROM $wpdb->options WHERE option_name = %s", $session_option), ARRAY_A );
113
+
114
+ if ( empty($session) )
115
+ {
116
+ $wpdb->query($wpdb->prepare("INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, 'no')", $session_option, serialize($this->_data)));
117
+ $wpdb->query($wpdb->prepare("INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, 'no')", $session_expiry_option, $this->_session_expiration));
118
+ // add_option( $session_option, $this->_data, '', 'no' );
119
+ // add_option( $session_expiry_option, $this->_session_expiration, '', 'no' );
120
+ } else {
121
+ // update_option( $session_option, $this->_data );
122
+ $wpdb->query($wpdb->prepare("UPDATE `$wpdb->options` SET `option_value` = %s WHERE `option_name` = %s", serialize($this->_data), $session_option));
123
+ }
124
+ }
125
+ }
126
+
127
+ public function clean_session( $import_id = 'new' )
128
+ {
129
+ global $wpdb;
130
+
131
+ $wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->options WHERE option_name = %s", '_wpallexport_session_' . $import_id . '_') );
132
+ $wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->options WHERE option_name = %s", '_wpallexport_session_expires_' . $import_id . '_') );
133
+ }
134
+ }
trunk/classes/helper.php ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Helper class which defnes a namespace for some commonly used functions
4
+ *
5
+ * @author Pavel Kulbakin <p.kulbakin@gmail.com>
6
+ */
7
+ class PMXE_Helper {
8
+ const GLOB_MARK = 1;
9
+ const GLOB_NOSORT = 2;
10
+ const GLOB_ONLYDIR = 4;
11
+
12
+ const GLOB_NODIR = 256;
13
+ const GLOB_PATH = 512;
14
+ const GLOB_NODOTS = 1024;
15
+ const GLOB_RECURSE = 2048;
16
+
17
+ /**
18
+ * A safe empowered glob().
19
+ *
20
+ * Function glob() is prohibited on some server (probably in safe mode)
21
+ * (Message "Warning: glob() has been disabled for security reasons in
22
+ * (script) on line (line)") for security reasons as stated on:
23
+ * http://seclists.org/fulldisclosure/2005/Sep/0001.html
24
+ *
25
+ * safe_glob() intends to replace glob() using readdir() & fnmatch() instead.
26
+ * Supported flags: self::GLOB_MARK, self::GLOB_NOSORT, self::GLOB_ONLYDIR
27
+ * Additional flags: self::GLOB_NODIR, self::GLOB_PATH, self::GLOB_NODOTS, self::GLOB_RECURSE
28
+ * (not original glob() flags)
29
+ * @author BigueNique AT yahoo DOT ca
30
+ * @updates
31
+ * - 080324 Added support for additional flags: self::GLOB_NODIR, self::GLOB_PATH,
32
+ * self::GLOB_NODOTS, self::GLOB_RECURSE
33
+ * - 100607 Recurse is_dir check fixed by Pavel Kulbakin <p.kulbakin@gmail.com>
34
+ */
35
+ public static function safe_glob($pattern, $flags=0) {
36
+ $split = explode('/', str_replace('\\', '/', $pattern));
37
+ $mask = array_pop($split);
38
+ $path = implode('/', $split);
39
+
40
+ if (($dir = @opendir($path . '/')) !== false or ($dir = @opendir($path)) !== false) {
41
+ $glob = array();
42
+ while(($file = readdir($dir)) !== false) {
43
+ // Recurse subdirectories (self::GLOB_RECURSE)
44
+ if (($flags & self::GLOB_RECURSE) && is_dir($path . '/' . $file) && ( ! in_array($file, array('.', '..')))) {
45
+ $glob = array_merge($glob, self::array_prepend(self::safe_glob($path . '/' . $file . '/' . $mask, $flags), ($flags & self::GLOB_PATH ? '' : $file . '/')));
46
+ }
47
+ // Match file mask
48
+ if (self::fnmatch($mask, $file, FNM_CASEFOLD)) {
49
+ if ((( ! ($flags & self::GLOB_ONLYDIR)) || is_dir("$path/$file"))
50
+ && (( ! ($flags & self::GLOB_NODIR)) || ( ! is_dir($path . '/' . $file)))
51
+ && (( ! ($flags & self::GLOB_NODOTS)) || ( ! in_array($file, array('.', '..'))))
52
+ ) {
53
+ $glob[] = ($flags & self::GLOB_PATH ? $path . '/' : '') . $file . ($flags & self::GLOB_MARK ? '/' : '');
54
+ }
55
+ }
56
+ }
57
+ closedir($dir);
58
+ if ( ! ($flags & self::GLOB_NOSORT)) sort($glob);
59
+ return $glob;
60
+ } else {
61
+ return (strpos($pattern, "*") === false) ? array($pattern) : false;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Prepends $string to each element of $array
67
+ * If $deep is true, will indeed also apply to sub-arrays
68
+ * @author BigueNique AT yahoo DOT ca
69
+ * @since 080324
70
+ */
71
+ public static function array_prepend($array, $string, $deep=false) {
72
+ if(empty($array)||empty($string)) {
73
+ return $array;
74
+ }
75
+ foreach ($array as $key => $element) {
76
+ if (is_array($element)) {
77
+ if ($deep) {
78
+ $array[$key] = self::array_prepend($element,$string,$deep);
79
+ } else {
80
+ trigger_error(__METHOD__ . ': array element', E_USER_WARNING);
81
+ }
82
+ } else {
83
+ $array[$key] = $string.$element;
84
+ }
85
+ }
86
+ return $array;
87
+
88
+ }
89
+
90
+ const FNM_PATHNAME = 1;
91
+ const FNM_NOESCAPE = 2;
92
+ const FNM_PERIOD = 4;
93
+ const FNM_CASEFOLD = 16;
94
+
95
+ /**
96
+ * non-POSIX complient remplacement for the fnmatch
97
+ */
98
+ public static function fnmatch($pattern, $string, $flags = 0) {
99
+
100
+ $modifiers = null;
101
+ $transforms = array(
102
+ '\*' => '.*',
103
+ '\?' => '.',
104
+ '\[\!' => '[^',
105
+ '\[' => '[',
106
+ '\]' => ']',
107
+ '\.' => '\.',
108
+ '\\' => '\\\\',
109
+ '\-' => '-',
110
+ );
111
+
112
+ // Forward slash in string must be in pattern:
113
+ if ($flags & FNM_PATHNAME) {
114
+ $transforms['\*'] = '[^/]*';
115
+ }
116
+
117
+ // Back slash should not be escaped:
118
+ if ($flags & FNM_NOESCAPE) {
119
+ unset($transforms['\\']);
120
+ }
121
+
122
+ // Perform case insensitive match:
123
+ if ($flags & FNM_CASEFOLD) {
124
+ $modifiers .= 'i';
125
+ }
126
+
127
+ // Period at start must be the same as pattern:
128
+ if ($flags & FNM_PERIOD) {
129
+ if (strpos($string, '.') === 0 && strpos($pattern, '.') !== 0) return false;
130
+ }
131
+
132
+ $pattern = '#^'
133
+ .strtr(preg_quote($pattern, '#'), $transforms)
134
+ .'$#'
135
+ .$modifiers;
136
+
137
+ return (boolean)preg_match($pattern, $string);
138
+ }
139
+ }
trunk/classes/input.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class PMXE_Input {
3
+ protected $filters = array('stripslashes');
4
+
5
+ public function read($inputArray, $paramName, $default = NULL) {
6
+ if (is_array($paramName) and ! is_null($default)) {
7
+ throw new Exception('Either array of parameter names with default values as the only argument or param name and default value as seperate arguments are expected.');
8
+ }
9
+ if (is_array($paramName)) {
10
+ foreach ($paramName as $param => $def) {
11
+ if (isset($inputArray[$param])) {
12
+ $paramName[$param] = $this->applyFilters($inputArray[$param]);
13
+ }
14
+ }
15
+ return $paramName;
16
+ } else {
17
+ return isset($inputArray[$paramName]) ? $this->applyFilters($inputArray[$paramName]) : $default;
18
+ }
19
+ }
20
+
21
+ public function get($paramName, $default = NULL) {
22
+ $this->addFilter('htmlspecialchars');
23
+ $this->addFilter('strip_tags');
24
+ $this->addFilter('esc_sql');
25
+ $this->addFilter('esc_js');
26
+ $result = $this->read($_GET, $paramName, $default);
27
+ $this->removeFilter('htmlspecialchars');
28
+ $this->removeFilter('strip_tags');
29
+ $this->removeFilter('esc_sql');
30
+ $this->removeFilter('esc_js');
31
+ return $result;
32
+ }
33
+
34
+ public function post($paramName, $default = NULL) {
35
+ return $this->read($_POST, $paramName, $default);
36
+ }
37
+
38
+ public function cookie($paramName, $default = NULL) {
39
+ return $this->read($_COOKIE, $paramName, $default);
40
+ }
41
+
42
+ public function request($paramName, $default = NULL) {
43
+ return $this->read($_GET + $_POST + $_COOKIE, $paramName, $default);
44
+ }
45
+
46
+ public function getpost($paramName, $default = NULL) {
47
+ return $this->read($_GET + $_POST, $paramName, $default);
48
+ }
49
+
50
+ public function server($paramName, $default = NULL) {
51
+ return $this->read($_SERVER, $paramName, $default);
52
+ }
53
+
54
+ public function addFilter($callback) {
55
+ if ( ! is_callable($callback)) {
56
+ throw new Exception(get_class($this) . '::' . __METHOD__ . ' parameter must be a proper callback function reference.');
57
+ }
58
+ if ( ! in_array($callback, $this->filters)) {
59
+ $this->filters[] = $callback;
60
+ }
61
+ return $this;
62
+ }
63
+
64
+ public function removeFilter($callback) {
65
+ $this->filters = array_diff($this->filters, array($callback));
66
+ return $this;
67
+ }
68
+
69
+ protected function applyFilters($val) {
70
+ if (is_array($val)) {
71
+ foreach ($val as $k => $v) {
72
+ $val[$k] = $this->applyFilters($v);
73
+ }
74
+ } else {
75
+ foreach ($this->filters as $filter) {
76
+ $val = call_user_func($filter, $val);
77
+ }
78
+ }
79
+ return $val;
80
+ }
81
+ }
trunk/classes/installer.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class PMXE_Installer
4
+ {
5
+ const MIN_PHP_VERSION = "5.3.0";
6
+
7
+ const WRONG_PHP_VERSION_MESSAGE = "WP All Export requires PHP %1s or greater, you are using PHP %2s. Please contact your host and tell them to update your server to at least PHP %1s.";
8
+
9
+ public function checkActivationConditions()
10
+ {
11
+ if (version_compare(phpversion(), self::MIN_PHP_VERSION , "<")) {
12
+ $this->error(sprintf(
13
+ self::WRONG_PHP_VERSION_MESSAGE,
14
+ self::MIN_PHP_VERSION,
15
+ phpversion(),
16
+ self::MIN_PHP_VERSION
17
+ ));
18
+ }
19
+ }
20
+
21
+ private function error($message){
22
+
23
+ $message = __($message);
24
+ $error = <<<EOT
25
+ <style type="text/css">
26
+ body, html {
27
+ margin: 0;
28
+ padding: 0;
29
+ }
30
+ </style>
31
+ <div class="error">
32
+ <p style="padding-left:2px; font-size:13px; color: #444; font-family: 'Open Sans',sans-serif; -webkit-font-smoothing: subpixel-antialiased;">
33
+ $message
34
+ </p>
35
+ </div>
36
+ EOT;
37
+ echo $error;
38
+ die;
39
+ }
40
+ }
trunk/classes/session.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ abstract class PMXE_Session {
4
+
5
+ /** @var int $_customer_id */
6
+ protected $_import_id;
7
+
8
+ /** @var array $_data */
9
+ protected $_data = array();
10
+
11
+ /** @var bool $_dirty When something changes */
12
+ protected $_dirty = false;
13
+
14
+ /**
15
+ * __get function.
16
+ *
17
+ * @access public
18
+ * @param mixed $key
19
+ * @return mixed
20
+ */
21
+ public function __get( $key ) {
22
+ return $this->get( $key );
23
+ }
24
+
25
+ /**
26
+ * __set function.
27
+ *
28
+ * @access public
29
+ * @param mixed $key
30
+ * @param mixed $value
31
+ * @return void
32
+ */
33
+ public function __set( $key, $value ) {
34
+ $this->set( $key, $value );
35
+ }
36
+
37
+ /**
38
+ * __isset function.
39
+ *
40
+ * @access public
41
+ * @param mixed $key
42
+ * @return bool
43
+ */
44
+ public function __isset( $key ) {
45
+ return isset( $this->_data[ sanitize_title( $key ) ] );
46
+ }
47
+
48
+ /**
49
+ * __unset function.
50
+ *
51
+ * @access public
52
+ * @param mixed $key
53
+ * @return void
54
+ */
55
+ public function __unset( $key ) {
56
+
57
+ if ( isset( $this->_data[ $key ] ) ) {
58
+ unset( $this->_data[ $key ] );
59
+ $this->_dirty = true;
60
+ }
61
+
62
+ }
63
+
64
+ /**
65
+ * Get a session variable
66
+ *
67
+ * @param string $key
68
+ * @param mixed $default used if the session variable isn't set
69
+ * @return mixed value of session variable
70
+ */
71
+ public function get( $key, $default = null ) {
72
+ $key = sanitize_key( $key );
73
+ return isset( $this->_data[ $key ] ) ? maybe_unserialize( $this->_data[ $key ] ) : $default;
74
+ }
75
+
76
+ /**
77
+ * Set a session variable
78
+ *
79
+ * @param string $key
80
+ * @param mixed $value
81
+ */
82
+ public function set( $key, $value ) {
83
+ $this->_data[ sanitize_key( $key ) ] = maybe_serialize( $value );
84
+ $this->_dirty = true;
85
+ }
86
+
87
+ /**
88
+ * get_import_id function.
89
+ *
90
+ * @access public
91
+ * @return int
92
+ */
93
+ public function get_import_id() {
94
+ return $this->_import_id;
95
+ }
96
+ }
trunk/classes/wpallimport.php ADDED
@@ -0,0 +1,582 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ final class PMXE_Wpallimport
4
+ {
5
+ /**
6
+ * Singletone instance
7
+ * @var PMXE_Wpallimport
8
+ */
9
+ protected static $instance;
10
+
11
+ /**
12
+ * Return singletone instance
13
+ * @return PMXE_Wpallimport
14
+ */
15
+ static public function getInstance() {
16
+ if (self::$instance == NULL) {
17
+ self::$instance = new self();
18
+ }
19
+ return self::$instance;
20
+ }
21
+
22
+ private function __construct(){}
23
+
24
+ public static function create_an_import( & $export )
25
+ {
26
+
27
+ $custom_type = (empty($export->options['cpt'])) ? 'post' : $export->options['cpt'][0];
28
+
29
+ // Do not create an import for WooCommerce Orders & Refunds
30
+ // if ( in_array($custom_type, array('shop_order'))) return false;
31
+
32
+ if ( $export->options['is_generate_import'] and wp_all_export_is_compatible() ){
33
+
34
+ $import = new PMXI_Import_Record();
35
+
36
+ if ( ! empty($export->options['import_id']) ) $import->getById($export->options['import_id']);
37
+
38
+ if ($import->isEmpty())
39
+ {
40
+ $import->set(array(
41
+ 'parent_import_id' => 99999,
42
+ 'xpath' => '/',
43
+ 'type' => 'upload',
44
+ 'options' => array('empty'),
45
+ 'root_element' => 'root',
46
+ 'path' => 'path',
47
+ 'imported' => 0,
48
+ 'created' => 0,
49
+ 'updated' => 0,
50
+ 'skipped' => 0,
51
+ 'deleted' => 0,
52
+ 'iteration' => 1
53
+ ))->save();
54
+
55
+ if ( ! empty(PMXE_Plugin::$session) and PMXE_Plugin::$session->has_session() )
56
+ {
57
+ PMXE_Plugin::$session->set('import_id', $import->id);
58
+ }
59
+ $options = $export->options;
60
+ $options['import_id'] = $import->id;
61
+
62
+ $export->set(array(
63
+ 'options' => $options
64
+ ))->save();
65
+ }
66
+ else
67
+ {
68
+ if ( $import->parent_import_id != 99999 )
69
+ {
70
+ $newImport = new PMXI_Import_Record();
71
+
72
+ $newImport->set(array(
73
+ 'parent_import_id' => 99999,
74
+ 'xpath' => '/',
75
+ 'type' => 'upload',
76
+ 'options' => array('empty'),
77
+ 'root_element' => 'root',
78
+ 'path' => 'path',
79
+ 'imported' => 0,
80
+ 'created' => 0,
81
+ 'updated' => 0,
82
+ 'skipped' => 0,
83
+ 'deleted' => 0,
84
+ 'iteration' => 1
85
+ ))->save();
86
+
87
+ if ( ! empty(PMXE_Plugin::$session) and PMXE_Plugin::$session->has_session() )
88
+ {
89
+ PMXE_Plugin::$session->set('import_id', $newImport->id);
90
+ }
91
+
92
+ $options = $export->options;
93
+ $options['import_id'] = $newImport->id;
94
+
95
+ $export->set(array(
96
+ 'options' => $options
97
+ ))->save();
98
+ }
99
+ else
100
+ {
101
+ global $wpdb;
102
+ $post = new PMXI_Post_List();
103
+ $wpdb->query($wpdb->prepare('DELETE FROM ' . $post->getTable() . ' WHERE import_id = %s', $import->id));
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ public static $templateOptions = array();
110
+ public static function generateImportTemplate( & $export, $file_path = '', $foundPosts = 0, $link_to_import = true )
111
+ {
112
+ $exportOptions = $export->options;
113
+
114
+ $custom_type = (empty($exportOptions['cpt'])) ? 'post' : $exportOptions['cpt'][0];
115
+
116
+ // Do not create an import template for WooCommerce Refunds
117
+ if ( $export->options['export_to'] == 'xml' && in_array($export->options['xml_template_type'], array('custom', 'XmlGoogleMerchants')) ) return false;
118
+
119
+ // Generate template for WP All Import
120
+ if ($exportOptions['is_generate_templates'])
121
+ {
122
+ self::$templateOptions = array(
123
+ 'type' => ( ! empty($exportOptions['cpt']) and $exportOptions['cpt'][0] == 'page') ? 'page' : 'post',
124
+ 'wizard_type' => 'new',
125
+ 'deligate' => 'wpallexport',
126
+ 'custom_type' => (XmlExportEngine::$is_user_export) ? 'import_users' : $custom_type,
127
+ 'status' => 'xpath',
128
+ 'is_multiple_page_parent' => 'no',
129
+ 'unique_key' => '',
130
+ 'acf' => array(),
131
+ 'fields' => array(),
132
+ 'is_multiple_field_value' => array(),
133
+ 'multiple_value' => array(),
134
+ 'fields_delimiter' => array(),
135
+ 'update_all_data' => 'no',
136
+ 'is_update_status' => 0,
137
+ 'is_update_title' => 0,
138
+ 'is_update_author' => 0,
139
+ 'is_update_slug' => 0,
140
+ 'is_update_content' => 0,
141
+ 'is_update_excerpt' => 0,
142
+ 'is_update_dates' => 0,
143
+ 'is_update_menu_order' => 0,
144
+ 'is_update_parent' => 0,
145
+ 'is_update_attachments' => 0,
146
+ 'is_update_acf' => 0,
147
+ 'is_update_comment_status' => 0,
148
+ 'import_img_tags' => 1,
149
+ 'update_acf_logic' => 'only',
150
+ 'acf_list' => '',
151
+ 'is_update_product_type' => 1,
152
+ 'is_update_attributes' => 0,
153
+ 'update_attributes_logic' => 'only',
154
+ 'attributes_list' => '',
155
+ 'is_update_images' => 0,
156
+ 'is_update_custom_fields' => 0,
157
+ 'update_custom_fields_logic' => 'only',
158
+ 'custom_fields_list' => '',
159
+ 'is_update_categories' => 0,
160
+ 'update_categories_logic' => 'only',
161
+ 'taxonomies_list' => '',
162
+ 'export_id' => $export->id
163
+ );
164
+
165
+ if ( in_array('product', $exportOptions['cpt']) )
166
+ {
167
+ $default = array(
168
+ 'is_multiple_product_type' => 'yes',
169
+ 'multiple_product_type' => 'simple',
170
+ 'is_product_virtual' => 'no',
171
+ 'is_product_downloadable' => 'no',
172
+ 'is_product_enabled' => 'yes',
173
+ 'is_variation_enabled' => 'yes',
174
+ 'is_product_featured' => 'no',
175
+ 'is_product_visibility' => 'visible',
176
+ 'is_multiple_product_tax_status' => 'yes',
177
+ 'multiple_product_tax_status' => 'none',
178
+ 'is_multiple_product_tax_class' => 'yes',
179
+ 'is_product_manage_stock' => 'no',
180
+ 'product_stock_status' => 'auto',
181
+ 'product_allow_backorders' => 'no',
182
+ 'product_sold_individually' => 'no',
183
+ 'is_multiple_product_shipping_class' => 'yes',
184
+ 'is_multiple_grouping_product' => 'yes',
185
+ 'is_product_enable_reviews' => 'no',
186
+ 'single_sale_price_dates_from' => 'now',
187
+ 'single_sale_price_dates_to' => 'now',
188
+ 'product_files_delim' => ',',
189
+ 'product_files_names_delim' => ',',
190
+ 'matching_parent' => 'auto',
191
+ 'parent_indicator' => 'custom field',
192
+ 'missing_records_stock_status' => 0,
193
+ 'is_variable_sale_price_shedule' => 0,
194
+ 'is_variable_product_virtual' => 'no',
195
+ 'is_variable_product_manage_stock' => 'no',
196
+ 'is_multiple_variable_product_shipping_class' => 'yes',
197
+ 'is_multiple_variable_product_tax_class' => 'yes',
198
+ 'multiple_variable_product_tax_class' => 'parent',
199
+ 'variable_stock_status' => 'instock',
200
+ 'variable_allow_backorders' => 'no',
201
+ 'is_variable_product_downloadable' => 'no',
202
+ 'variable_product_files_delim' => ',',
203
+ 'variable_product_files_names_delim' => ',',
204
+ 'is_variable_product_enabled' => 'yes',
205
+ 'first_is_parent' => 'yes',
206
+ 'default_attributes_type' => 'first',
207
+ 'disable_sku_matching' => 1,
208
+ 'disable_prepare_price' => 1,
209
+ 'convert_decimal_separator' => 1,
210
+ 'grouping_indicator' => 'xpath',
211
+ 'is_update_product_type' => 1,
212
+ 'make_simple_product' => 1,
213
+ 'single_product_regular_price_adjust_type' => '%',
214
+ 'single_product_sale_price_adjust_type' => '%',
215
+ 'is_variation_product_manage_stock' => 'no',
216
+ 'variation_stock_status' => 'auto',
217
+ );
218
+
219
+ self::$templateOptions = array_replace_recursive(self::$templateOptions, $default);
220
+
221
+ self::$templateOptions['_virtual'] = 1;
222
+ self::$templateOptions['_downloadable'] = 1;
223
+ self::$templateOptions['put_variation_image_to_gallery'] = 1;
224
+ self::$templateOptions['disable_auto_sku_generation'] = 1;
225
+ }
226
+
227
+ if ( in_array('shop_order', $exportOptions['cpt']) )
228
+ {
229
+ self::$templateOptions['update_all_data'] = 'no';
230
+ self::$templateOptions['is_update_status'] = 0;
231
+ self::$templateOptions['is_update_dates'] = 0;
232
+ self::$templateOptions['is_update_excerpt'] = 0;
233
+
234
+ // $default = PMWI_Plugin::get_default_import_options();
235
+ // self::$templateOptions['pmwi_order'] = $default['pmwi_order'];
236
+ self::$templateOptions['pmwi_order'] = array();
237
+ self::$templateOptions['pmwi_order']['is_update_billing_details'] = 0;
238
+ self::$templateOptions['pmwi_order']['is_update_shipping_details'] = 0;
239
+ self::$templateOptions['pmwi_order']['is_update_payment'] = 0;
240
+ self::$templateOptions['pmwi_order']['is_update_notes'] = 0;
241
+ self::$templateOptions['pmwi_order']['is_update_products'] = 0;
242
+ self::$templateOptions['pmwi_order']['is_update_fees'] = 0;
243
+ self::$templateOptions['pmwi_order']['is_update_coupons'] = 0;
244
+ self::$templateOptions['pmwi_order']['is_update_shipping'] = 0;
245
+ self::$templateOptions['pmwi_order']['is_update_taxes'] = 0;
246
+ self::$templateOptions['pmwi_order']['is_update_refunds'] = 0;
247
+ self::$templateOptions['pmwi_order']['is_update_total'] = 0;
248
+ self::$templateOptions['pmwi_order']['is_guest_matching'] = 1;
249
+ self::$templateOptions['pmwi_order']['status'] = 'wc-pending';
250
+ self::$templateOptions['pmwi_order']['billing_source'] = 'existing';
251
+ self::$templateOptions['pmwi_order']['billing_source_match_by'] = 'username';
252
+ self::$templateOptions['pmwi_order']['shipping_source'] = 'guest';
253
+ self::$templateOptions['pmwi_order']['copy_from_billing'] = 1;
254
+ self::$templateOptions['pmwi_order']['products_repeater_mode'] = 'csv';
255
+ self::$templateOptions['pmwi_order']['products_repeater_mode_separator'] = '|';
256
+ self::$templateOptions['pmwi_order']['products_source'] = 'existing';
257
+ self::$templateOptions['pmwi_order']['fees_repeater_mode'] = 'csv';
258
+ self::$templateOptions['pmwi_order']['fees_repeater_mode_separator'] = '|';
259
+ self::$templateOptions['pmwi_order']['coupons_repeater_mode'] = 'csv';
260
+ self::$templateOptions['pmwi_order']['coupons_repeater_mode_separator'] = '|';
261
+ self::$templateOptions['pmwi_order']['shipping_repeater_mode'] = 'csv';
262
+ self::$templateOptions['pmwi_order']['shipping_repeater_mode_separator'] = '|';
263
+ self::$templateOptions['pmwi_order']['taxes_repeater_mode'] = 'csv';
264
+ self::$templateOptions['pmwi_order']['taxes_repeater_mode_separator'] = '|';
265
+ self::$templateOptions['pmwi_order']['order_total_logic'] = 'auto';
266
+ self::$templateOptions['pmwi_order']['order_refund_date'] = 'now';
267
+ self::$templateOptions['pmwi_order']['order_refund_issued_source'] = 'existing';
268
+ self::$templateOptions['pmwi_order']['order_refund_issued_match_by'] = 'username';
269
+ self::$templateOptions['pmwi_order']['notes_repeater_mode'] = 'csv';
270
+ self::$templateOptions['pmwi_order']['notes_repeater_mode_separator'] = '|';
271
+ }
272
+
273
+ if ( XmlExportEngine::$is_user_export )
274
+ {
275
+ self::$templateOptions['is_update_first_name'] = 0;
276
+ self::$templateOptions['is_update_last_name'] = 0;
277
+ self::$templateOptions['is_update_role'] = 0;
278
+ self::$templateOptions['is_update_nickname'] = 0;
279
+ self::$templateOptions['is_update_description'] = 0;
280
+ self::$templateOptions['is_update_login'] = 0;
281
+ self::$templateOptions['is_update_password'] = 0;
282
+ self::$templateOptions['is_update_nicename'] = 0;
283
+ self::$templateOptions['is_update_email'] = 0;
284
+ self::$templateOptions['is_update_registered'] = 0;
285
+ self::$templateOptions['is_update_display_name'] = 0;
286
+ self::$templateOptions['is_update_url'] = 0;
287
+ }
288
+
289
+ if (XmlExportEngine::$is_taxonomy_export){
290
+ self::$templateOptions['taxonomy_type'] = $exportOptions['taxonomy_to_export'];
291
+ }
292
+
293
+ self::prepare_import_template( $exportOptions );
294
+
295
+ if ( in_array('product', $exportOptions['cpt']) )
296
+ {
297
+ self::$templateOptions['single_page_parent'] = '';
298
+ if ( ! empty($exportOptions['export_variations']) && $exportOptions['export_variations'] == XmlExportEngine::VARIABLE_PRODUCTS_EXPORT_VARIATION ){
299
+ if ( $exportOptions['export_variations_title'] == XmlExportEngine::VARIATION_USE_PARENT_TITLE ){
300
+ self::$templateOptions['matching_parent'] = 'first_is_variation';
301
+ }
302
+ if ( $exportOptions['export_variations_title'] == XmlExportEngine::VARIATION_USE_DEFAULT_TITLE ) {
303
+ self::$templateOptions['matching_parent'] = 'first_is_parent_id';
304
+ }
305
+ self::$templateOptions['create_new_records'] = 0;
306
+ self::$templateOptions['is_update_product_type'] = 0;
307
+ }
308
+ }
309
+
310
+ $tpl_options = self::$templateOptions;
311
+
312
+ if ( 'csv' == $exportOptions['export_to'] )
313
+ {
314
+ $tpl_options['delimiter'] = $exportOptions['delimiter'];
315
+ $tpl_options['root_element'] = 'node';
316
+ }
317
+ else
318
+ {
319
+ $tpl_options['root_element'] = $exportOptions['record_xml_tag'];
320
+ }
321
+
322
+ $tpl_options['update_all_data'] = 'yes';
323
+ $tpl_options['is_update_status'] = 1;
324
+ $tpl_options['is_update_title'] = 1;
325
+ $tpl_options['is_update_author'] = 1;
326
+ $tpl_options['is_update_slug'] = 1;
327
+ $tpl_options['is_update_content'] = 1;
328
+ $tpl_options['is_update_excerpt'] = 1;
329
+ $tpl_options['is_update_dates'] = 1;
330
+ $tpl_options['is_update_menu_order'] = 1;
331
+ $tpl_options['is_update_parent'] = 1;
332
+ $tpl_options['is_update_attachments'] = 1;
333
+ $tpl_options['is_update_acf'] = 1;
334
+ $tpl_options['update_acf_logic'] = 'full_update';
335
+ $tpl_options['acf_list'] = '';
336
+ $tpl_options['is_update_product_type'] = 1;
337
+ $tpl_options['is_update_attributes'] = 1;
338
+ $tpl_options['update_attributes_logic'] = 'full_update';
339
+ $tpl_options['attributes_list'] = '';
340
+ $tpl_options['is_update_images'] = 1;
341
+ $tpl_options['is_update_custom_fields'] = 1;
342
+ $tpl_options['update_custom_fields_logic'] = 'full_update';
343
+ $tpl_options['custom_fields_list'] = '';
344
+ $tpl_options['is_update_categories'] = 1;
345
+ $tpl_options['update_categories_logic'] = 'full_update';
346
+ $tpl_options['taxonomies_list'] = '';
347
+
348
+ $tpl_data = array(
349
+ 'name' => $exportOptions['template_name'],
350
+ 'is_keep_linebreaks' => 1,
351
+ 'is_leave_html' => 0,
352
+ 'fix_characters' => 0,
353
+ 'options' => $tpl_options,
354
+ );
355
+
356
+ $exportOptions['tpl_data'] = $tpl_data;
357
+
358
+ $export->set(array(
359
+ 'options' => $exportOptions
360
+ ))->save();
361
+
362
+ }
363
+
364
+ $link_to_import and $export->options['is_generate_import'] and self::link_template_to_import( $export, $file_path, $foundPosts );
365
+ }
366
+
367
+ public static function link_template_to_import( & $export, $file_path, $foundPosts )
368
+ {
369
+
370
+ $exportOptions = $export->options;
371
+
372
+ // associate exported posts with new import
373
+ if ( wp_all_export_is_compatible() )
374
+ {
375
+ $options = self::$templateOptions + PMXI_Plugin::get_default_import_options();
376
+
377
+ $import = new PMXI_Import_Record();
378
+
379
+ $import->getById($exportOptions['import_id']);
380
+
381
+ if ( ! $import->isEmpty() and $import->parent_import_id == 99999 ){
382
+
383
+ $xmlPath = $file_path;
384
+
385
+ $root_element = '';
386
+
387
+ $historyPath = $file_path;
388
+
389
+ if ( 'csv' == $exportOptions['export_to'] )
390
+ {
391
+ $is_secure_import = PMXE_Plugin::getInstance()->getOption('secure');
392
+
393
+ $options['delimiter'] = $exportOptions['delimiter'];
394
+
395
+ include_once( PMXI_Plugin::ROOT_DIR . '/libraries/XmlImportCsvParse.php' );
396
+
397
+ $path_info = pathinfo($xmlPath);
398
+
399
+ $path_parts = explode(DIRECTORY_SEPARATOR, $path_info['dirname']);
400
+
401
+ $security_folder = array_pop($path_parts);
402
+
403
+ $wp_uploads = wp_upload_dir();
404
+
405
+ $target = $is_secure_import ? $wp_uploads['basedir'] . DIRECTORY_SEPARATOR . PMXE_Plugin::UPLOADS_DIRECTORY . DIRECTORY_SEPARATOR . $security_folder : $wp_uploads['path'];
406
+
407
+ $csv = new PMXI_CsvParser( array( 'filename' => $xmlPath, 'targetDir' => $target ) );
408
+
409
+ if ( ! in_array($xmlPath, $exportOptions['attachment_list']) )
410
+ {
411
+ $exportOptions['attachment_list'][] = $csv->xml_path;
412
+ }
413
+
414
+ $historyPath = $csv->xml_path;
415
+
416
+ $root_element = 'node';
417
+
418
+ }
419
+ else
420
+ {
421
+ $root_element = apply_filters('wp_all_export_record_xml_tag', $exportOptions['record_xml_tag'], $export->id);
422
+ }
423
+
424
+ $import->set(array(
425
+ 'xpath' => '/' . $root_element,
426
+ 'type' => 'upload',
427
+ 'options' => $options,
428
+ 'root_element' => $root_element,
429
+ 'path' => $xmlPath,
430
+ 'name' => basename($xmlPath),
431
+ 'imported' => 0,
432
+ 'created' => 0,
433
+ 'updated' => 0,
434
+ 'skipped' => 0,
435
+ 'deleted' => 0,
436
+ 'iteration' => 1,
437
+ 'count' => $foundPosts
438
+ ))->save();
439
+
440
+ $history_file = new PMXI_File_Record();
441
+ $history_file->set(array(
442
+ 'name' => $import->name,
443
+ 'import_id' => $import->id,
444
+ 'path' => $historyPath,
445
+ 'registered_on' => date('Y-m-d H:i:s')
446
+ ))->save();
447
+
448
+ $exportOptions['import_id'] = $import->id;
449
+
450
+ $export->set(array(
451
+ 'options' => $exportOptions
452
+ ))->save();
453
+ }
454
+ }
455
+ }
456
+
457
+ public static function prepare_import_template( $exportOptions )
458
+ {
459
+ $options = $exportOptions;
460
+
461
+ $is_xml_template = $options['export_to'] == 'xml';
462
+
463
+ $required_add_ons = array();
464
+
465
+ $cf_list = array();
466
+ $attr_list = array();
467
+ $taxs_list = array();
468
+ $acf_list = array();
469
+
470
+ $implode_delimiter = ($options['delimiter'] == ',') ? '|' : ',';
471
+
472
+ if ( ! empty($options['is_user_export']) ) self::$templateOptions['pmui']['import_users'] = 1;
473
+
474
+ foreach ($options['ids'] as $ID => $value)
475
+ {
476
+ if (empty($options['cc_type'][$ID])) continue;
477
+
478
+ if ($is_xml_template)
479
+ {
480
+ $element_name = (!empty($options['cc_name'][$ID])) ? str_replace(':', '_', preg_replace('/[^a-z0-9_:-]/i', '', $options['cc_name'][$ID])) : 'untitled_' . $ID;
481
+ }
482
+ else
483
+ {
484
+ $element_name = strtolower((!empty($options['cc_name'][$ID])) ? preg_replace('/[^a-z0-9_]/i', '', $options['cc_name'][$ID]) : 'untitled_' . $ID);
485
+ }
486
+
487
+ if (empty($element_name)) $element_name = 'undefined' . $ID;
488
+
489
+ $element_type = $options['cc_type'][$ID];
490
+
491
+ switch ($element_type)
492
+ {
493
+ case 'woo':
494
+
495
+ if ( ! empty($options['cc_value'][$ID]) )
496
+ {
497
+ if (empty($required_add_ons['PMWI_Plugin']))
498
+ {
499
+ $required_add_ons['PMWI_Plugin'] = array(
500
+ 'name' => 'WooCommerce Add-On Pro',
501
+ 'paid' => true,
502
+ 'url' => 'http://www.wpallimport.com/woocommerce-product-import/?utm_source=export-plugin-free&utm_medium=upgrade-notice&utm_campaign=import-wooco-products-template'
503
+ );
504
+ }
505
+
506
+ XmlExportWooCommerce::prepare_import_template( $options, self::$templateOptions, $cf_list, $attr_list, $element_name, $options['cc_label'][$ID] );
507
+ }
508
+
509
+ break;
510
+
511
+ case 'acf':
512
+
513
+ if (empty($required_add_ons['PMAI_Plugin']))
514
+ {
515
+ $required_add_ons['PMAI_Plugin'] = array(
516
+ 'name' => 'ACF Add-On Pro',
517
+ 'paid' => true,
518
+ 'url' => 'http://www.wpallimport.com/advanced-custom-fields/?utm_source=export-plugin-free&utm_medium=upgrade-notice&utm_campaign=import-acf-template'
519
+ );
520
+ }
521
+
522
+ $field_options = unserialize($options['cc_options'][$ID]);
523
+
524
+ // add ACF group ID to the template options
525
+ if( ! in_array($field_options['group_id'], self::$templateOptions['acf'])){
526
+ self::$templateOptions['acf'][$field_options['group_id']] = 1;
527
+ }
528
+
529
+ self::$templateOptions['fields'][$field_options['key']] = XmlExportACF::prepare_import_template( $options, self::$templateOptions, $acf_list, $element_name, $field_options);
530
+
531
+ break;
532
+
533
+ default:
534
+
535
+ $addons = new \Wpae\App\Service\Addons\AddonService();
536
+ XmlExportCpt::prepare_import_template( $options, self::$templateOptions, $cf_list, $attr_list, $taxs_list, $element_name, $ID);
537
+
538
+ XmlExportMediaGallery::prepare_import_template( $options, self::$templateOptions, $element_name, $ID);
539
+
540
+ if($addons->isUserAddonActive()) {
541
+ XmlExportUser::prepare_import_template($options, self::$templateOptions, $element_name, $ID);
542
+ }
543
+
544
+ XmlExportTaxonomy::prepare_import_template( $options, self::$templateOptions, $element_name, $ID);
545
+
546
+ XmlExportWooCommerceOrder::prepare_import_template( $options, self::$templateOptions, $element_name, $ID);
547
+
548
+ break;
549
+ }
550
+ }
551
+
552
+ if ( ! empty($cf_list) )
553
+ {
554
+ self::$templateOptions['is_update_custom_fields'] = 1;
555
+ self::$templateOptions['custom_fields_list'] = $cf_list;
556
+ }
557
+ if ( ! empty($attr_list) )
558
+ {
559
+ self::$templateOptions['is_update_attributes'] = 1;
560
+ self::$templateOptions['update_attributes_logic'] = 'only';
561
+ self::$templateOptions['attributes_list'] = $attr_list;
562
+ self::$templateOptions['attributes_only_list'] = implode(',', $attr_list);
563
+ }
564
+ else{
565
+ self::$templateOptions['is_update_attributes'] = 0;
566
+ }
567
+ if ( ! empty($taxs_list) )
568
+ {
569
+ self::$templateOptions['is_update_categories'] = 1;
570
+ self::$templateOptions['taxonomies_list'] = $taxs_list;
571
+ }
572
+ if ( ! empty($acf_list) )
573
+ {
574
+ self::$templateOptions['is_update_acf'] = 1;
575
+ self::$templateOptions['acf_list'] = $acf_list;
576
+ }
577
+
578
+ self::$templateOptions['required_add_ons'] = $required_add_ons;
579
+ }
580
+ }
581
+
582
+ PMXE_Wpallimport::getInstance();
trunk/classes/zip.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! class_exists('PMXE_Zip')){
4
+
5
+ class PMXE_Zip
6
+ {
7
+ /**
8
+ * Add files and sub-directories in a folder to zip file.
9
+ * @param string $folder
10
+ * @param ZipArchive $zipFile
11
+ * @param int $exclusiveLength Number of text to be exclusived from the file path.
12
+ */
13
+ private static function folderToZip($folder, &$zipFile, $exclusiveLength) {
14
+ $handle = opendir($folder);
15
+ while (false !== $f = readdir($handle)) {
16
+ if ($f != '.' && $f != '..') {
17
+ $filePath = "$folder/$f";
18
+ // Remove prefix from file path before add to zip.
19
+ $localPath = substr($filePath, $exclusiveLength);
20
+ if (is_file($filePath)) {
21
+ $zipFile->addFile($filePath, $localPath);
22
+ } elseif (is_dir($filePath)) {
23
+ // Add sub-directory.
24
+ $zipFile->addEmptyDir($localPath);
25
+ self::folderToZip($filePath, $zipFile, $exclusiveLength);
26
+ }
27
+ }
28
+ }
29
+ closedir($handle);
30
+ }
31
+
32
+ /**
33
+ * Zip a folder (include itself).
34
+ * Usage:
35
+ * PMXE_Zip::zipDir('/path/to/sourceDir', '/path/to/out.zip');
36
+ *
37
+ * @param string $sourcePath Path of directory to be zip.
38
+ * @param string $outZipPath Path of output zip file.
39
+ */
40
+ public static function zipDir($sourcePath, $outZipPath)
41
+ {
42
+ $pathInfo = pathInfo($sourcePath);
43
+ $parentPath = $pathInfo['dirname'];
44
+ $dirName = $pathInfo['basename'];
45
+
46
+ $z = new ZipArchive();
47
+ $z->open($outZipPath, ZIPARCHIVE::CREATE);
48
+ $z->addEmptyDir($dirName);
49
+ self::folderToZip($sourcePath, $z, strlen("$parentPath/"));
50
+ $z->close();
51
+ }
52
+ }
53
+
54
+ }
trunk/config/options.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * List of plugin options, contains only default values, actual values are stored in database
4
+ * and can be changed by corresponding WordPress function calls
5
+ */
6
+ $config = array(
7
+ "info_api_url" => "http://www.wpallimport.com",
8
+ "dismiss" => 0,
9
+ "dismiss_manage_top" => 0,
10
+ "dismiss_manage_bottom" => 0,
11
+ "cron_job_key" => wp_all_export_url_title(wp_all_export_rand_char(12)),
12
+ "max_input_time" => -1,
13
+ "max_execution_time" => 0,
14
+ "secure" => 1,
15
+ "license" => "",
16
+ "license_status" => "",
17
+ "scheduling_license" => "",
18
+ "scheduling_license_status" => "",
19
+ "zapier_api_key" => wp_all_export_rand_char(32),
20
+ "zapier_invitation_url" => "",
21
+ "zapier_invitation_url_received" => ""
22
+ );
trunk/controllers/admin/export.php ADDED
@@ -0,0 +1,563 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Export configuration wizard
5
+ *
6
+ * @author Max Tsiplyakov <makstsiplyakov@gmail.com>
7
+ */
8
+ class PMXE_Admin_Export extends PMXE_Controller_Admin
9
+ {
10
+
11
+ protected $isWizard = true; // indicates whether controller is in wizard mode (otherwise it called to be delegated an edit action)
12
+
13
+ protected function init()
14
+ {
15
+
16
+ parent::init();
17
+
18
+ if ('PMXE_Admin_Manage' == PMXE_Plugin::getInstance()->getAdminCurrentScreen()->base) { // prereqisites are not checked when flow control is deligated
19
+ $id = $this->input->get('id');
20
+ $this->data['export'] = $export = new PMXE_Export_Record();
21
+ if (!$id or $export->getById($id)->isEmpty()) { // specified import is not found
22
+ wp_redirect(add_query_arg('page', 'pmxe-admin-manage', admin_url('admin.php')));
23
+ die();
24
+ }
25
+ $this->isWizard = false;
26
+ $export->fix_template_options();
27
+ } else {
28
+ $action = PMXE_Plugin::getInstance()->getAdminCurrentScreen()->action;
29
+ $this->_step_ready($action);
30
+ }
31
+
32
+ // preserve id parameter as part of baseUrl
33
+ $id = $this->input->get('id') and $this->baseUrl = add_query_arg('id', $id, $this->baseUrl);
34
+
35
+ }
36
+
37
+ public function set($var, $val)
38
+ {
39
+ $this->{$var} = $val;
40
+ }
41
+
42
+ public function get($var)
43
+ {
44
+ return $this->{$var};
45
+ }
46
+
47
+ /**
48
+ * Checks whether corresponding step of wizard is complete
49
+ * @param string $action
50
+ * @return bool
51
+ */
52
+ protected function _step_ready($action)
53
+ {
54
+
55
+ // step #1: xml selction - has no prerequisites
56
+ if ('index' == $action) return true;
57
+
58
+ if ('element' == $action) return true;
59
+
60
+ $this->data['update_previous'] = $update_previous = new PMXE_Export_Record();
61
+
62
+ $update_previous->getById(PMXE_Plugin::$session->update_previous);
63
+
64
+ if (!$update_previous->isEmpty()) {
65
+ $update_previous->fix_template_options();
66
+ }
67
+
68
+ if ('options' == $action) return true;
69
+
70
+ if (!PMXE_Plugin::$session->has_session()) {
71
+ wp_redirect_or_javascript($this->baseUrl);
72
+ die();
73
+ }
74
+
75
+ if ('process' == $action) return true;
76
+
77
+ }
78
+
79
+ /**
80
+ * Step #1: Choose CPT
81
+ */
82
+ public function index()
83
+ {
84
+
85
+ $action = $this->input->get('action');
86
+
87
+ $DefaultOptions = array(
88
+ 'cpt' => '',
89
+ 'export_to' => 'xml',
90
+ 'export_type' => 'specific',
91
+ 'wp_query' => '',
92
+ 'filter_rules_hierarhy' => '',
93
+ 'product_matching_mode' => 'strict',
94
+ 'wp_query_selector' => 'wp_query',
95
+ 'auto_generate' => 0,
96
+ 'taxonomy_to_export' => '',
97
+ 'created_at_version' => PMXE_VERSION
98
+ );
99
+
100
+ if (!in_array($action, array('index'))) {
101
+ PMXE_Plugin::$session->clean_session();
102
+ $this->data['preload'] = false;
103
+ } else {
104
+ $DefaultOptions = (PMXE_Plugin::$session->has_session() ? PMXE_Plugin::$session->get_clear_session_data() : array()) + $DefaultOptions;
105
+ $this->data['preload'] = true;
106
+ }
107
+
108
+ $this->data['post'] = $post = $this->input->post($DefaultOptions);
109
+
110
+ if (is_array($this->data['post']['cpt'])) $this->data['post']['cpt'] = $this->data['post']['cpt'][0];
111
+
112
+ // Delete history
113
+ if(is_dir(PMXE_ROOT_DIR.'/history')) {
114
+ $history_files = PMXE_Helper::safe_glob(PMXE_ROOT_DIR . '/history/*', PMXE_Helper::GLOB_RECURSE | PMXE_Helper::GLOB_PATH);
115
+ if (!empty($history_files)) {
116
+ foreach ($history_files as $filePath) {
117
+ @file_exists($filePath) and @unlink($filePath);
118
+ }
119
+ }
120
+ }
121
+
122
+ if (!class_exists('ZipArchive')) {
123
+ $this->errors->add('form-validation', __('ZipArchive class is missing on your server.<br/>Please contact your web hosting provider and ask them to install and activate ZipArchive.', 'wp_all_export_plugin'));
124
+ }
125
+ if (!class_exists('XMLReader') or !class_exists('XMLWriter')) {
126
+ $this->errors->add('form-validation', __('Required PHP components are missing.<br/><br/>WP All Export requires XMLReader, and XMLWriter PHP modules to be installed.<br/>These are standard features of PHP, and are necessary for WP All Export to write the files you are trying to export.<br/>Please contact your web hosting provider and ask them to install and activate the DOMDocument, XMLReader, and XMLWriter PHP modules.', 'wp_all_export_plugin'));
127
+ }
128
+
129
+ if ($this->input->post('is_submitted')) {
130
+
131
+ PMXE_Plugin::$session->set('export_type', $post['export_type']);
132
+ PMXE_Plugin::$session->set('filter_rules_hierarhy', $post['filter_rules_hierarhy']);
133
+ PMXE_Plugin::$session->set('product_matching_mode', $post['product_matching_mode']);
134
+ PMXE_Plugin::$session->set('wp_query_selector', $post['wp_query_selector']);
135
+ PMXE_Plugin::$session->set('taxonomy_to_export', $post['taxonomy_to_export']);
136
+ PMXE_Plugin::$session->set('created_at_version', $post['created_at_version']);
137
+
138
+ if (!empty($post['auto_generate'])) {
139
+ $auto_generate = XmlCsvExport::auto_genetate_export_fields($post, $this->errors);
140
+
141
+ foreach ($auto_generate as $key => $value) {
142
+ PMXE_Plugin::$session->set($key, $value);
143
+ }
144
+
145
+ PMXE_Plugin::$session->save_data();
146
+ } else {
147
+ $engine = new XmlExportEngine($post, $this->errors);
148
+ $engine->init_additional_data();
149
+ }
150
+ }
151
+
152
+ if ($this->input->post('is_submitted') and !$this->errors->get_error_codes()) {
153
+
154
+ check_admin_referer('choose-cpt', '_wpnonce_choose-cpt');
155
+
156
+ PMXE_Plugin::$session->save_data();
157
+
158
+ if (!empty($post['auto_generate'])) {
159
+ wp_redirect(add_query_arg('action', 'options', $this->baseUrl));
160
+ die();
161
+ } else {
162
+ wp_redirect(add_query_arg('action', 'template', $this->baseUrl));
163
+ die();
164
+ }
165
+
166
+ }
167
+
168
+ $this->render();
169
+ }
170
+
171
+ /**
172
+ * Step #2: Export Template
173
+ */
174
+ public function template()
175
+ {
176
+
177
+ $template = new PMXE_Template_Record();
178
+
179
+ $default = PMXE_Plugin::get_default_import_options();
180
+
181
+ $this->data['dismiss_warnings'] = 0;
182
+
183
+ if ($this->isWizard) {
184
+ // New export
185
+ $DefaultOptions = (PMXE_Plugin::$session->has_session() ? PMXE_Plugin::$session->get_clear_session_data() : array()) + $default;
186
+ $post = $this->input->post($DefaultOptions);
187
+ } else {
188
+ // Edit export
189
+ $DefaultOptions = $this->data['export']->options + $default;
190
+
191
+ if (empty($this->data['export']->options['export_variations'])) {
192
+ $DefaultOptions['export_variations'] = XmlExportEngine::VARIABLE_PRODUCTS_EXPORT_PARENT_AND_VARIATION;
193
+ }
194
+ if (empty($this->data['export']->options['export_variations_title'])) {
195
+ $DefaultOptions['export_variations_title'] = XmlExportEngine::VARIATION_USE_DEFAULT_TITLE;
196
+ }
197
+ $post = $this->input->post($DefaultOptions);
198
+ $post['scheduled'] = $this->data['export']->scheduled;
199
+
200
+ foreach ($post as $key => $value) {
201
+ PMXE_Plugin::$session->set($key, $value);
202
+ }
203
+ $this->data['dismiss_warnings'] = get_option('wpae_dismiss_warnings_' . $this->data['export']->id, 0);
204
+ }
205
+
206
+ $max_input_vars = @ini_get('max_input_vars');
207
+
208
+ if (ctype_digit($max_input_vars) && count($_POST, COUNT_RECURSIVE) >= $max_input_vars) {
209
+ $this->errors->add('form-validation', sprintf(__('You\'ve reached your max_input_vars limit of %d. Please contact your web host to increase it.', 'wp_all_export_plugin'), $max_input_vars));
210
+ }
211
+
212
+ PMXE_Plugin::$session->save_data();
213
+
214
+ $this->data['post'] =& $post;
215
+
216
+ PMXE_Plugin::$session->set('is_loaded_template', '');
217
+
218
+ $this->data['engine'] = null;
219
+
220
+ XmlExportEngine::$exportQuery = PMXE_Plugin::$session->get('exportQuery');
221
+
222
+ if (($load_template = $this->input->post('load_template'))) { // init form with template selected
223
+ if (!$template->getById($load_template)->isEmpty()) {
224
+ $template_options = $template->options;
225
+ unset($template_options['cpt']);
226
+ unset($template_options['wp_query']);
227
+ unset($template_options['filter_rules_hierarhy']);
228
+ unset($template_options['product_matching_mode']);
229
+ unset($template_options['wp_query_selector']);
230
+ $this->data['post'] = array_merge($post, $template_options);
231
+ PMXE_Plugin::$session->set('is_loaded_template', $load_template);
232
+ }
233
+
234
+ } elseif ($this->input->post('is_submitted')) {
235
+ check_admin_referer('template', '_wpnonce_template');
236
+
237
+ if (empty($post['cc_type'][0]) && !in_array($post['xml_template_type'], array('custom', 'XmlGoogleMerchants'))) {
238
+ $this->errors->add('form-validation', __('You haven\'t selected any columns for export.', 'wp_all_export_plugin'));
239
+ }
240
+
241
+ if ('csv' == $post['export_to'] and '' == $post['delimiter']) {
242
+ $this->errors->add('form-validation', __('CSV delimiter must be specified.', 'wp_all_export_plugin'));
243
+ }
244
+
245
+ if ('xml' == $post['export_to'] && !in_array($post['xml_template_type'], array('custom', 'XmlGoogleMerchants'))) {
246
+ $post['main_xml_tag'] = preg_replace('/[^a-z0-9_]/i', '', $post['main_xml_tag']);
247
+ if (empty($post['main_xml_tag'])) {
248
+ $this->errors->add('form-validation', __('Main XML Tag is required.', 'wp_all_export_plugin'));
249
+ }
250
+
251
+ $post['record_xml_tag'] = preg_replace('/[^a-z0-9_]/i', '', $post['record_xml_tag']);
252
+ if (empty($post['record_xml_tag'])) {
253
+ $this->errors->add('form-validation', __('Single Record XML Tag is required.', 'wp_all_export_plugin'));
254
+ }
255
+
256
+ if ($post['main_xml_tag'] == $post['record_xml_tag']) {
257
+ $this->errors->add('form-validation', __('Main XML Tag equals to Single Record XML Tag.', 'wp_all_export_plugin'));
258
+ }
259
+ }
260
+
261
+ if (($post['export_to'] == XmlExportEngine::EXPORT_TYPE_XML) && in_array($post['xml_template_type'], array('custom', 'XmlGoogleMerchants'))) {
262
+
263
+ if (empty($post['custom_xml_template'])) {
264
+ $this->errors->add('form-validation', __('XML template is empty.', 'wp_all_export_plugin'));
265
+ }
266
+
267
+ // Convert Custom XML template to default
268
+ if (!empty($post['custom_xml_template'])) {
269
+
270
+ $post['custom_xml_template'] = str_replace('<ID>', '<id>', $post['custom_xml_template']);
271
+ $post['custom_xml_template'] = str_replace('</ID>', '</id>', $post['custom_xml_template']);
272
+
273
+ $post['custom_xml_template'] = str_replace("<!-- BEGIN POST LOOP -->", "<!-- BEGIN LOOP -->", $post['custom_xml_template']);
274
+ $post['custom_xml_template'] = str_replace("<!-- END POST LOOP -->", "<!-- END LOOP -->", $post['custom_xml_template']);
275
+
276
+ $this->data['engine'] = new XmlExportEngine($post, $this->errors);
277
+
278
+ $this->data['engine']->init_additional_data();
279
+
280
+ $this->data = array_merge($this->data, $this->data['engine']->init_available_data());
281
+
282
+ $result = $this->data['engine']->parse_custom_xml_template();
283
+
284
+ if (!$this->errors->get_error_codes()) {
285
+ $post = array_merge($post, $result);
286
+ }
287
+ }
288
+ }
289
+
290
+ if (!$this->errors->get_error_codes()) {
291
+
292
+ if (!empty($post['name']) and !empty($post['save_template_as'])) { // save template in database
293
+ $template->getByName($post['name'])->set(array(
294
+ 'name' => $post['name'],
295
+ 'options' => $post
296
+ ))->save();
297
+ PMXE_Plugin::$session->set('saved_template', $template->id);
298
+ }
299
+
300
+ if ($this->isWizard) {
301
+ foreach ($this->data['post'] as $key => $value) {
302
+ PMXE_Plugin::$session->set($key, $value);
303
+ }
304
+ PMXE_Plugin::$session->save_data();
305
+ wp_redirect(add_query_arg('action', 'options', $this->baseUrl));
306
+ die();
307
+ } else {
308
+ $this->data['export']->set(array('options' => $post, 'settings_update_on' => date('Y-m-d H:i:s')))->save();
309
+
310
+ if (!empty($post['friendly_name'])) {
311
+ $this->data['export']->set(array('friendly_name' => $post['friendly_name'], 'scheduled' => (($post['is_scheduled']) ? $post['scheduled_period'] : '')))->save();
312
+ }
313
+
314
+ wp_redirect(add_query_arg(array('page' => 'pmxe-admin-manage', 'pmxe_nt' => urlencode(__('Options updated', 'pmxi_plugin'))) + array_intersect_key($_GET, array_flip($this->baseUrlParamNames)), admin_url('admin.php')));
315
+ die();
316
+ }
317
+ }
318
+ }
319
+
320
+ if (empty($this->data['engine'])) {
321
+
322
+ $this->data['engine'] = new XmlExportEngine($post, $this->errors);
323
+
324
+ $this->data['engine']->init_additional_data();
325
+
326
+ $this->data = array_merge($this->data, $this->data['engine']->init_available_data());
327
+ }
328
+
329
+ $this->data['available_data_view'] = $this->data['engine']->render();
330
+
331
+ $this->data['available_fields_view'] = $this->data['engine']->render_new_field();
332
+
333
+ if (class_exists('SitePress')) {
334
+ global $sitepress;
335
+ $langs = $sitepress->get_active_languages();
336
+ if (!empty($langs)) {
337
+ // prepare active languages list
338
+ $language_list = array('all' => 'All');
339
+ foreach ($langs as $code => $langInfo) {
340
+ $language_list[$code] = "<img width='18' height='12' src='" . $sitepress->get_flag_url($code) . "' style='position:relative; top: 2px;'/> " . $langInfo['display_name'];
341
+ if(isset($this->default_language)){
342
+ if ($code == $this->default_language) $language_list[$code] .= ' ( <strong>default</strong> )';
343
+ }
344
+
345
+ }
346
+ }
347
+ $this->data['wpml_options'] = $language_list;
348
+ }
349
+
350
+ $this->render();
351
+ }
352
+
353
+ /**
354
+ * Step #3: Export Options
355
+ */
356
+ public function options()
357
+ {
358
+ $default = PMXE_Plugin::get_default_import_options();
359
+
360
+ if ($this->isWizard) {
361
+
362
+ $DefaultOptions = (PMXE_Plugin::$session->has_session() ? PMXE_Plugin::$session->get_clear_session_data() : array()) + $default;
363
+ $post = $this->input->post($DefaultOptions);
364
+
365
+ if(isset($post['update_previous'])) {
366
+ $exportId = $post['update_previous'];
367
+ } else {
368
+ $exportId = false;
369
+ }
370
+
371
+ if(!$exportId) {
372
+ $export = $this->data['update_previous'];
373
+ $export->set(
374
+ array(
375
+ 'triggered' => 0,
376
+ 'processing' => 0,
377
+ 'exported' => 0,
378
+ 'executing' => 0,
379
+ 'canceled' => 0,
380
+ 'options' => $post,
381
+ 'friendly_name' => $this->getFriendlyName($post),
382
+ 'last_activity' => date('Y-m-d H:i:s')
383
+ )
384
+ )->save();
385
+
386
+ PMXE_Plugin::$session->set('update_previous', $export->id);
387
+ PMXE_Plugin::$session->set('friendly_name', $this->getFriendlyName($post));
388
+ PMXE_Plugin::$session->save_data();
389
+ $exportId = $export->id;
390
+ }
391
+ $this->data['export_id'] = $exportId;
392
+ $this->data['export'] = new PMXE_Export_Record();
393
+ $this->data['export'] = $this->data['export']->getBy('id', $exportId);
394
+
395
+ if(empty($post['friendly_name'])) {
396
+ $post['friendly_name'] = $this->getFriendlyName($post);
397
+ }
398
+ }
399
+ else {
400
+ $DefaultOptions = $this->data['export']->options + $default;
401
+ if (empty($this->data['export']->options['export_variations'])) {
402
+ $DefaultOptions['export_variations'] = XmlExportEngine::VARIABLE_PRODUCTS_EXPORT_PARENT_AND_VARIATION;
403
+ }
404
+ if (empty($this->data['export']->options['export_variations_title'])) {
405
+ $DefaultOptions['export_variations_title'] = XmlExportEngine::VARIATION_USE_DEFAULT_TITLE;
406
+ }
407
+ $post = $this->input->post($DefaultOptions);
408
+ $post['scheduled'] = $this->data['export']->scheduled;
409
+
410
+ foreach ($post as $key => $value) {
411
+ PMXE_Plugin::$session->set($key, $value);
412
+ }
413
+ PMXE_Plugin::$session->save_data();
414
+ $this->data['export_id'] = $this->data['export']->id;
415
+ }
416
+
417
+ $this->data['engine'] = new XmlExportEngine($post, $this->errors);
418
+
419
+ $this->data['engine']->init_available_data();
420
+
421
+ $this->data['post'] =& $post;
422
+
423
+ if ($this->input->post('is_submitted')) {
424
+
425
+ check_admin_referer('options', '_wpnonce_options');
426
+
427
+ if ($post['is_generate_templates'] and '' == $post['template_name']) {
428
+ $friendly_name = $this->getFriendlyName($post);
429
+ $post['template_name'] = $friendly_name;
430
+ }
431
+
432
+ if ($this->isWizard) {
433
+ if (!$this->errors->get_error_codes()) {
434
+ foreach ($this->data['post'] as $key => $value) {
435
+ PMXE_Plugin::$session->set($key, $value);
436
+ }
437
+ PMXE_Plugin::$session->save_data();
438
+ wp_redirect(add_query_arg('action', 'process', $this->baseUrl));
439
+ die();
440
+ }
441
+ } else {
442
+ $this->errors->remove('count-validation');
443
+ if (!$this->errors->get_error_codes()) {
444
+ $this->data['export']->set(array('options' => $post, 'settings_update_on' => date('Y-m-d H:i:s')))->save();
445
+ if (!empty($post['friendly_name'])) {
446
+ $this->data['export']->set(array('friendly_name' => $post['friendly_name'], 'scheduled' => (($post['is_scheduled']) ? $post['scheduled_period'] : '')))->save();
447
+ }
448
+ wp_redirect(add_query_arg(array('page' => 'pmxe-admin-manage', 'pmxe_nt' => urlencode(__('Options updated', 'wp_all_export_plugin'))) + array_intersect_key($_GET, array_flip($this->baseUrlParamNames)), admin_url('admin.php')));
449
+ die();
450
+ }
451
+ }
452
+ }
453
+
454
+ $this->render();
455
+ }
456
+
457
+ /**
458
+ * Step #4: Export Processing
459
+ */
460
+ public function process()
461
+ {
462
+ @set_time_limit(0);
463
+
464
+ $export = $this->data['update_previous'];
465
+
466
+ if (!PMXE_Plugin::is_ajax()) {
467
+
468
+ if ("" == PMXE_Plugin::$session->friendly_name) {
469
+
470
+ $post_types = PMXE_Plugin::$session->get('cpt');
471
+ if (!empty($post_types)) {
472
+ if (in_array('users', $post_types)) {
473
+ $friendly_name = 'Users Export - ' . date("Y F d H:i");
474
+ } elseif (in_array('shop_customer', $post_types)) {
475
+ $friendly_name = 'Customers Export - ' . date("Y F d H:i");
476
+ } elseif (in_array('comments', $post_types)) {
477
+ $friendly_name = 'Comments Export - ' . date("Y F d H:i");
478
+ } elseif (in_array('taxonomies', $post_types)) {
479
+ $tx = get_taxonomy(PMXE_Plugin::$session->get('taxonomy_to_export'));
480
+ if (!empty($tx->labels->name)) {
481
+ $friendly_name = $tx->labels->name . ' Export - ' . date("Y F d H:i");
482
+ } else {
483
+ $friendly_name = 'Taxonomy Terms Export - ' . date("Y F d H:i");
484
+ }
485
+ } else {
486
+ $post_type_details = get_post_type_object(array_shift($post_types));
487
+ $friendly_name = $post_type_details->labels->name . ' Export - ' . date("Y F d H:i");
488
+ }
489
+ } else {
490
+ $friendly_name = 'WP_Query Export - ' . date("Y F d H:i");
491
+ }
492
+
493
+ PMXE_Plugin::$session->set('friendly_name', $friendly_name);
494
+ }
495
+
496
+ PMXE_Plugin::$session->set('file', '');
497
+ PMXE_Plugin::$session->save_data();
498
+
499
+ $export->set(
500
+ array(
501
+ 'triggered' => 0,
502
+ 'processing' => 0,
503
+ 'exported' => 0,
504
+ 'executing' => 1,
505
+ 'canceled' => 0,
506
+ 'options' => PMXE_Plugin::$session->get_clear_session_data(),
507
+ 'friendly_name' => PMXE_Plugin::$session->friendly_name,
508
+ 'scheduled' => (PMXE_Plugin::$session->is_scheduled) ? PMXE_Plugin::$session->scheduled_period : '',
509
+ //'registered_on' => date('Y-m-d H:i:s'),
510
+ 'last_activity' => date('Y-m-d H:i:s')
511
+ )
512
+ )->save();
513
+
514
+ // create an import for this export
515
+ if ($export->options['export_to'] == 'csv' || !in_array($export->options['xml_template_type'], array('custom', 'XmlGoogleMerchants'))) PMXE_Wpallimport::create_an_import($export);
516
+ PMXE_Plugin::$session->set('update_previous', $export->id);
517
+ PMXE_Plugin::$session->save_data();
518
+
519
+ do_action('pmxe_before_export', $export->id);
520
+
521
+ }
522
+
523
+ $this->render();
524
+ }
525
+
526
+ /**
527
+ * @param $post
528
+ * @return string
529
+ */
530
+ protected function getFriendlyName($post)
531
+ {
532
+ $friendly_name = '';
533
+ $post_types = PMXE_Plugin::$session->get('cpt');
534
+ if (!empty($post_types)) {
535
+ if (in_array('users', $post_types)) {
536
+ $friendly_name = 'Users Export - ' . date("Y F d H:i");
537
+ return $friendly_name;
538
+ } elseif (in_array('shop_customer', $post_types)) {
539
+ $friendly_name = 'Customers Export - ' . date("Y F d H:i");
540
+ return $friendly_name;
541
+ } elseif (in_array('comments', $post_types)) {
542
+ $friendly_name = 'Comments Export - ' . date("Y F d H:i");
543
+ return $friendly_name;
544
+ } elseif (in_array('taxonomies', $post_types)) {
545
+ $tx = get_taxonomy($post['taxonomy_to_export']);
546
+ if (!empty($tx->labels->name)) {
547
+ $friendly_name = $tx->labels->name . ' Export - ' . date("Y F d H:i");
548
+ return $friendly_name;
549
+ } else {
550
+ $friendly_name = 'Taxonomy Terms Export - ' . date("Y F d H:i");
551
+ return $friendly_name;
552
+ }
553
+ } else {
554
+ $post_type_details = get_post_type_object(array_shift($post_types));
555
+ $friendly_name = $post_type_details->labels->name . ' Export - ' . date("Y F d H:i");
556
+ return $friendly_name;
557
+ }
558
+ } else {
559
+ $friendly_name = 'WP_Query Export - ' . date("Y F d H:i");
560
+ return $friendly_name;
561
+ }
562
+ }
563
+ }
trunk/controllers/admin/feedback.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Admin Help page
4
+ *
5
+ * @author Pavel Kulbakin <p.kulbakin@gmail.com>
6
+ */
7
+ class PMXE_Admin_Feedback extends PMXE_Controller_Admin {
8
+
9
+ public function index() {
10
+ $this->render();
11
+ }
12
+ }
trunk/controllers/admin/help.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Admin Help page
4
+ *
5
+ * @author Pavel Kulbakin <p.kulbakin@gmail.com>
6
+ */
7
+ class PMXE_Admin_Help extends PMXE_Controller_Admin {
8
+
9
+ public function index() {
10
+ $this->render();
11
+ }
12
+ }
trunk/controllers/admin/manage.php ADDED
@@ -0,0 +1,526 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Manage Imports
4
+ *
5
+ * @author Pavel Kulbakin <p.kulbakin@gmail.com>
6
+ */
7
+ class PMXE_Admin_Manage extends PMXE_Controller_Admin {
8
+
9
+ public function init() {
10
+ parent::init();
11
+
12
+ if ('update' == PMXE_Plugin::getInstance()->getAdminCurrentScreen()->action) {
13
+ $this->isInline = true;
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Previous Imports list
19
+ */
20
+ public function index() {
21
+
22
+ $get = $this->input->get(array(
23
+ 's' => '',
24
+ 'order_by' => 'id',
25
+ 'order' => 'DESC',
26
+ 'pagenum' => 1,
27
+ 'perPage' => 25,
28
+ ));
29
+ $get['pagenum'] = absint($get['pagenum']);
30
+ extract($get);
31
+ $this->data += $get;
32
+
33
+ if ( ! in_array($order_by, array('registered_on', 'id', 'friendly_name'))){
34
+ $order_by = 'registered_on';
35
+ }
36
+
37
+ if ( ! in_array($order, array('DESC', 'ASC'))){
38
+ $order = 'DESC';
39
+ }
40
+
41
+ $list = new PMXE_Export_List();
42
+ $by = array('parent_id' => 0);
43
+ if ('' != $s) {
44
+ $like = '%' . preg_replace('%\s+%', '%', preg_replace('/[%?]/', '\\\\$0', $s)) . '%';
45
+ $by[] = array(array('friendly_name LIKE' => $like, 'registered_on LIKE' => $like), 'OR');
46
+ }
47
+
48
+ $this->data['list'] = $list->setColumns(
49
+ $list->getTable() . '.*'
50
+ )->getBy($by, "$order_by $order", $pagenum, $perPage, $list->getTable() . '.id');
51
+
52
+ $this->data['page_links'] = paginate_links(array(
53
+ 'base' => add_query_arg('pagenum', '%#%', $this->baseUrl),
54
+ 'add_args' => array('page' => 'pmxe-admin-manage'),
55
+ 'format' => '',
56
+ 'prev_text' => __('&laquo;', 'PMXE_plugin'),
57
+ 'next_text' => __('&raquo;', 'PMXE_plugin'),
58
+ 'total' => ceil($list->total() / $perPage),
59
+ 'current' => $pagenum,
60
+ ));
61
+
62
+ PMXE_Plugin::$session->clean_session();
63
+
64
+ $this->render();
65
+ }
66
+
67
+ /**
68
+ * Edit Options
69
+ */
70
+ public function options() {
71
+
72
+ // deligate operation to other controller
73
+ $controller = new PMXE_Admin_Export();
74
+ $controller->set('isTemplateEdit', true);
75
+ $controller->options();
76
+ }
77
+
78
+ /**
79
+ * Edit Template
80
+ */
81
+ public function template() {
82
+
83
+ // deligate operation to other controller
84
+ $controller = new PMXE_Admin_Export();
85
+ $controller->set('isTemplateEdit', true);
86
+ $controller->template();
87
+ }
88
+
89
+ /**
90
+ * Cron Scheduling
91
+ */
92
+ public function scheduling() {
93
+ $this->data['id'] = $id = $this->input->get('id');
94
+ $this->data['cron_job_key'] = PMXE_Plugin::getInstance()->getOption('cron_job_key');
95
+ $this->data['item'] = $item = new PMXE_Export_Record();
96
+ if ( ! $id or $item->getById($id)->isEmpty()) {
97
+ wp_redirect($this->baseUrl); die();
98
+ }
99
+
100
+ $wp_uploads = wp_upload_dir();
101
+
102
+ $this->data['file_path'] = site_url() . '/wp-cron.php?security_token=' . substr(md5($this->data['cron_job_key'] . $item['id']), 0, 16) . '&export_id=' . $item['id'] . '&action=get_data';
103
+
104
+ $this->data['bundle_url'] = '';
105
+
106
+ if ( ! empty($item['options']['bundlepath']) )
107
+ {
108
+ $this->data['bundle_url'] = site_url() . '/wp-cron.php?security_token=' . substr(md5($this->data['cron_job_key'] . $item['id']), 0, 16) . '&export_id=' . $item['id'] . '&action=get_bundle&t=zip';
109
+ }
110
+
111
+ $this->render();
112
+ }
113
+
114
+ /**
115
+ * Google merchants info
116
+ */
117
+ public function google_merchants_info() {
118
+
119
+ $this->data['id'] = $id = $this->input->get('id');
120
+ $this->data['cron_job_key'] = PMXE_Plugin::getInstance()->getOption('cron_job_key');
121
+ $this->data['item'] = $item = new PMXE_Export_Record();
122
+ if ( ! $id or $item->getById($id)->isEmpty()) {
123
+ wp_redirect($this->baseUrl); die();
124
+ }
125
+
126
+ $this->data['file_path'] = site_url() . '/wp-cron.php?security_token=' . substr(md5($this->data['cron_job_key'] . $item['id']), 0, 16) . '&export_id=' . $item['id'] . '&action=get_data';
127
+
128
+ $this->render();
129
+ }
130
+
131
+ /**
132
+ * Download import templates
133
+ */
134
+ public function templates() {
135
+ $this->data['id'] = $id = $this->input->get('id');
136
+ $this->data['item'] = $item = new PMXE_Export_Record();
137
+ if ( ! $id or $item->getById($id)->isEmpty()) {
138
+ wp_redirect($this->baseUrl); die();
139
+ }
140
+
141
+ $this->render();
142
+ }
143
+
144
+ /**
145
+ * Cancel import processing
146
+ */
147
+ public function cancel(){
148
+
149
+ $id = $this->input->get('id');
150
+
151
+ PMXE_Plugin::$session->clean_session( $id );
152
+
153
+ $item = new PMXE_Export_Record();
154
+ if ( ! $id or $item->getById($id)->isEmpty()) {
155
+ wp_redirect($this->baseUrl); die();
156
+ }
157
+ $item->set(array(
158
+ 'triggered' => 0,
159
+ 'processing' => 0,
160
+ 'executing' => 0,
161
+ 'canceled' => 1,
162
+ 'canceled_on' => date('Y-m-d H:i:s')
163
+ ))->update();
164
+
165
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(__('Export canceled', 'wp_all_import_plugin')), $this->baseUrl)); die();
166
+ }
167
+
168
+ /**
169
+ * Reexport
170
+ */
171
+ public function update() {
172
+
173
+ $id = $this->input->get('id');
174
+
175
+ PMXE_Plugin::$session->clean_session($id);
176
+
177
+ $action_type = $this->input->get('type');
178
+
179
+ $this->data['item'] = $item = new PMXE_Export_Record();
180
+ if ( ! $id or $item->getById($id)->isEmpty()) {
181
+ wp_redirect($this->baseUrl); die();
182
+ }
183
+
184
+ $item->fix_template_options();
185
+
186
+ $default = PMXE_Plugin::get_default_import_options();
187
+ $DefaultOptions = $item->options + $default;
188
+ if (empty($item->options['export_variations'])){
189
+ $DefaultOptions['export_variations'] = XmlExportEngine::VARIABLE_PRODUCTS_EXPORT_PARENT_AND_VARIATION;
190
+ }
191
+ if (empty($item->options['export_variations_title'])){
192
+ $DefaultOptions['export_variations_title'] = XmlExportEngine::VARIATION_USE_DEFAULT_TITLE;
193
+ }
194
+ $this->data['post'] = $post = $this->input->post($DefaultOptions);
195
+ $this->data['iteration'] = $item->iteration;
196
+
197
+ if ($this->input->post('is_confirmed')) {
198
+
199
+ check_admin_referer('update-export', '_wpnonce_update-export');
200
+
201
+ $iteration = ( empty($item->options['creata_a_new_export_file']) && ! empty($post['creata_a_new_export_file'])) ? 0 : $item->iteration;
202
+
203
+ $item->set(array( 'options' => $post, 'iteration' => $iteration))->save();
204
+ if ( ! empty($post['friendly_name']) ) {
205
+ $item->set( array( 'friendly_name' => $post['friendly_name'], 'scheduled' => (($post['is_scheduled']) ? $post['scheduled_period'] : '') ) )->save();
206
+ }
207
+
208
+ // compose data to look like result of wizard steps
209
+ $sesson_data = $post + array('update_previous' => $item->id ) + $default;
210
+
211
+ foreach ($sesson_data as $key => $value) {
212
+ PMXE_Plugin::$session->set($key, $value);
213
+ }
214
+
215
+ $this->data['engine'] = new XmlExportEngine($sesson_data, $this->errors);
216
+ $this->data['engine']->init_additional_data();
217
+ $this->data['engine']->init_available_data();
218
+
219
+ PMXE_Plugin::$session->save_data();
220
+
221
+ if ( ! $this->errors->get_error_codes() && $this->input->post('record-count')) {
222
+
223
+ // deligate operation to other controller
224
+ $controller = new PMXE_Admin_Export();
225
+ $controller->data['update_previous'] = $item;
226
+ $controller->process();
227
+ return;
228
+
229
+ }
230
+
231
+ $this->errors->remove('count-validation');
232
+ if ( ! $this->errors->get_error_codes()) {
233
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(__('Options updated', 'wp_all_export_plugin')), $this->baseUrl));
234
+ die();
235
+ }
236
+
237
+ }
238
+
239
+ $this->data['isWizard'] = false;
240
+ $this->data['engine'] = new XmlExportEngine($post, $this->errors);
241
+ $this->data['engine']->init_available_data();
242
+
243
+ $this->render();
244
+ }
245
+
246
+ /**
247
+ * Delete an export
248
+ */
249
+ public function delete() {
250
+ $id = $this->input->get('id');
251
+ $this->data['item'] = $item = new PMXE_Export_Record();
252
+ if ( ! $id or $item->getById($id)->isEmpty()) {
253
+ wp_redirect($this->baseUrl); die();
254
+ }
255
+
256
+ if ($this->input->post('is_confirmed')) {
257
+ check_admin_referer('delete-export', '_wpnonce_delete-export');
258
+ $item->delete();
259
+
260
+ $scheduling = \Wpae\Scheduling\Scheduling::create();
261
+ $scheduling->deleteScheduleIfExists($id);
262
+
263
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(__('Export deleted', 'wp_all_export_plugin')), $this->baseUrl)); die();
264
+ }
265
+
266
+ $this->render();
267
+ }
268
+
269
+ /**
270
+ * Bulk actions
271
+ */
272
+ public function bulk() {
273
+ check_admin_referer('bulk-exports', '_wpnonce_bulk-exports');
274
+ if ($this->input->post('doaction2')) {
275
+ $this->data['action'] = $action = $this->input->post('bulk-action2');
276
+ } else {
277
+ $this->data['action'] = $action = $this->input->post('bulk-action');
278
+ }
279
+ $this->data['ids'] = $ids = $this->input->post('items');
280
+ $this->data['items'] = $items = new PMXE_Export_List();
281
+ if (empty($action) or ! in_array($action, array('delete')) or empty($ids) or $items->getBy('id', $ids)->isEmpty()) {
282
+ wp_redirect($this->baseUrl); die();
283
+ }
284
+ if ($this->input->post('is_confirmed')) {
285
+ foreach($items->convertRecords() as $item) {
286
+
287
+ if ($item->attch_id) wp_delete_attachment($item->attch_id, true);
288
+
289
+ $item->delete();
290
+
291
+ $scheduling = \Wpae\Scheduling\Scheduling::create();
292
+ $scheduling->deleteScheduleIfExists($item->id);
293
+ }
294
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(sprintf(__('%d %s deleted', 'wp_all_export_plugin'), $items->count(), _n('export', 'exports', $items->count(), 'wp_all_export_plugin'))), $this->baseUrl)); die();
295
+ }
296
+ $this->render();
297
+ }
298
+
299
+ public function get_template(){
300
+ $nonce = (!empty($_REQUEST['_wpnonce'])) ? $_REQUEST['_wpnonce'] : '';
301
+ if ( ! wp_verify_nonce( $nonce, '_wpnonce-download_template' ) ) {
302
+ die( __('Security check', 'wp_all_export_plugin') );
303
+ } else {
304
+
305
+ $id = $this->input->get('id');
306
+
307
+ $export = new PMXE_Export_Record();
308
+
309
+ $filepath = '';
310
+
311
+ $export_data = array();
312
+
313
+ if ( ! $export->getById($id)->isEmpty()){
314
+
315
+ $export_data[] = $export->options['tpl_data'];
316
+ $uploads = wp_upload_dir();
317
+ $targetDir = $uploads['basedir'] . DIRECTORY_SEPARATOR . PMXE_Plugin::TEMP_DIRECTORY;
318
+
319
+ $export_file_name = "WP All Import Template - " . sanitize_file_name($export->friendly_name) . ".txt";
320
+
321
+ file_put_contents($targetDir . DIRECTORY_SEPARATOR . $export_file_name, json_encode($export_data));
322
+
323
+ PMXE_download::csv($targetDir . DIRECTORY_SEPARATOR . $export_file_name);
324
+
325
+ }
326
+ }
327
+ }
328
+
329
+ /*
330
+ * Download bundle for WP All Import
331
+ *
332
+ */
333
+ public function bundle()
334
+ {
335
+ $nonce = (!empty($_REQUEST['_wpnonce'])) ? $_REQUEST['_wpnonce'] : '';
336
+ if ( ! wp_verify_nonce( $nonce, '_wpnonce-download_bundle' ) ) {
337
+ die( __('Security check', 'wp_all_export_plugin') );
338
+ } else {
339
+
340
+ $uploads = wp_upload_dir();
341
+
342
+ $id = $this->input->get('id');
343
+
344
+ $export = new PMXE_Export_Record();
345
+
346
+ if ( ! $export->getById($id)->isEmpty())
347
+ {
348
+ if ( ! empty($export->options['bundlepath']) )
349
+ {
350
+ $bundle_path = wp_all_export_get_absolute_path($export->options['bundlepath']);
351
+
352
+ if ( @file_exists($bundle_path) )
353
+ {
354
+ $bundle_url = $uploads['baseurl'] . str_replace($uploads['basedir'], '', $bundle_path);
355
+
356
+ PMXE_download::zip($bundle_path);
357
+ }
358
+ }
359
+ else
360
+ {
361
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(__('The exported bundle is missing and can\'t be downloaded. Please re-run your export to re-generate it.', 'wp_all_export_plugin')), $this->baseUrl)); die();
362
+ }
363
+ }
364
+ else
365
+ {
366
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(__('This export doesn\'t exist.', 'wp_all_export_plugin')), $this->baseUrl)); die();
367
+ }
368
+ }
369
+ }
370
+
371
+ public function split_bundle(){
372
+ $nonce = (!empty($_REQUEST['_wpnonce'])) ? $_REQUEST['_wpnonce'] : '';
373
+ if ( ! wp_verify_nonce( $nonce, '_wpnonce-download_split_bundle' ) ) {
374
+ die( __('Security check', 'wp_all_export_plugin') );
375
+ } else {
376
+
377
+ $uploads = wp_upload_dir();
378
+
379
+ $id = PMXE_Plugin::$session->update_previous;
380
+
381
+ if (empty($id))
382
+ $id = $this->input->get('id');
383
+
384
+ $export = new PMXE_Export_Record();
385
+
386
+ if ( ! $export->getById($id)->isEmpty())
387
+ {
388
+ if ( ! empty($export->options['split_files_list']))
389
+ {
390
+ $tmp_dir = $uploads['basedir'] . DIRECTORY_SEPARATOR . PMXE_Plugin::TEMP_DIRECTORY . DIRECTORY_SEPARATOR . md5($export->id) . DIRECTORY_SEPARATOR;
391
+ $bundle_dir = $tmp_dir . 'split_files' . DIRECTORY_SEPARATOR;
392
+
393
+ wp_all_export_rrmdir($tmp_dir);
394
+
395
+ @mkdir($tmp_dir);
396
+ @mkdir($bundle_dir);
397
+
398
+ foreach ($export->options['split_files_list'] as $file) {
399
+ @copy( $file, $bundle_dir . basename($file) );
400
+ }
401
+
402
+ $friendly_name = sanitize_file_name($export->friendly_name);
403
+
404
+ $bundle_path = $tmp_dir . $friendly_name . '-split-files.zip';
405
+
406
+ PMXE_Zip::zipDir($bundle_dir, $bundle_path);
407
+
408
+ if (file_exists($bundle_path))
409
+ {
410
+ $bundle_url = $uploads['baseurl'] . str_replace($uploads['basedir'], '', $bundle_path);
411
+
412
+ PMXE_download::zip($bundle_path);
413
+ }
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ /*
420
+ * Download import log file
421
+ *
422
+ */
423
+ public function get_file(){
424
+
425
+ $nonce = (!empty($_REQUEST['_wpnonce'])) ? $_REQUEST['_wpnonce'] : '';
426
+ if ( ! wp_verify_nonce( $nonce, '_wpnonce-download_feed' ) ) {
427
+ die( __('Security check', 'wp_all_export_plugin') );
428
+ } else {
429
+
430
+ $is_secure_import = PMXE_Plugin::getInstance()->getOption('secure');
431
+
432
+ $id = $this->input->get('id');
433
+
434
+ $export = new PMXE_Export_Record();
435
+
436
+ $filepath = '';
437
+
438
+ if ( ! $export->getById($id)->isEmpty())
439
+ {
440
+ if ( ! $is_secure_import)
441
+ {
442
+ $filepath = get_attached_file($export->attch_id);
443
+ }
444
+ else
445
+ {
446
+ $filepath = wp_all_export_get_absolute_path($export->options['filepath']);
447
+ }
448
+
449
+ if ( @file_exists($filepath) )
450
+ {
451
+ switch ($export->options['export_to'])
452
+ {
453
+ case 'xml':
454
+ if($export['options']['xml_template_type'] == XmlExportEngine::EXPORT_TYPE_GOOLE_MERCHANTS) {
455
+ PMXE_Download::txt($filepath);
456
+ } else {
457
+ PMXE_download::xml($filepath);
458
+ }
459
+
460
+ break;
461
+ case 'csv':
462
+ if (empty($export->options['export_to_sheet']) or $export->options['export_to_sheet'] == 'csv')
463
+ {
464
+ PMXE_download::csv($filepath);
465
+ }
466
+ else
467
+ {
468
+ PMXE_download::xls($filepath);
469
+ }
470
+ break;
471
+ default:
472
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(__('File format not supported', 'wp_all_export_plugin')), $this->baseUrl)); die();
473
+ break;
474
+ }
475
+ }
476
+ else
477
+ {
478
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(__('The exported file is missing and can\'t be downloaded. Please re-run your export to re-generate it.', 'wp_all_export_plugin')), $this->baseUrl)); die();
479
+ }
480
+ }
481
+ else
482
+ {
483
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(__('The exported file is missing and can\'t be downloaded. Please re-run your export to re-generate it.', 'wp_all_export_plugin')), $this->baseUrl)); die();
484
+ }
485
+ }
486
+ }
487
+
488
+ /**
489
+ * @param $post
490
+ * @return string
491
+ */
492
+ protected function getFriendlyName($post)
493
+ {
494
+ $friendly_name = '';
495
+ $post_types = PMXE_Plugin::$session->get('cpt');
496
+ if (!empty($post_types)) {
497
+ if (in_array('users', $post_types)) {
498
+ $friendly_name = 'Users Export - ' . date("Y F d H:i");
499
+ return $friendly_name;
500
+ } elseif (in_array('shop_customer', $post_types)) {
501
+ $friendly_name = 'Customers Export - ' . date("Y F d H:i");
502
+ return $friendly_name;
503
+ } elseif (in_array('comments', $post_types)) {
504
+ $friendly_name = 'Comments Export - ' . date("Y F d H:i");
505
+ return $friendly_name;
506
+ } elseif (in_array('taxonomies', $post_types)) {
507
+ $tx = get_taxonomy($post['taxonomy_to_export']);
508
+ if (!empty($tx->labels->name)) {
509
+ $friendly_name = $tx->labels->name . ' Export - ' . date("Y F d H:i");
510
+ return $friendly_name;
511
+ } else {
512
+ $friendly_name = 'Taxonomy Terms Export - ' . date("Y F d H:i");
513
+ return $friendly_name;
514
+ }
515
+ } else {
516
+ $post_type_details = get_post_type_object(array_shift($post_types));
517
+ $friendly_name = $post_type_details->labels->name . ' Export - ' . date("Y F d H:i");
518
+ return $friendly_name;
519
+ }
520
+ } else {
521
+ $friendly_name = 'WP_Query Export - ' . date("Y F d H:i");
522
+ return $friendly_name;
523
+ }
524
+ }
525
+
526
+ }
trunk/controllers/admin/settings.php ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Admin Statistics page
4
+ *
5
+ * @author Pavel Kulbakin <p.kulbakin@gmail.com>
6
+ */
7
+ class PMXE_Admin_Settings extends PMXE_Controller_Admin {
8
+
9
+ /** @var \Wpae\App\Service\License\LicenseActivator */
10
+ private $licenseActivator;
11
+
12
+ protected function init()
13
+ {
14
+ $this->licenseActivator = new \Wpae\App\Service\License\LicenseActivator();
15
+ }
16
+
17
+ public function index() {
18
+
19
+ $this->data['post'] = $post = $this->input->post(PMXE_Plugin::getInstance()->getOption());
20
+
21
+ if ($this->input->post('is_settings_submitted')) { // save settings form
22
+
23
+ check_admin_referer('edit-settings', '_wpnonce_edit-settings');
24
+
25
+ if ( ! $this->errors->get_error_codes()) { // no validation errors detected
26
+
27
+ PMXE_Plugin::getInstance()->updateOption($post);
28
+
29
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(__('Settings saved', 'wp_all_export_plugin')), $this->baseUrl)); die();
30
+ }
31
+ }
32
+
33
+ if ($this->input->post('is_scheduling_license_submitted')) {
34
+
35
+ check_admin_referer('edit-license', '_wpnonce_edit-scheduling-license');
36
+
37
+ if (!$this->errors->get_error_codes()) { // no validation errors detected
38
+
39
+ PMXE_Plugin::getInstance()->updateOption($post);
40
+ if (empty($_POST['pmxe_scheduling_license_activate']) and empty($_POST['pmxe_scheduling_license_deactivate'])) {
41
+ $post['scheduling_license_status'] = $this->check_scheduling_license();
42
+ if ($post['scheduling_license_status'] == 'valid') {
43
+
44
+ $this->data['scheduling_license_message'] = __('License activated.', 'wp_all_import_plugin');
45
+ }
46
+
47
+ PMXE_Plugin::getInstance()->updateOption($post);
48
+ $this->activate_scheduling_licenses();
49
+
50
+ }
51
+ }
52
+
53
+ $this->data['post'] = $post = PMXE_Plugin::getInstance()->getOption();
54
+ }
55
+
56
+
57
+ $post['scheduling_license_status'] = $this->check_scheduling_license();
58
+ $this->data['is_license_active'] = false;
59
+ if (!empty($post['license_status']) && $post['license_status'] == 'valid') {
60
+ $this->data['is_license_active'] = true;
61
+ }
62
+
63
+ $this->data['is_scheduling_license_active'] = false;
64
+ if (!empty($post['scheduling_license_status']) && $post['scheduling_license_status'] == 'valid') {
65
+ $this->data['is_scheduling_license_active'] = true;
66
+ }
67
+
68
+ if ($this->input->post('is_templates_submitted')) { // delete templates form
69
+
70
+ check_admin_referer('delete-templates', '_wpnonce_delete-templates');
71
+
72
+ if ($this->input->post('import_templates')){
73
+
74
+ if (!empty($_FILES)){
75
+ $file_name = $_FILES['template_file']['name'];
76
+ $file_size = $_FILES['template_file']['size'];
77
+ $tmp_name = $_FILES['template_file']['tmp_name'];
78
+
79
+ if(isset($file_name))
80
+ {
81
+ $filename = stripslashes($file_name);
82
+ $extension = strtolower(pmxe_getExtension($filename));
83
+
84
+ if (($extension != "txt"))
85
+ {
86
+ $this->errors->add('form-validation', __('Unknown File extension. Only txt files are permitted', 'wp_all_export_plugin'));
87
+ }
88
+ else {
89
+ $import_data = @file_get_contents($tmp_name);
90
+ if (!empty($import_data)){
91
+ $templates_data = json_decode($import_data, true);
92
+
93
+ if (!empty($templates_data)){
94
+ $templateOptions = empty($templates_data[0]['options']) ? false : unserialize($templates_data[0]['options']);
95
+ if ( empty($templateOptions) ){
96
+ $this->errors->add('form-validation', __('The template is invalid. Options are missing.', 'wp_all_export_plugin'));
97
+ }
98
+ else{
99
+ if (!isset($templateOptions['is_user_export'])){
100
+ $this->errors->add('form-validation', __('The template you\'ve uploaded is intended to be used with WP All Import plugin.', 'wp_all_export_plugin'));
101
+ }
102
+ else{
103
+ $template = new PMXE_Template_Record();
104
+ foreach ($templates_data as $template_data) {
105
+ unset($template_data['id']);
106
+ $template->clear()->set($template_data)->insert();
107
+ }
108
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(sprintf(_n('%d template imported', '%d templates imported', count($templates_data), 'wp_all_export_plugin'), count($templates_data))), $this->baseUrl));
109
+ die();
110
+ }
111
+ }
112
+ }
113
+ else $this->errors->add('form-validation', __('Wrong imported data format', 'wp_all_export_plugin'));
114
+ }
115
+ else $this->errors->add('form-validation', __('File is empty or doesn\'t exests', 'wp_all_export_plugin'));
116
+ }
117
+ }
118
+ else $this->errors->add('form-validation', __('Undefined entry!', 'wp_all_export_plugin'));
119
+ }
120
+ else $this->errors->add('form-validation', __('Please select file.', 'wp_all_export_plugin'));
121
+
122
+ }
123
+ else{
124
+ $templates_ids = $this->input->post('templates', array());
125
+ if (empty($templates_ids)) {
126
+ $this->errors->add('form-validation', __('Templates must be selected', 'wp_all_export_plugin'));
127
+ }
128
+
129
+ if ( ! $this->errors->get_error_codes()) { // no validation errors detected
130
+ if ($this->input->post('delete_templates')){
131
+ $template = new PMXE_Template_Record();
132
+ foreach ($templates_ids as $template_id) {
133
+ $template->clear()->set('id', $template_id)->delete();
134
+ }
135
+ wp_redirect(add_query_arg('pmxe_nt', urlencode(sprintf(_n('%d template deleted', '%d templates deleted', count($templates_ids), 'wp_all_export_plugin'), count($templates_ids))), $this->baseUrl)); die();
136
+ }
137
+ if ($this->input->post('export_templates')){
138
+ $export_data = array();
139
+ $template = new PMXE_Template_Record();
140
+ foreach ($templates_ids as $template_id) {
141
+ $export_data[] = $template->clear()->getBy('id', $template_id)->toArray(TRUE);
142
+ }
143
+
144
+ $uploads = wp_upload_dir();
145
+ $targetDir = $uploads['basedir'] . DIRECTORY_SEPARATOR . PMXE_Plugin::TEMP_DIRECTORY;
146
+ $export_file_name = "templates_".uniqid().".txt";
147
+ file_put_contents($targetDir . DIRECTORY_SEPARATOR . $export_file_name, json_encode($export_data));
148
+
149
+ PMXE_download::csv($targetDir . DIRECTORY_SEPARATOR . $export_file_name);
150
+
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ $this->render();
157
+
158
+ }
159
+
160
+ public function dismiss(){
161
+
162
+ PMXE_Plugin::getInstance()->updateOption("dismiss", 1);
163
+
164
+ exit('OK');
165
+ }
166
+
167
+ protected function activate_scheduling_licenses()
168
+ {
169
+ return $this->licenseActivator->activateLicense(PMXE_Plugin::getSchedulingName(),\Wpae\App\Service\License\LicenseActivator::CONTEXT_SCHEDULING);
170
+ }
171
+
172
+ public function check_scheduling_license()
173
+ {
174
+ $options = PMXE_Plugin::getInstance()->getOption();
175
+
176
+ return $this->licenseActivator->checkLicense(PMXE_Plugin::getSchedulingName(), $options, \Wpae\App\Service\License\LicenseActivator::CONTEXT_SCHEDULING);
177
+ }
178
+ }
trunk/controllers/controller.php ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Common logic for all shortcodes plugin implements
4
+ *
5
+ * @author Pavel Kulbakin <p.kulbakin@gmail.com>
6
+ */
7
+ abstract class PMXE_Controller {
8
+ /**
9
+ * Input class instance to retrieve parameters submitted during page request
10
+ * @var PMXE_Input
11
+ */
12
+ protected $input;
13
+ /**
14
+ * Error messages
15
+ * @var WP_Error
16
+ */
17
+ protected $errors;
18
+ /**
19
+ * Associative array of data which will be automatically available as variables when template is rendered
20
+ * @var array
21
+ */
22
+ public $data = array();
23
+ /**
24
+ * Constructor
25
+ */
26
+ public function __construct() {
27
+ $this->input = new PMXE_Input();
28
+ $this->input->addFilter('trim');
29
+
30
+ $this->errors = new WP_Error();
31
+
32
+ $this->init();
33
+ }
34
+
35
+ /**
36
+ * Method to put controller initialization logic to
37
+ */
38
+ protected function init() {}
39
+
40
+ /**
41
+ * Checks wether protocol is HTTPS and redirects user to secure connection if not
42
+ */
43
+ protected function force_ssl() {
44
+ if (force_ssl_admin() && ! is_ssl()) {
45
+ if ( 0 === strpos($_SERVER['REQUEST_URI'], 'http') ) {
46
+ wp_redirect(preg_replace('|^http://|', 'https://', $_SERVER['REQUEST_URI'])); die();
47
+ } else {
48
+ wp_redirect('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); die();
49
+ }
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Method returning resolved template content
55
+ *
56
+ * @param string [optional] $viewPath Template path to render
57
+ * @throws Exception
58
+ */
59
+ protected function render($viewPath = null) {
60
+
61
+ if ( ! get_current_user_id() or ! current_user_can( PMXE_Plugin::$capabilities )) {
62
+ // This nonce is not valid.
63
+ die( 'Security check' );
64
+
65
+ } else {
66
+
67
+ // assume template file name depending on calling function
68
+ if (is_null($viewPath)) {
69
+ $trace = debug_backtrace();
70
+ $viewPath = str_replace('_', '/', preg_replace('%^' . preg_quote(PMXE_Plugin::PREFIX, '%') . '%', '', strtolower($trace[1]['class']))) . '/' . $trace[1]['function'];
71
+ }
72
+ // append file extension if not specified
73
+ if ( ! preg_match('%\.php$%', $viewPath)) {
74
+ $viewPath .= '.php';
75
+ }
76
+ $filePath = PMXE_Plugin::ROOT_DIR . '/views/' . $viewPath;
77
+ if (is_file($filePath)) {
78
+ extract($this->data);
79
+ include $filePath;
80
+ } else {
81
+ throw new Exception("Requested template file $filePath is not found.");
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Display list of errors
88
+ *
89
+ * @param string|array|WP_Error[optional] $msgs
90
+ */
91
+ protected function error($msgs = NULL) {
92
+ if (is_null($msgs)) {
93
+ $msgs = $this->errors;
94
+ }
95
+ if (is_wp_error($msgs))
96
+ {
97
+ unset($msgs->errors['count-validation']);
98
+
99
+ $msgs = $msgs->get_error_messages();
100
+ }
101
+ if ( ! is_array($msgs)) {
102
+ $msgs = array($msgs);
103
+ }
104
+ $this->data['errors'] = $msgs;
105
+
106
+ $viewPathRel = str_replace('_', '/', preg_replace('%^' . preg_quote(PMXE_Plugin::PREFIX, '%') . '%', '', strtolower(get_class($this)))) . '/error.php';
107
+ if (is_file(PMXE_Plugin::ROOT_DIR . '/views/' . $viewPathRel)) { // if calling controller class has specific error view
108
+ $this->render($viewPathRel);
109
+ } else { // render default error view
110
+ $this->render('controller/error.php');
111
+ }
112
+ }
113
+
114
+ public function download(){
115
+
116
+
117
+ $nonce = (!empty($_REQUEST['_wpnonce'])) ? $_REQUEST['_wpnonce'] : '';
118
+ if ( ! wp_verify_nonce( $nonce, '_wpnonce-download_feed' ) && !isset($_GET['google_feed']) ) {
119
+ die( __('Security check', 'wp_all_export_plugin') );
120
+ } else {
121
+
122
+ $is_secure_import = PMXE_Plugin::getInstance()->getOption('secure');
123
+
124
+ $id = $this->input->get('id');
125
+
126
+ $export = new PMXE_Export_Record();
127
+
128
+ $filepath = '';
129
+
130
+ if ( ! $export->getById($id)->isEmpty())
131
+ {
132
+ if($export->options['export_to'] != XmlExportEngine::EXPORT_TYPE_GOOLE_MERCHANTS && isset($_GET['google_feed'])) {
133
+ die('Unauthorized');
134
+ }
135
+ if ( ! $is_secure_import)
136
+ {
137
+ $filepath = get_attached_file($export->attch_id);
138
+ }
139
+ else
140
+ {
141
+ $filepath = wp_all_export_get_absolute_path($export->options['filepath']);
142
+ }
143
+ if ( @file_exists($filepath) )
144
+ {
145
+ switch ($export['options']['export_to'])
146
+ {
147
+ case XmlExportEngine::EXPORT_TYPE_XML:
148
+
149
+ if($export['options']['xml_template_type'] == XmlExportEngine::EXPORT_TYPE_GOOLE_MERCHANTS) {
150
+ PMXE_download::txt($filepath);
151
+ } else {
152
+ PMXE_download::xml($filepath);
153
+ }
154
+
155
+ break;
156
+ case XmlExportEngine::EXPORT_TYPE_CSV:
157
+ if (empty($export->options['export_to_sheet']) or $export->options['export_to_sheet'] == 'csv')
158
+ {
159
+ PMXE_download::csv($filepath);
160
+ }
161
+ else
162
+ {
163
+ switch ($export->options['export_to_sheet']){
164
+ case 'xls':
165
+ PMXE_download::xls($filepath);
166
+ break;
167
+ case 'xlsx':
168
+ PMXE_download::xlsx($filepath);
169
+ break;
170
+ }
171
+ }
172
+ break;
173
+
174
+ default:
175
+
176
+ break;
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
trunk/controllers/controller/admin.php ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Introduce special type for controllers which render pages inside admin area
4
+ *
5
+ * @author Pavel Kulbakin <p.kulbakin@gmail.com>
6
+ */
7
+ abstract class PMXE_Controller_Admin extends PMXE_Controller {
8
+ /**
9
+ * Admin page base url (request url without all get parameters but `page`)
10
+ * @var string
11
+ */
12
+ public $baseUrl;
13
+ /**
14
+ * Parameters which is left when baseUrl is detected
15
+ * @var array
16
+ */
17
+ public $baseUrlParamNames = array('page', 'pagenum', 'order', 'order_by', 'type', 's', 'f');
18
+ /**
19
+ * Whether controller is rendered inside wordpress page
20
+ * @var bool
21
+ */
22
+ public $isInline = false;
23
+ /**
24
+ * Constructor
25
+ */
26
+ public function __construct() {
27
+
28
+ $remove = array_diff(array_keys($_GET), $this->baseUrlParamNames);
29
+ if ($remove) {
30
+ $this->baseUrl = remove_query_arg($remove);
31
+ } else {
32
+ $this->baseUrl = $_SERVER['REQUEST_URI'];
33
+ }
34
+ parent::__construct();
35
+
36
+ // add special filter for url fields
37
+ $this->input->addFilter('pmxe_url_filter');
38
+
39
+ // enqueue required sripts and styles
40
+ global $wp_styles;
41
+ if ( ! is_a($wp_styles, 'WP_Styles'))
42
+ $wp_styles = new WP_Styles();
43
+
44
+ wp_enqueue_style('jquery-ui', PMXE_ROOT_URL . '/static/js/jquery/css/redmond/jquery-ui.css', array('media-views'));
45
+ wp_enqueue_style('jquery-tipsy', PMXE_ROOT_URL . '/static/js/jquery/css/smoothness/jquery.tipsy.css', array('media-views'));
46
+ wp_enqueue_style('pmxe-admin-style', PMXE_ROOT_URL . '/static/css/admin.css',array('media-views'), PMXE_VERSION);
47
+ wp_enqueue_style('pmxe-admin-style-ie', PMXE_ROOT_URL . '/static/css/admin-ie.css', array('media-views'));
48
+ wp_enqueue_style('jquery-select2', PMXE_ROOT_URL . '/static/js/jquery/css/select2/select2.css', array('media-views'));
49
+ wp_enqueue_style('jquery-select2', PMXE_ROOT_URL . '/static/js/jquery/css/select2/select2-bootstrap.css', array('media-views'));
50
+ wp_enqueue_style('jquery-chosen', PMXE_ROOT_URL . '/static/js/jquery/css/chosen/chosen.css', array('media-views'));
51
+ wp_enqueue_style('jquery-codemirror', PMXE_ROOT_URL . '/static/codemirror/codemirror.css', array('media-views'), PMXE_VERSION);
52
+ wp_enqueue_style('jquery-timepicker', PMXE_ROOT_URL . '/static/js/jquery/css/timepicker/jquery.timepicker.css', array('media-views'), PMXE_VERSION);
53
+ wp_enqueue_style('pmxe-angular-scss', PMXE_ROOT_URL . '/dist/styles.css', array('media-views'), PMXE_VERSION);
54
+
55
+ $wp_styles->add_data('pmxe-admin-style-ie', 'conditional', 'lte IE 7');
56
+ wp_enqueue_style('wp-pointer');
57
+
58
+ if ( version_compare(get_bloginfo('version'), '3.8-RC1') >= 0 ){
59
+ wp_enqueue_style('pmxe-admin-style-wp-3.8', PMXE_ROOT_URL . '/static/css/admin-wp-3.8.css', array('media-views'));
60
+ }
61
+
62
+ if ( version_compare(get_bloginfo('version'), '4.4') >= 0 ){
63
+ wp_enqueue_style('pmxe-admin-style-wp-4.4', PMXE_ROOT_URL . '/static/css/admin-wp-4.4.css', array('media-views'));
64
+ }
65
+
66
+ $scheme_color = get_user_option('admin_color') and is_file(PMXE_Plugin::ROOT_DIR . '/static/css/admin-colors-' . $scheme_color . '.css') or $scheme_color = 'fresh';
67
+ if (is_file(PMXE_Plugin::ROOT_DIR . '/static/css/admin-colors-' . $scheme_color . '.css')) {
68
+ wp_enqueue_style('pmxe-admin-style-color', PMXE_ROOT_URL . '/static/css/admin-colors-' . $scheme_color . '.css', array('media-views'));
69
+ }
70
+
71
+ wp_enqueue_script('jquery-ui-datepicker', PMXE_ROOT_URL . '/static/js/jquery/ui.datepicker.js', 'jquery-ui-core');
72
+ wp_enqueue_script('jquery-tipsy', PMXE_ROOT_URL . '/static/js/jquery/jquery.tipsy.js', 'jquery');
73
+ wp_enqueue_script('jquery-pmxe-nestable', PMXE_ROOT_URL . '/static/js/jquery/jquery.mjs.pmxe_nestedSortable.js', array('jquery', 'jquery-ui-dialog', 'jquery-ui-sortable', 'jquery-ui-draggable', 'jquery-ui-droppable', 'jquery-ui-tabs', 'jquery-ui-progressbar'));
74
+ wp_enqueue_script('jquery-select2', PMXE_ROOT_URL . '/static/js/jquery/select2.min.js', 'jquery');
75
+ wp_enqueue_script('jquery-ddslick', PMXE_ROOT_URL . '/static/js/jquery/jquery.ddslick.min.js', 'jquery');
76
+ wp_enqueue_script('jquery-chosen', PMXE_ROOT_URL . '/static/js/jquery/chosen.jquery.js', 'jquery');
77
+ wp_enqueue_script('jquery-codemirror', PMXE_ROOT_URL . '/static/codemirror/codemirror.js', array(), PMXE_VERSION);
78
+ wp_enqueue_script('jquery-codemirror-matchbrackets', PMXE_ROOT_URL . '/static/codemirror/matchbrackets.js', array('jquery-codemirror'), PMXE_VERSION);
79
+ wp_enqueue_script('jquery-codemirror-htmlmixed', PMXE_ROOT_URL . '/static/codemirror/htmlmixed.js', array('jquery-codemirror-matchbrackets'), PMXE_VERSION);
80
+ wp_enqueue_script('jquery-codemirror-xml', PMXE_ROOT_URL . '/static/codemirror/xml.js', array('jquery-codemirror-htmlmixed'), PMXE_VERSION);
81
+ wp_enqueue_script('jquery-codemirror-javascript', PMXE_ROOT_URL . '/static/codemirror/javascript.js', array('jquery-codemirror-xml'), PMXE_VERSION);
82
+ wp_enqueue_script('jquery-codemirror-clike', PMXE_ROOT_URL . '/static/codemirror/clike.js', array('jquery-codemirror-javascript'), PMXE_VERSION);
83
+ wp_enqueue_script('jquery-codemirror-php', PMXE_ROOT_URL . '/static/codemirror/php.js', array('jquery-codemirror-clike'), PMXE_VERSION);
84
+ wp_enqueue_script('jquery-codemirror-autorefresh', PMXE_ROOT_URL . '/static/codemirror/autorefresh.js', array('jquery-codemirror'), PMXE_VERSION);
85
+ wp_enqueue_script('jquery-timepicker', PMXE_ROOT_URL . '/static/js/jquery/jquery.timepicker.js', array('jquery'), PMXE_VERSION);
86
+
87
+ wp_enqueue_script('wp-pointer');
88
+
89
+ /* load plupload scripts */
90
+ wp_enqueue_script('pmxe-admin-script', PMXE_ROOT_URL . '/static/js/admin.js', array('jquery', 'jquery-ui-dialog', 'jquery-ui-datepicker', 'jquery-ui-draggable', 'jquery-ui-droppable', 'jquery-ui-core', 'jquery-ui-widget', 'jquery-ui-position', 'jquery-ui-autocomplete', 'updates' ), PMXE_VERSION);
91
+ wp_enqueue_script('pmxe-admin-validate-braces', PMXE_ROOT_URL . '/static/js/validate-braces.js', array('pmxe-admin-script' ), PMXE_VERSION);
92
+
93
+ if(getenv('WPAE_DEV')) {
94
+ wp_enqueue_script('pmxe-angular-app', PMXE_ROOT_URL . '/dist/app.js', array('jquery'), PMXE_VERSION);
95
+ } else {
96
+ wp_enqueue_script('pmxe-angular-app', PMXE_ROOT_URL . '/dist/app.min.js', array('jquery'), PMXE_VERSION);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * @see Controller::render()
102
+ */
103
+ protected function render($viewPath = NULL)
104
+ {
105
+ // assume template file name depending on calling function
106
+ if (is_null($viewPath)) {
107
+ $trace = debug_backtrace();
108
+ $viewPath = str_replace('_', '/', preg_replace('%^' . preg_quote(PMXE_Plugin::PREFIX, '%') . '%', '', strtolower($trace[1]['class']))) . '/' . $trace[1]['function'];
109
+ }
110
+
111
+ // render contextual help automatically
112
+ $viewHelpPath = $viewPath;
113
+ // append file extension if not specified
114
+ if ( ! preg_match('%\.php$%', $viewHelpPath)) {
115
+ $viewHelpPath .= '.php';
116
+ }
117
+ $viewHelpPath = preg_replace('%\.php$%', '-help.php', $viewHelpPath);
118
+ $fileHelpPath = PMXE_Plugin::ROOT_DIR . '/views/' . $viewHelpPath;
119
+
120
+ if (is_file($fileHelpPath)) { // there is help file defined
121
+ ob_start();
122
+ include $fileHelpPath;
123
+ add_contextual_help(PMXE_Plugin::getInstance()->getAdminCurrentScreen()->id, ob_get_clean());
124
+ }
125
+
126
+ parent::render($viewPath);
127
+ }
128
+
129
+ }
trunk/dist/app.js ADDED
@@ -0,0 +1,41342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @license AngularJS v1.6.6
3
+ * (c) 2010-2017 Google, Inc. http://angularjs.org
4
+ * License: MIT
5
+ */
6
+ (function(window) {'use strict';
7
+
8
+ /* exported
9
+ minErrConfig,
10
+ errorHandlingConfig,
11
+ isValidObjectMaxDepth
12
+ */
13
+
14
+ var minErrConfig = {
15
+ objectMaxDepth: 5
16
+ };
17
+
18
+ /**
19
+ * @ngdoc function
20
+ * @name angular.errorHandlingConfig
21
+ * @module ng
22
+ * @kind function
23
+ *
24
+ * @description
25
+ * Configure several aspects of error handling in AngularJS if used as a setter or return the
26
+ * current configuration if used as a getter. The following options are supported:
27
+ *
28
+ * - **objectMaxDepth**: The maximum depth to which objects are traversed when stringified for error messages.
29
+ *
30
+ * Omitted or undefined options will leave the corresponding configuration values unchanged.
31
+ *
32
+ * @param {Object=} config - The configuration object. May only contain the options that need to be
33
+ * updated. Supported keys:
34
+ *
35
+ * * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a
36
+ * non-positive or non-numeric value, removes the max depth limit.
37
+ * Default: 5
38
+ */
39
+ function errorHandlingConfig(config) {
40
+ if (isObject(config)) {
41
+ if (isDefined(config.objectMaxDepth)) {
42
+ minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN;
43
+ }
44
+ } else {
45
+ return minErrConfig;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * @private
51
+ * @param {Number} maxDepth
52
+ * @return {boolean}
53
+ */
54
+ function isValidObjectMaxDepth(maxDepth) {
55
+ return isNumber(maxDepth) && maxDepth > 0;
56
+ }
57
+
58
+ /**
59
+ * @description
60
+ *
61
+ * This object provides a utility for producing rich Error messages within
62
+ * Angular. It can be called as follows:
63
+ *
64
+ * var exampleMinErr = minErr('example');
65
+ * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
66
+ *
67
+ * The above creates an instance of minErr in the example namespace. The
68
+ * resulting error will have a namespaced error code of example.one. The
69
+ * resulting error will replace {0} with the value of foo, and {1} with the
70
+ * value of bar. The object is not restricted in the number of arguments it can
71
+ * take.
72
+ *
73
+ * If fewer arguments are specified than necessary for interpolation, the extra
74
+ * interpolation markers will be preserved in the final string.
75
+ *
76
+ * Since data will be parsed statically during a build step, some restrictions
77
+ * are applied with respect to how minErr instances are created and called.
78
+ * Instances should have names of the form namespaceMinErr for a minErr created
79
+ * using minErr('namespace') . Error codes, namespaces and template strings
80
+ * should all be static strings, not variables or general expressions.
81
+ *
82
+ * @param {string} module The namespace to use for the new minErr instance.
83
+ * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
84
+ * error from returned function, for cases when a particular type of error is useful.
85
+ * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
86
+ */
87
+
88
+ function minErr(module, ErrorConstructor) {
89
+ ErrorConstructor = ErrorConstructor || Error;
90
+ return function() {
91
+ var code = arguments[0],
92
+ template = arguments[1],
93
+ message = '[' + (module ? module + ':' : '') + code + '] ',
94
+ templateArgs = sliceArgs(arguments, 2).map(function(arg) {
95
+ return toDebugString(arg, minErrConfig.objectMaxDepth);
96
+ }),
97
+ paramPrefix, i;
98
+
99
+ message += template.replace(/\{\d+\}/g, function(match) {
100
+ var index = +match.slice(1, -1);
101
+
102
+ if (index < templateArgs.length) {
103
+ return templateArgs[index];
104
+ }
105
+
106
+ return match;
107
+ });
108
+
109
+ message += '\nhttp://errors.angularjs.org/1.6.6/' +
110
+ (module ? module + '/' : '') + code;
111
+
112
+ for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
113
+ message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
114
+ }
115
+
116
+ return new ErrorConstructor(message);
117
+ };
118
+ }
119
+
120
+ /* We need to tell ESLint what variables are being exported */
121
+ /* exported
122
+ angular,
123
+ msie,
124
+ jqLite,
125
+ jQuery,
126
+ slice,
127
+ splice,
128
+ push,
129
+ toString,
130
+ minErrConfig,
131
+ errorHandlingConfig,
132
+ isValidObjectMaxDepth,
133
+ ngMinErr,
134
+ angularModule,
135
+ uid,
136
+ REGEX_STRING_REGEXP,
137
+ VALIDITY_STATE_PROPERTY,
138
+
139
+ lowercase,
140
+ uppercase,
141
+ manualLowercase,
142
+ manualUppercase,
143
+ nodeName_,
144
+ isArrayLike,
145
+ forEach,
146
+ forEachSorted,
147
+ reverseParams,
148
+ nextUid,
149
+ setHashKey,
150
+ extend,
151
+ toInt,
152
+ inherit,
153
+ merge,
154
+ noop,
155
+ identity,
156
+ valueFn,
157
+ isUndefined,
158
+ isDefined,
159
+ isObject,
160
+ isBlankObject,
161
+ isString,
162
+ isNumber,
163
+ isNumberNaN,
164
+ isDate,
165
+ isError,
166
+ isArray,
167
+ isFunction,
168
+ isRegExp,
169
+ isWindow,
170
+ isScope,
171
+ isFile,
172
+ isFormData,
173
+ isBlob,
174
+ isBoolean,
175
+ isPromiseLike,
176
+ trim,
177
+ escapeForRegexp,
178
+ isElement,
179
+ makeMap,
180
+ includes,
181
+ arrayRemove,
182
+ copy,
183
+ simpleCompare,
184
+ equals,
185
+ csp,
186
+ jq,
187
+ concat,
188
+ sliceArgs,
189
+ bind,
190
+ toJsonReplacer,
191
+ toJson,
192
+ fromJson,
193
+ convertTimezoneToLocal,
194
+ timezoneToOffset,
195
+ startingTag,
196
+ tryDecodeURIComponent,
197
+ parseKeyValue,
198
+ toKeyValue,
199
+ encodeUriSegment,
200
+ encodeUriQuery,
201
+ angularInit,
202
+ bootstrap,
203
+ getTestability,
204
+ snake_case,
205
+ bindJQuery,
206
+ assertArg,
207
+ assertArgFn,
208
+ assertNotHasOwnProperty,
209
+ getter,
210
+ getBlockNodes,
211
+ hasOwnProperty,
212
+ createMap,
213
+ stringify,
214
+
215
+ NODE_TYPE_ELEMENT,
216
+ NODE_TYPE_ATTRIBUTE,
217
+ NODE_TYPE_TEXT,
218
+ NODE_TYPE_COMMENT,
219
+ NODE_TYPE_DOCUMENT,
220
+ NODE_TYPE_DOCUMENT_FRAGMENT
221
+ */
222
+
223
+ ////////////////////////////////////
224
+
225
+ /**
226
+ * @ngdoc module
227
+ * @name ng
228
+ * @module ng
229
+ * @installation
230
+ * @description
231
+ *
232
+ * # ng (core module)
233
+ * The ng module is loaded by default when an AngularJS application is started. The module itself
234
+ * contains the essential components for an AngularJS application to function. The table below
235
+ * lists a high level breakdown of each of the services/factories, filters, directives and testing
236
+ * components available within this core module.
237
+ *
238
+ * <div doc-module-components="ng"></div>
239
+ */
240
+
241
+ var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
242
+
243
+ // The name of a form control's ValidityState property.
244
+ // This is used so that it's possible for internal tests to create mock ValidityStates.
245
+ var VALIDITY_STATE_PROPERTY = 'validity';
246
+
247
+
248
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
249
+
250
+ /**
251
+ * @ngdoc function
252
+ * @name angular.lowercase
253
+ * @module ng
254
+ * @kind function
255
+ *
256
+ * @deprecated
257
+ * sinceVersion="1.5.0"
258
+ * removeVersion="1.7.0"
259
+ * Use [String.prototype.toLowerCase](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase) instead.
260
+ *
261
+ * @description Converts the specified string to lowercase.
262
+ * @param {string} string String to be converted to lowercase.
263
+ * @returns {string} Lowercased string.
264
+ */
265
+ var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
266
+
267
+ /**
268
+ * @ngdoc function
269
+ * @name angular.uppercase
270
+ * @module ng
271
+ * @kind function
272
+ *
273
+ * @deprecated
274
+ * sinceVersion="1.5.0"
275
+ * removeVersion="1.7.0"
276
+ * Use [String.prototype.toUpperCase](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) instead.
277
+ *
278
+ * @description Converts the specified string to uppercase.
279
+ * @param {string} string String to be converted to uppercase.
280
+ * @returns {string} Uppercased string.
281
+ */
282
+ var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
283
+
284
+
285
+ var manualLowercase = function(s) {
286
+ /* eslint-disable no-bitwise */
287
+ return isString(s)
288
+ ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
289
+ : s;
290
+ /* eslint-enable */
291
+ };
292
+ var manualUppercase = function(s) {
293
+ /* eslint-disable no-bitwise */
294
+ return isString(s)
295
+ ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
296
+ : s;
297
+ /* eslint-enable */
298
+ };
299
+
300
+
301
+ // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
302
+ // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
303
+ // with correct but slower alternatives. See https://github.com/angular/angular.js/issues/11387
304
+ if ('i' !== 'I'.toLowerCase()) {
305
+ lowercase = manualLowercase;
306
+ uppercase = manualUppercase;
307
+ }
308
+
309
+
310
+ var
311
+ msie, // holds major version number for IE, or NaN if UA is not IE.
312
+ jqLite, // delay binding since jQuery could be loaded after us.
313
+ jQuery, // delay binding
314
+ slice = [].slice,
315
+ splice = [].splice,
316
+ push = [].push,
317
+ toString = Object.prototype.toString,
318
+ getPrototypeOf = Object.getPrototypeOf,
319
+ ngMinErr = minErr('ng'),
320
+
321
+ /** @name angular */
322
+ angular = window.angular || (window.angular = {}),
323
+ angularModule,
324
+ uid = 0;
325
+
326
+ // Support: IE 9-11 only
327
+ /**
328
+ * documentMode is an IE-only property
329
+ * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
330
+ */
331
+ msie = window.document.documentMode;
332
+
333
+
334
+ /**
335
+ * @private
336
+ * @param {*} obj
337
+ * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
338
+ * String ...)
339
+ */
340
+ function isArrayLike(obj) {
341
+
342
+ // `null`, `undefined` and `window` are not array-like
343
+ if (obj == null || isWindow(obj)) return false;
344
+
345
+ // arrays, strings and jQuery/jqLite objects are array like
346
+ // * jqLite is either the jQuery or jqLite constructor function
347
+ // * we have to check the existence of jqLite first as this method is called
348
+ // via the forEach method when constructing the jqLite object in the first place
349
+ if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
350
+
351
+ // Support: iOS 8.2 (not reproducible in simulator)
352
+ // "length" in obj used to prevent JIT error (gh-11508)
353
+ var length = 'length' in Object(obj) && obj.length;
354
+
355
+ // NodeList objects (with `item` method) and
356
+ // other objects with suitable length characteristics are array-like
357
+ return isNumber(length) &&
358
+ (length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item === 'function');
359
+
360
+ }
361
+
362
+ /**
363
+ * @ngdoc function
364
+ * @name angular.forEach
365
+ * @module ng
366
+ * @kind function
367
+ *
368
+ * @description
369
+ * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
370
+ * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
371
+ * is the value of an object property or an array element, `key` is the object property key or
372
+ * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
373
+ *
374
+ * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
375
+ * using the `hasOwnProperty` method.
376
+ *
377
+ * Unlike ES262's
378
+ * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
379
+ * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
380
+ * return the value provided.
381
+ *
382
+ ```js
383
+ var values = {name: 'misko', gender: 'male'};
384
+ var log = [];
385
+ angular.forEach(values, function(value, key) {
386
+ this.push(key + ': ' + value);
387
+ }, log);
388
+ expect(log).toEqual(['name: misko', 'gender: male']);
389
+ ```
390
+ *
391
+ * @param {Object|Array} obj Object to iterate over.
392
+ * @param {Function} iterator Iterator function.
393
+ * @param {Object=} context Object to become context (`this`) for the iterator function.
394
+ * @returns {Object|Array} Reference to `obj`.
395
+ */
396
+
397
+ function forEach(obj, iterator, context) {
398
+ var key, length;
399
+ if (obj) {
400
+ if (isFunction(obj)) {
401
+ for (key in obj) {
402
+ if (key !== 'prototype' && key !== 'length' && key !== 'name' && obj.hasOwnProperty(key)) {
403
+ iterator.call(context, obj[key], key, obj);
404
+ }
405
+ }
406
+ } else if (isArray(obj) || isArrayLike(obj)) {
407
+ var isPrimitive = typeof obj !== 'object';
408
+ for (key = 0, length = obj.length; key < length; key++) {
409
+ if (isPrimitive || key in obj) {
410
+ iterator.call(context, obj[key], key, obj);
411
+ }
412
+ }
413
+ } else if (obj.forEach && obj.forEach !== forEach) {
414
+ obj.forEach(iterator, context, obj);
415
+ } else if (isBlankObject(obj)) {
416
+ // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
417
+ for (key in obj) {
418
+ iterator.call(context, obj[key], key, obj);
419
+ }
420
+ } else if (typeof obj.hasOwnProperty === 'function') {
421
+ // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
422
+ for (key in obj) {
423
+ if (obj.hasOwnProperty(key)) {
424
+ iterator.call(context, obj[key], key, obj);
425
+ }
426
+ }
427
+ } else {
428
+ // Slow path for objects which do not have a method `hasOwnProperty`
429
+ for (key in obj) {
430
+ if (hasOwnProperty.call(obj, key)) {
431
+ iterator.call(context, obj[key], key, obj);
432
+ }
433
+ }
434
+ }
435
+ }
436
+ return obj;
437
+ }
438
+
439
+ function forEachSorted(obj, iterator, context) {
440
+ var keys = Object.keys(obj).sort();
441
+ for (var i = 0; i < keys.length; i++) {
442
+ iterator.call(context, obj[keys[i]], keys[i]);
443
+ }
444
+ return keys;
445
+ }
446
+
447
+
448
+ /**
449
+ * when using forEach the params are value, key, but it is often useful to have key, value.
450
+ * @param {function(string, *)} iteratorFn
451
+ * @returns {function(*, string)}
452
+ */
453
+ function reverseParams(iteratorFn) {
454
+ return function(value, key) {iteratorFn(key, value);};
455
+ }
456
+
457
+ /**
458
+ * A consistent way of creating unique IDs in angular.
459
+ *
460
+ * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
461
+ * we hit number precision issues in JavaScript.
462
+ *
463
+ * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
464
+ *
465
+ * @returns {number} an unique alpha-numeric string
466
+ */
467
+ function nextUid() {
468
+ return ++uid;
469
+ }
470
+
471
+
472
+ /**
473
+ * Set or clear the hashkey for an object.
474
+ * @param obj object
475
+ * @param h the hashkey (!truthy to delete the hashkey)
476
+ */
477
+ function setHashKey(obj, h) {
478
+ if (h) {
479
+ obj.$$hashKey = h;
480
+ } else {
481
+ delete obj.$$hashKey;
482
+ }
483
+ }
484
+
485
+
486
+ function baseExtend(dst, objs, deep) {
487
+ var h = dst.$$hashKey;
488
+
489
+ for (var i = 0, ii = objs.length; i < ii; ++i) {
490
+ var obj = objs[i];
491
+ if (!isObject(obj) && !isFunction(obj)) continue;
492
+ var keys = Object.keys(obj);
493
+ for (var j = 0, jj = keys.length; j < jj; j++) {
494
+ var key = keys[j];
495
+ var src = obj[key];
496
+
497
+ if (deep && isObject(src)) {
498
+ if (isDate(src)) {
499
+ dst[key] = new Date(src.valueOf());
500
+ } else if (isRegExp(src)) {
501
+ dst[key] = new RegExp(src);
502
+ } else if (src.nodeName) {
503
+ dst[key] = src.cloneNode(true);
504
+ } else if (isElement(src)) {
505
+ dst[key] = src.clone();
506
+ } else {
507
+ if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
508
+ baseExtend(dst[key], [src], true);
509
+ }
510
+ } else {
511
+ dst[key] = src;
512
+ }
513
+ }
514
+ }
515
+
516
+ setHashKey(dst, h);
517
+ return dst;
518
+ }
519
+
520
+ /**
521
+ * @ngdoc function
522
+ * @name angular.extend
523
+ * @module ng
524
+ * @kind function
525
+ *
526
+ * @description
527
+ * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
528
+ * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
529
+ * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
530
+ *
531
+ * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
532
+ * {@link angular.merge} for this.
533
+ *
534
+ * @param {Object} dst Destination object.
535
+ * @param {...Object} src Source object(s).
536
+ * @returns {Object} Reference to `dst`.
537
+ */
538
+ function extend(dst) {
539
+ return baseExtend(dst, slice.call(arguments, 1), false);
540
+ }
541
+
542
+
543
+ /**
544
+ * @ngdoc function
545
+ * @name angular.merge
546
+ * @module ng
547
+ * @kind function
548
+ *
549
+ * @description
550
+ * Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
551
+ * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
552
+ * by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
553
+ *
554
+ * Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
555
+ * objects, performing a deep copy.
556
+ *
557
+ * @deprecated
558
+ * sinceVersion="1.6.5"
559
+ * This function is deprecated, but will not be removed in the 1.x lifecycle.
560
+ * There are edge cases (see {@link angular.merge#known-issues known issues}) that are not
561
+ * supported by this function. We suggest
562
+ * using [lodash's merge()](https://lodash.com/docs/4.17.4#merge) instead.
563
+ *
564
+ * @knownIssue
565
+ * This is a list of (known) object types that are not handled correctly by this function:
566
+ * - [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob)
567
+ * - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream)
568
+ * - [`CanvasGradient`](https://developer.mozilla.org/docs/Web/API/CanvasGradient)
569
+ * - AngularJS {@link $rootScope.Scope scopes};
570
+ *
571
+ * @param {Object} dst Destination object.
572
+ * @param {...Object} src Source object(s).
573
+ * @returns {Object} Reference to `dst`.
574
+ */
575
+ function merge(dst) {
576
+ return baseExtend(dst, slice.call(arguments, 1), true);
577
+ }
578
+
579
+
580
+
581
+ function toInt(str) {
582
+ return parseInt(str, 10);
583
+ }
584
+
585
+ var isNumberNaN = Number.isNaN || function isNumberNaN(num) {
586
+ // eslint-disable-next-line no-self-compare
587
+ return num !== num;
588
+ };
589
+
590
+
591
+ function inherit(parent, extra) {
592
+ return extend(Object.create(parent), extra);
593
+ }
594
+
595
+ /**
596
+ * @ngdoc function
597
+ * @name angular.noop
598
+ * @module ng
599
+ * @kind function
600
+ *
601
+ * @description
602
+ * A function that performs no operations. This function can be useful when writing code in the
603
+ * functional style.
604
+ ```js
605
+ function foo(callback) {
606
+ var result = calculateResult();
607
+ (callback || angular.noop)(result);
608
+ }
609
+ ```
610
+ */
611
+ function noop() {}
612
+ noop.$inject = [];
613
+
614
+
615
+ /**
616
+ * @ngdoc function
617
+ * @name angular.identity
618
+ * @module ng
619
+ * @kind function
620
+ *
621
+ * @description
622
+ * A function that returns its first argument. This function is useful when writing code in the
623
+ * functional style.
624
+ *
625
+ ```js
626
+ function transformer(transformationFn, value) {
627
+ return (transformationFn || angular.identity)(value);
628
+ };
629
+
630
+ // E.g.
631
+ function getResult(fn, input) {
632
+ return (fn || angular.identity)(input);
633
+ };
634
+
635
+ getResult(function(n) { return n * 2; }, 21); // returns 42
636
+ getResult(null, 21); // returns 21
637
+ getResult(undefined, 21); // returns 21
638
+ ```
639
+ *
640
+ * @param {*} value to be returned.
641
+ * @returns {*} the value passed in.
642
+ */
643
+ function identity($) {return $;}
644
+ identity.$inject = [];
645
+
646
+
647
+ function valueFn(value) {return function valueRef() {return value;};}
648
+
649
+ function hasCustomToString(obj) {
650
+ return isFunction(obj.toString) && obj.toString !== toString;
651
+ }
652
+
653
+
654
+ /**
655
+ * @ngdoc function
656
+ * @name angular.isUndefined
657
+ * @module ng
658
+ * @kind function
659
+ *
660
+ * @description
661
+ * Determines if a reference is undefined.
662
+ *
663
+ * @param {*} value Reference to check.
664
+ * @returns {boolean} True if `value` is undefined.
665
+ */
666
+ function isUndefined(value) {return typeof value === 'undefined';}
667
+
668
+
669
+ /**
670
+ * @ngdoc function
671
+ * @name angular.isDefined
672
+ * @module ng
673
+ * @kind function
674
+ *
675
+ * @description
676
+ * Determines if a reference is defined.
677
+ *
678
+ * @param {*} value Reference to check.
679
+ * @returns {boolean} True if `value` is defined.
680
+ */
681
+ function isDefined(value) {return typeof value !== 'undefined';}
682
+
683
+
684
+ /**
685
+ * @ngdoc function
686
+ * @name angular.isObject
687
+ * @module ng
688
+ * @kind function
689
+ *
690
+ * @description
691
+ * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
692
+ * considered to be objects. Note that JavaScript arrays are objects.
693
+ *
694
+ * @param {*} value Reference to check.
695
+ * @returns {boolean} True if `value` is an `Object` but not `null`.
696
+ */
697
+ function isObject(value) {
698
+ // http://jsperf.com/isobject4
699
+ return value !== null && typeof value === 'object';
700
+ }
701
+
702
+
703
+ /**
704
+ * Determine if a value is an object with a null prototype
705
+ *
706
+ * @returns {boolean} True if `value` is an `Object` with a null prototype
707
+ */
708
+ function isBlankObject(value) {
709
+ return value !== null && typeof value === 'object' && !getPrototypeOf(value);
710
+ }
711
+
712
+
713
+ /**
714
+ * @ngdoc function
715
+ * @name angular.isString
716
+ * @module ng
717
+ * @kind function
718
+ *
719
+ * @description
720
+ * Determines if a reference is a `String`.
721
+ *
722
+ * @param {*} value Reference to check.
723
+ * @returns {boolean} True if `value` is a `String`.
724
+ */
725
+ function isString(value) {return typeof value === 'string';}
726
+
727
+
728
+ /**
729
+ * @ngdoc function
730
+ * @name angular.isNumber
731
+ * @module ng
732
+ * @kind function
733
+ *
734
+ * @description
735
+ * Determines if a reference is a `Number`.
736
+ *
737
+ * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
738
+ *
739
+ * If you wish to exclude these then you can use the native
740
+ * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
741
+ * method.
742
+ *
743
+ * @param {*} value Reference to check.
744
+ * @returns {boolean} True if `value` is a `Number`.
745
+ */
746
+ function isNumber(value) {return typeof value === 'number';}
747
+
748
+
749
+ /**
750
+ * @ngdoc function
751
+ * @name angular.isDate
752
+ * @module ng
753
+ * @kind function
754
+ *
755
+ * @description
756
+ * Determines if a value is a date.
757
+ *
758
+ * @param {*} value Reference to check.
759
+ * @returns {boolean} True if `value` is a `Date`.
760
+ */
761
+ function isDate(value) {
762
+ return toString.call(value) === '[object Date]';
763
+ }
764
+
765
+
766
+ /**
767
+ * @ngdoc function
768
+ * @name angular.isArray
769
+ * @module ng
770
+ * @kind function
771
+ *
772
+ * @description
773
+ * Determines if a reference is an `Array`. Alias of Array.isArray.
774
+ *
775
+ * @param {*} value Reference to check.
776
+ * @returns {boolean} True if `value` is an `Array`.
777
+ */
778
+ var isArray = Array.isArray;
779
+
780
+ /**
781
+ * @description
782
+ * Determines if a reference is an `Error`.
783
+ * Loosely based on https://www.npmjs.com/package/iserror
784
+ *
785
+ * @param {*} value Reference to check.
786
+ * @returns {boolean} True if `value` is an `Error`.
787
+ */
788
+ function isError(value) {
789
+ var tag = toString.call(value);
790
+ switch (tag) {
791
+ case '[object Error]': return true;
792
+ case '[object Exception]': return true;
793
+ case '[object DOMException]': return true;
794
+ default: return value instanceof Error;
795
+ }
796
+ }
797
+
798
+ /**
799
+ * @ngdoc function
800
+ * @name angular.isFunction
801
+ * @module ng
802
+ * @kind function
803
+ *
804
+ * @description
805
+ * Determines if a reference is a `Function`.
806
+ *
807
+ * @param {*} value Reference to check.
808
+ * @returns {boolean} True if `value` is a `Function`.
809
+ */
810
+ function isFunction(value) {return typeof value === 'function';}
811
+
812
+
813
+ /**
814
+ * Determines if a value is a regular expression object.
815
+ *
816
+ * @private
817
+ * @param {*} value Reference to check.
818
+ * @returns {boolean} True if `value` is a `RegExp`.
819
+ */
820
+ function isRegExp(value) {
821
+ return toString.call(value) === '[object RegExp]';
822
+ }
823
+
824
+
825
+ /**
826
+ * Checks if `obj` is a window object.
827
+ *
828
+ * @private
829
+ * @param {*} obj Object to check
830
+ * @returns {boolean} True if `obj` is a window obj.
831
+ */
832
+ function isWindow(obj) {
833
+ return obj && obj.window === obj;
834
+ }
835
+
836
+
837
+ function isScope(obj) {
838
+ return obj && obj.$evalAsync && obj.$watch;
839
+ }
840
+
841
+
842
+ function isFile(obj) {
843
+ return toString.call(obj) === '[object File]';
844
+ }
845
+
846
+
847
+ function isFormData(obj) {
848
+ return toString.call(obj) === '[object FormData]';
849
+ }
850
+
851
+
852
+ function isBlob(obj) {
853
+ return toString.call(obj) === '[object Blob]';
854
+ }
855
+
856
+
857
+ function isBoolean(value) {
858
+ return typeof value === 'boolean';
859
+ }
860
+
861
+
862
+ function isPromiseLike(obj) {
863
+ return obj && isFunction(obj.then);
864
+ }
865
+
866
+
867
+ var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/;
868
+ function isTypedArray(value) {
869
+ return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
870
+ }
871
+
872
+ function isArrayBuffer(obj) {
873
+ return toString.call(obj) === '[object ArrayBuffer]';
874
+ }
875
+
876
+
877
+ var trim = function(value) {
878
+ return isString(value) ? value.trim() : value;
879
+ };
880
+
881
+ // Copied from:
882
+ // http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
883
+ // Prereq: s is a string.
884
+ var escapeForRegexp = function(s) {
885
+ return s
886
+ .replace(/([-()[\]{}+?*.$^|,:#<!\\])/g, '\\$1')
887
+ // eslint-disable-next-line no-control-regex
888
+ .replace(/\x08/g, '\\x08');
889
+ };
890
+
891
+
892
+ /**
893
+ * @ngdoc function
894
+ * @name angular.isElement
895
+ * @module ng
896
+ * @kind function
897
+ *
898
+ * @description
899
+ * Determines if a reference is a DOM element (or wrapped jQuery element).
900
+ *
901
+ * @param {*} value Reference to check.
902
+ * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
903
+ */
904
+ function isElement(node) {
905
+ return !!(node &&
906
+ (node.nodeName // We are a direct element.
907
+ || (node.prop && node.attr && node.find))); // We have an on and find method part of jQuery API.
908
+ }
909
+
910
+ /**
911
+ * @param str 'key1,key2,...'
912
+ * @returns {object} in the form of {key1:true, key2:true, ...}
913
+ */
914
+ function makeMap(str) {
915
+ var obj = {}, items = str.split(','), i;
916
+ for (i = 0; i < items.length; i++) {
917
+ obj[items[i]] = true;
918
+ }
919
+ return obj;
920
+ }
921
+
922
+
923
+ function nodeName_(element) {
924
+ return lowercase(element.nodeName || (element[0] && element[0].nodeName));
925
+ }
926
+
927
+ function includes(array, obj) {
928
+ return Array.prototype.indexOf.call(array, obj) !== -1;
929
+ }
930
+
931
+ function arrayRemove(array, value) {
932
+ var index = array.indexOf(value);
933
+ if (index >= 0) {
934
+ array.splice(index, 1);
935
+ }
936
+ return index;
937
+ }
938
+
939
+ /**
940
+ * @ngdoc function
941
+ * @name angular.copy
942
+ * @module ng
943
+ * @kind function
944
+ *
945
+ * @description
946
+ * Creates a deep copy of `source`, which should be an object or an array.
947
+ *
948
+ * * If no destination is supplied, a copy of the object or array is created.
949
+ * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
950
+ * are deleted and then all elements/properties from the source are copied to it.
951
+ * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
952
+ * * If `source` is identical to `destination` an exception will be thrown.
953
+ *
954
+ * <br />
955
+ * <div class="alert alert-warning">
956
+ * Only enumerable properties are taken into account. Non-enumerable properties (both on `source`
957
+ * and on `destination`) will be ignored.
958
+ * </div>
959
+ *
960
+ * @param {*} source The source that will be used to make a copy.
961
+ * Can be any type, including primitives, `null`, and `undefined`.
962
+ * @param {(Object|Array)=} destination Destination into which the source is copied. If
963
+ * provided, must be of the same type as `source`.
964
+ * @returns {*} The copy or updated `destination`, if `destination` was specified.
965
+ *
966
+ * @example
967
+ <example module="copyExample" name="angular-copy">
968
+ <file name="index.html">
969
+ <div ng-controller="ExampleController">
970
+ <form novalidate class="simple-form">
971
+ <label>Name: <input type="text" ng-model="user.name" /></label><br />
972
+ <label>Age: <input type="number" ng-model="user.age" /></label><br />
973
+ Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label>
974
+ <label><input type="radio" ng-model="user.gender" value="female" />female</label><br />
975
+ <button ng-click="reset()">RESET</button>
976
+ <button ng-click="update(user)">SAVE</button>
977
+ </form>
978
+ <pre>form = {{user | json}}</pre>
979
+ <pre>master = {{master | json}}</pre>
980
+ </div>
981
+ </file>
982
+ <file name="script.js">
983
+ // Module: copyExample
984
+ angular.
985
+ module('copyExample', []).
986
+ controller('ExampleController', ['$scope', function($scope) {
987
+ $scope.master = {};
988
+
989
+ $scope.reset = function() {
990
+ // Example with 1 argument
991
+ $scope.user = angular.copy($scope.master);
992
+ };
993
+
994
+ $scope.update = function(user) {
995
+ // Example with 2 arguments
996
+ angular.copy(user, $scope.master);
997
+ };
998
+
999
+ $scope.reset();
1000
+ }]);
1001
+ </file>
1002
+ </example>
1003
+ */
1004
+ function copy(source, destination, maxDepth) {
1005
+ var stackSource = [];
1006
+ var stackDest = [];
1007
+ maxDepth = isValidObjectMaxDepth(maxDepth) ? maxDepth : NaN;
1008
+
1009
+ if (destination) {
1010
+ if (isTypedArray(destination) || isArrayBuffer(destination)) {
1011
+ throw ngMinErr('cpta', 'Can\'t copy! TypedArray destination cannot be mutated.');
1012
+ }
1013
+ if (source === destination) {
1014
+ throw ngMinErr('cpi', 'Can\'t copy! Source and destination are identical.');
1015
+ }
1016
+
1017
+ // Empty the destination object
1018
+ if (isArray(destination)) {
1019
+ destination.length = 0;
1020
+ } else {
1021
+ forEach(destination, function(value, key) {
1022
+ if (key !== '$$hashKey') {
1023
+ delete destination[key];
1024
+ }
1025
+ });
1026
+ }
1027
+
1028
+ stackSource.push(source);
1029
+ stackDest.push(destination);
1030
+ return copyRecurse(source, destination, maxDepth);
1031
+ }
1032
+
1033
+ return copyElement(source, maxDepth);
1034
+
1035
+ function copyRecurse(source, destination, maxDepth) {
1036
+ maxDepth--;
1037
+ if (maxDepth < 0) {
1038
+ return '...';
1039
+ }
1040
+ var h = destination.$$hashKey;
1041
+ var key;
1042
+ if (isArray(source)) {
1043
+ for (var i = 0, ii = source.length; i < ii; i++) {
1044
+ destination.push(copyElement(source[i], maxDepth));
1045
+ }
1046
+ } else if (isBlankObject(source)) {
1047
+ // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
1048
+ for (key in source) {
1049
+ destination[key] = copyElement(source[key], maxDepth);
1050
+ }
1051
+ } else if (source && typeof source.hasOwnProperty === 'function') {
1052
+ // Slow path, which must rely on hasOwnProperty
1053
+ for (key in source) {
1054
+ if (source.hasOwnProperty(key)) {
1055
+ destination[key] = copyElement(source[key], maxDepth);
1056
+ }
1057
+ }
1058
+ } else {
1059
+ // Slowest path --- hasOwnProperty can't be called as a method
1060
+ for (key in source) {
1061
+ if (hasOwnProperty.call(source, key)) {
1062
+ destination[key] = copyElement(source[key], maxDepth);
1063
+ }
1064
+ }
1065
+ }
1066
+ setHashKey(destination, h);
1067
+ return destination;
1068
+ }
1069
+
1070
+ function copyElement(source, maxDepth) {
1071
+ // Simple values
1072
+ if (!isObject(source)) {
1073
+ return source;
1074
+ }
1075
+
1076
+ // Already copied values
1077
+ var index = stackSource.indexOf(source);
1078
+ if (index !== -1) {
1079
+ return stackDest[index];
1080
+ }
1081
+
1082
+ if (isWindow(source) || isScope(source)) {
1083
+ throw ngMinErr('cpws',
1084
+ 'Can\'t copy! Making copies of Window or Scope instances is not supported.');
1085
+ }
1086
+
1087
+ var needsRecurse = false;
1088
+ var destination = copyType(source);
1089
+
1090
+ if (destination === undefined) {
1091
+ destination = isArray(source) ? [] : Object.create(getPrototypeOf(source));
1092
+ needsRecurse = true;
1093
+ }
1094
+
1095
+ stackSource.push(source);
1096
+ stackDest.push(destination);
1097
+
1098
+ return needsRecurse
1099
+ ? copyRecurse(source, destination, maxDepth)
1100
+ : destination;
1101
+ }
1102
+
1103
+ function copyType(source) {
1104
+ switch (toString.call(source)) {
1105
+ case '[object Int8Array]':
1106
+ case '[object Int16Array]':
1107
+ case '[object Int32Array]':
1108
+ case '[object Float32Array]':
1109
+ case '[object Float64Array]':
1110
+ case '[object Uint8Array]':
1111
+ case '[object Uint8ClampedArray]':
1112
+ case '[object Uint16Array]':
1113
+ case '[object Uint32Array]':
1114
+ return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length);
1115
+
1116
+ case '[object ArrayBuffer]':
1117
+ // Support: IE10
1118
+ if (!source.slice) {
1119
+ // If we're in this case we know the environment supports ArrayBuffer
1120
+ /* eslint-disable no-undef */
1121
+ var copied = new ArrayBuffer(source.byteLength);
1122
+ new Uint8Array(copied).set(new Uint8Array(source));
1123
+ /* eslint-enable */
1124
+ return copied;
1125
+ }
1126
+ return source.slice(0);
1127
+
1128
+ case '[object Boolean]':
1129
+ case '[object Number]':
1130
+ case '[object String]':
1131
+ case '[object Date]':
1132
+ return new source.constructor(source.valueOf());
1133
+
1134
+ case '[object RegExp]':
1135
+ var re = new RegExp(source.source, source.toString().match(/[^/]*$/)[0]);
1136
+ re.lastIndex = source.lastIndex;
1137
+ return re;
1138
+
1139
+ case '[object Blob]':
1140
+ return new source.constructor([source], {type: source.type});
1141
+ }
1142
+
1143
+ if (isFunction(source.cloneNode)) {
1144
+ return source.cloneNode(true);
1145
+ }
1146
+ }
1147
+ }
1148
+
1149
+
1150
+ // eslint-disable-next-line no-self-compare
1151
+ function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }
1152
+
1153
+
1154
+ /**
1155
+ * @ngdoc function
1156
+ * @name angular.equals
1157
+ * @module ng
1158
+ * @kind function
1159
+ *
1160
+ * @description
1161
+ * Determines if two objects or two values are equivalent. Supports value types, regular
1162
+ * expressions, arrays and objects.
1163
+ *
1164
+ * Two objects or values are considered equivalent if at least one of the following is true:
1165
+ *
1166
+ * * Both objects or values pass `===` comparison.
1167
+ * * Both objects or values are of the same type and all of their properties are equal by
1168
+ * comparing them with `angular.equals`.
1169
+ * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1170
+ * * Both values represent the same regular expression (In JavaScript,
1171
+ * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1172
+ * representation matches).
1173
+ *
1174
+ * During a property comparison, properties of `function` type and properties with names
1175
+ * that begin with `$` are ignored.
1176
+ *
1177
+ * Scope and DOMWindow objects are being compared only by identify (`===`).
1178
+ *
1179
+ * @param {*} o1 Object or value to compare.
1180
+ * @param {*} o2 Object or value to compare.
1181
+ * @returns {boolean} True if arguments are equal.
1182
+ *
1183
+ * @example
1184
+ <example module="equalsExample" name="equalsExample">
1185
+ <file name="index.html">
1186
+ <div ng-controller="ExampleController">
1187
+ <form novalidate>
1188
+ <h3>User 1</h3>
1189
+ Name: <input type="text" ng-model="user1.name">
1190
+ Age: <input type="number" ng-model="user1.age">
1191
+
1192
+ <h3>User 2</h3>
1193
+ Name: <input type="text" ng-model="user2.name">
1194
+ Age: <input type="number" ng-model="user2.age">
1195
+
1196
+ <div>
1197
+ <br/>
1198
+ <input type="button" value="Compare" ng-click="compare()">
1199
+ </div>
1200
+ User 1: <pre>{{user1 | json}}</pre>
1201
+ User 2: <pre>{{user2 | json}}</pre>
1202
+ Equal: <pre>{{result}}</pre>
1203
+ </form>
1204
+ </div>
1205
+ </file>
1206
+ <file name="script.js">
1207
+ angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) {
1208
+ $scope.user1 = {};
1209
+ $scope.user2 = {};
1210
+ $scope.compare = function() {
1211
+ $scope.result = angular.equals($scope.user1, $scope.user2);
1212
+ };
1213
+ }]);
1214
+ </file>
1215
+ </example>
1216
+ */
1217
+ function equals(o1, o2) {
1218
+ if (o1 === o2) return true;
1219
+ if (o1 === null || o2 === null) return false;
1220
+ // eslint-disable-next-line no-self-compare
1221
+ if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1222
+ var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
1223
+ if (t1 === t2 && t1 === 'object') {
1224
+ if (isArray(o1)) {
1225
+ if (!isArray(o2)) return false;
1226
+ if ((length = o1.length) === o2.length) {
1227
+ for (key = 0; key < length; key++) {
1228
+ if (!equals(o1[key], o2[key])) return false;
1229
+ }
1230
+ return true;
1231
+ }
1232
+ } else if (isDate(o1)) {
1233
+ if (!isDate(o2)) return false;
1234
+ return simpleCompare(o1.getTime(), o2.getTime());
1235
+ } else if (isRegExp(o1)) {
1236
+ if (!isRegExp(o2)) return false;
1237
+ return o1.toString() === o2.toString();
1238
+ } else {
1239
+ if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1240
+ isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1241
+ keySet = createMap();
1242
+ for (key in o1) {
1243
+ if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1244
+ if (!equals(o1[key], o2[key])) return false;
1245
+ keySet[key] = true;
1246
+ }
1247
+ for (key in o2) {
1248
+ if (!(key in keySet) &&
1249
+ key.charAt(0) !== '$' &&
1250
+ isDefined(o2[key]) &&
1251
+ !isFunction(o2[key])) return false;
1252
+ }
1253
+ return true;
1254
+ }
1255
+ }
1256
+ return false;
1257
+ }
1258
+
1259
+ var csp = function() {
1260
+ if (!isDefined(csp.rules)) {
1261
+
1262
+
1263
+ var ngCspElement = (window.document.querySelector('[ng-csp]') ||
1264
+ window.document.querySelector('[data-ng-csp]'));
1265
+
1266
+ if (ngCspElement) {
1267
+ var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1268
+ ngCspElement.getAttribute('data-ng-csp');
1269
+ csp.rules = {
1270
+ noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1271
+ noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1272
+ };
1273
+ } else {
1274
+ csp.rules = {
1275
+ noUnsafeEval: noUnsafeEval(),
1276
+ noInlineStyle: false
1277
+ };
1278
+ }
1279
+ }
1280
+
1281
+ return csp.rules;
1282
+
1283
+ function noUnsafeEval() {
1284
+ try {
1285
+ // eslint-disable-next-line no-new, no-new-func
1286
+ new Function('');
1287
+ return false;
1288
+ } catch (e) {
1289
+ return true;
1290
+ }
1291
+ }
1292
+ };
1293
+
1294
+ /**
1295
+ * @ngdoc directive
1296
+ * @module ng
1297
+ * @name ngJq
1298
+ *
1299
+ * @element ANY
1300
+ * @param {string=} ngJq the name of the library available under `window`
1301
+ * to be used for angular.element
1302
+ * @description
1303
+ * Use this directive to force the angular.element library. This should be
1304
+ * used to force either jqLite by leaving ng-jq blank or setting the name of
1305
+ * the jquery variable under window (eg. jQuery).
1306
+ *
1307
+ * Since angular looks for this directive when it is loaded (doesn't wait for the
1308
+ * DOMContentLoaded event), it must be placed on an element that comes before the script
1309
+ * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1310
+ * others ignored.
1311
+ *
1312
+ * @example
1313
+ * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1314
+ ```html
1315
+ <!doctype html>
1316
+ <html ng-app ng-jq>
1317
+ ...
1318
+ ...
1319
+ </html>
1320
+ ```
1321
+ * @example
1322
+ * This example shows how to use a jQuery based library of a different name.
1323
+ * The library name must be available at the top most 'window'.
1324
+ ```html
1325
+ <!doctype html>
1326
+ <html ng-app ng-jq="jQueryLib">
1327
+ ...
1328
+ ...
1329
+ </html>
1330
+ ```
1331
+ */
1332
+ var jq = function() {
1333
+ if (isDefined(jq.name_)) return jq.name_;
1334
+ var el;
1335
+ var i, ii = ngAttrPrefixes.length, prefix, name;
1336
+ for (i = 0; i < ii; ++i) {
1337
+ prefix = ngAttrPrefixes[i];
1338
+ el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]');
1339
+ if (el) {
1340
+ name = el.getAttribute(prefix + 'jq');
1341
+ break;
1342
+ }
1343
+ }
1344
+
1345
+ return (jq.name_ = name);
1346
+ };
1347
+
1348
+ function concat(array1, array2, index) {
1349
+ return array1.concat(slice.call(array2, index));
1350
+ }
1351
+
1352
+ function sliceArgs(args, startIndex) {
1353
+ return slice.call(args, startIndex || 0);
1354
+ }
1355
+
1356
+
1357
+ /**
1358
+ * @ngdoc function
1359
+ * @name angular.bind
1360
+ * @module ng
1361
+ * @kind function
1362
+ *
1363
+ * @description
1364
+ * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1365
+ * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1366
+ * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1367
+ * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1368
+ *
1369
+ * @param {Object} self Context which `fn` should be evaluated in.
1370
+ * @param {function()} fn Function to be bound.
1371
+ * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1372
+ * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1373
+ */
1374
+ function bind(self, fn) {
1375
+ var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1376
+ if (isFunction(fn) && !(fn instanceof RegExp)) {
1377
+ return curryArgs.length
1378
+ ? function() {
1379
+ return arguments.length
1380
+ ? fn.apply(self, concat(curryArgs, arguments, 0))
1381
+ : fn.apply(self, curryArgs);
1382
+ }
1383
+ : function() {
1384
+ return arguments.length
1385
+ ? fn.apply(self, arguments)
1386
+ : fn.call(self);
1387
+ };
1388
+ } else {
1389
+ // In IE, native methods are not functions so they cannot be bound (note: they don't need to be).
1390
+ return fn;
1391
+ }
1392
+ }
1393
+
1394
+
1395
+ function toJsonReplacer(key, value) {
1396
+ var val = value;
1397
+
1398
+ if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1399
+ val = undefined;
1400
+ } else if (isWindow(value)) {
1401
+ val = '$WINDOW';
1402
+ } else if (value && window.document === value) {
1403
+ val = '$DOCUMENT';
1404
+ } else if (isScope(value)) {
1405
+ val = '$SCOPE';
1406
+ }
1407
+
1408
+ return val;
1409
+ }
1410
+
1411
+
1412
+ /**
1413
+ * @ngdoc function
1414
+ * @name angular.toJson
1415
+ * @module ng
1416
+ * @kind function
1417
+ *
1418
+ * @description
1419
+ * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1420
+ * stripped since angular uses this notation internally.
1421
+ *
1422
+ * @param {Object|Array|Date|string|number|boolean} obj Input to be serialized into JSON.
1423
+ * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1424
+ * If set to an integer, the JSON output will contain that many spaces per indentation.
1425
+ * @returns {string|undefined} JSON-ified string representing `obj`.
1426
+ * @knownIssue
1427
+ *
1428
+ * The Safari browser throws a `RangeError` instead of returning `null` when it tries to stringify a `Date`
1429
+ * object with an invalid date value. The only reliable way to prevent this is to monkeypatch the
1430
+ * `Date.prototype.toJSON` method as follows:
1431
+ *
1432
+ * ```
1433
+ * var _DatetoJSON = Date.prototype.toJSON;
1434
+ * Date.prototype.toJSON = function() {
1435
+ * try {
1436
+ * return _DatetoJSON.call(this);
1437
+ * } catch(e) {
1438
+ * if (e instanceof RangeError) {
1439
+ * return null;
1440
+ * }
1441
+ * throw e;
1442
+ * }
1443
+ * };
1444
+ * ```
1445
+ *
1446
+ * See https://github.com/angular/angular.js/pull/14221 for more information.
1447
+ */
1448
+ function toJson(obj, pretty) {
1449
+ if (isUndefined(obj)) return undefined;
1450
+ if (!isNumber(pretty)) {
1451
+ pretty = pretty ? 2 : null;
1452
+ }
1453
+ return JSON.stringify(obj, toJsonReplacer, pretty);
1454
+ }
1455
+
1456
+
1457
+ /**
1458
+ * @ngdoc function
1459
+ * @name angular.fromJson
1460
+ * @module ng
1461
+ * @kind function
1462
+ *
1463
+ * @description
1464
+ * Deserializes a JSON string.
1465
+ *
1466
+ * @param {string} json JSON string to deserialize.
1467
+ * @returns {Object|Array|string|number} Deserialized JSON string.
1468
+ */
1469
+ function fromJson(json) {
1470
+ return isString(json)
1471
+ ? JSON.parse(json)
1472
+ : json;
1473
+ }
1474
+
1475
+
1476
+ var ALL_COLONS = /:/g;
1477
+ function timezoneToOffset(timezone, fallback) {
1478
+ // Support: IE 9-11 only, Edge 13-15+
1479
+ // IE/Edge do not "understand" colon (`:`) in timezone
1480
+ timezone = timezone.replace(ALL_COLONS, '');
1481
+ var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1482
+ return isNumberNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1483
+ }
1484
+
1485
+
1486
+ function addDateMinutes(date, minutes) {
1487
+ date = new Date(date.getTime());
1488
+ date.setMinutes(date.getMinutes() + minutes);
1489
+ return date;
1490
+ }
1491
+
1492
+
1493
+ function convertTimezoneToLocal(date, timezone, reverse) {
1494
+ reverse = reverse ? -1 : 1;
1495
+ var dateTimezoneOffset = date.getTimezoneOffset();
1496
+ var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
1497
+ return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
1498
+ }
1499
+
1500
+
1501
+ /**
1502
+ * @returns {string} Returns the string representation of the element.
1503
+ */
1504
+ function startingTag(element) {
1505
+ element = jqLite(element).clone().empty();
1506
+ var elemHtml = jqLite('<div>').append(element).html();
1507
+ try {
1508
+ return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1509
+ elemHtml.
1510
+ match(/^(<[^>]+>)/)[1].
1511
+ replace(/^<([\w-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);});
1512
+ } catch (e) {
1513
+ return lowercase(elemHtml);
1514
+ }
1515
+
1516
+ }
1517
+
1518
+
1519
+ /////////////////////////////////////////////////
1520
+
1521
+ /**
1522
+ * Tries to decode the URI component without throwing an exception.
1523
+ *
1524
+ * @private
1525
+ * @param str value potential URI component to check.
1526
+ * @returns {boolean} True if `value` can be decoded
1527
+ * with the decodeURIComponent function.
1528
+ */
1529
+ function tryDecodeURIComponent(value) {
1530
+ try {
1531
+ return decodeURIComponent(value);
1532
+ } catch (e) {
1533
+ // Ignore any invalid uri component.
1534
+ }
1535
+ }
1536
+
1537
+
1538
+ /**
1539
+ * Parses an escaped url query string into key-value pairs.
1540
+ * @returns {Object.<string,boolean|Array>}
1541
+ */
1542
+ function parseKeyValue(/**string*/keyValue) {
1543
+ var obj = {};
1544
+ forEach((keyValue || '').split('&'), function(keyValue) {
1545
+ var splitPoint, key, val;
1546
+ if (keyValue) {
1547
+ key = keyValue = keyValue.replace(/\+/g,'%20');
1548
+ splitPoint = keyValue.indexOf('=');
1549
+ if (splitPoint !== -1) {
1550
+ key = keyValue.substring(0, splitPoint);
1551
+ val = keyValue.substring(splitPoint + 1);
1552
+ }
1553
+ key = tryDecodeURIComponent(key);
1554
+ if (isDefined(key)) {
1555
+ val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1556
+ if (!hasOwnProperty.call(obj, key)) {
1557
+ obj[key] = val;
1558
+ } else if (isArray(obj[key])) {
1559
+ obj[key].push(val);
1560
+ } else {
1561
+ obj[key] = [obj[key],val];
1562
+ }
1563
+ }
1564
+ }
1565
+ });
1566
+ return obj;
1567
+ }
1568
+
1569
+ function toKeyValue(obj) {
1570
+ var parts = [];
1571
+ forEach(obj, function(value, key) {
1572
+ if (isArray(value)) {
1573
+ forEach(value, function(arrayValue) {
1574
+ parts.push(encodeUriQuery(key, true) +
1575
+ (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1576
+ });
1577
+ } else {
1578
+ parts.push(encodeUriQuery(key, true) +
1579
+ (value === true ? '' : '=' + encodeUriQuery(value, true)));
1580
+ }
1581
+ });
1582
+ return parts.length ? parts.join('&') : '';
1583
+ }
1584
+
1585
+
1586
+ /**
1587
+ * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1588
+ * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1589
+ * segments:
1590
+ * segment = *pchar
1591
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1592
+ * pct-encoded = "%" HEXDIG HEXDIG
1593
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1594
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1595
+ * / "*" / "+" / "," / ";" / "="
1596
+ */
1597
+ function encodeUriSegment(val) {
1598
+ return encodeUriQuery(val, true).
1599
+ replace(/%26/gi, '&').
1600
+ replace(/%3D/gi, '=').
1601
+ replace(/%2B/gi, '+');
1602
+ }
1603
+
1604
+
1605
+ /**
1606
+ * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1607
+ * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1608
+ * encoded per http://tools.ietf.org/html/rfc3986:
1609
+ * query = *( pchar / "/" / "?" )
1610
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1611
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1612
+ * pct-encoded = "%" HEXDIG HEXDIG
1613
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1614
+ * / "*" / "+" / "," / ";" / "="
1615
+ */
1616
+ function encodeUriQuery(val, pctEncodeSpaces) {
1617
+ return encodeURIComponent(val).
1618
+ replace(/%40/gi, '@').
1619
+ replace(/%3A/gi, ':').
1620
+ replace(/%24/g, '$').
1621
+ replace(/%2C/gi, ',').
1622
+ replace(/%3B/gi, ';').
1623
+ replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1624
+ }
1625
+
1626
+ var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1627
+
1628
+ function getNgAttribute(element, ngAttr) {
1629
+ var attr, i, ii = ngAttrPrefixes.length;
1630
+ for (i = 0; i < ii; ++i) {
1631
+ attr = ngAttrPrefixes[i] + ngAttr;
1632
+ if (isString(attr = element.getAttribute(attr))) {
1633
+ return attr;
1634
+ }
1635
+ }
1636
+ return null;
1637
+ }
1638
+
1639
+ function allowAutoBootstrap(document) {
1640
+ var script = document.currentScript;
1641
+
1642
+ if (!script) {
1643
+ // Support: IE 9-11 only
1644
+ // IE does not have `document.currentScript`
1645
+ return true;
1646
+ }
1647
+
1648
+ // If the `currentScript` property has been clobbered just return false, since this indicates a probable attack
1649
+ if (!(script instanceof window.HTMLScriptElement || script instanceof window.SVGScriptElement)) {
1650
+ return false;
1651
+ }
1652
+
1653
+ var attributes = script.attributes;
1654
+ var srcs = [attributes.getNamedItem('src'), attributes.getNamedItem('href'), attributes.getNamedItem('xlink:href')];
1655
+
1656
+ return srcs.every(function(src) {
1657
+ if (!src) {
1658
+ return true;
1659
+ }
1660
+ if (!src.value) {
1661
+ return false;
1662
+ }
1663
+
1664
+ var link = document.createElement('a');
1665
+ link.href = src.value;
1666
+
1667
+ if (document.location.origin === link.origin) {
1668
+ // Same-origin resources are always allowed, even for non-whitelisted schemes.
1669
+ return true;
1670
+ }
1671
+ // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web.
1672
+ // This is to prevent angular.js bundled with browser extensions from being used to bypass the
1673
+ // content security policy in web pages and other browser extensions.
1674
+ switch (link.protocol) {
1675
+ case 'http:':
1676
+ case 'https:':
1677
+ case 'ftp:':
1678
+ case 'blob:':
1679
+ case 'file:':
1680
+ case 'data:':
1681
+ return true;
1682
+ default:
1683
+ return false;
1684
+ }
1685
+ });
1686
+ }
1687
+
1688
+ // Cached as it has to run during loading so that document.currentScript is available.
1689
+ var isAutoBootstrapAllowed = allowAutoBootstrap(window.document);
1690
+
1691
+ /**
1692
+ * @ngdoc directive
1693
+ * @name ngApp
1694
+ * @module ng
1695
+ *
1696
+ * @element ANY
1697
+ * @param {angular.Module} ngApp an optional application
1698
+ * {@link angular.module module} name to load.
1699
+ * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1700
+ * created in "strict-di" mode. This means that the application will fail to invoke functions which
1701
+ * do not use explicit function annotation (and are thus unsuitable for minification), as described
1702
+ * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1703
+ * tracking down the root of these bugs.
1704
+ *
1705
+ * @description
1706
+ *
1707
+ * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1708
+ * designates the **root element** of the application and is typically placed near the root element
1709
+ * of the page - e.g. on the `<body>` or `<html>` tags.
1710
+ *
1711
+ * There are a few things to keep in mind when using `ngApp`:
1712
+ * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1713
+ * found in the document will be used to define the root element to auto-bootstrap as an
1714
+ * application. To run multiple applications in an HTML document you must manually bootstrap them using
1715
+ * {@link angular.bootstrap} instead.
1716
+ * - AngularJS applications cannot be nested within each other.
1717
+ * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`.
1718
+ * This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and
1719
+ * {@link ngRoute.ngView `ngView`}.
1720
+ * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
1721
+ * causing animations to stop working and making the injector inaccessible from outside the app.
1722
+ *
1723
+ * You can specify an **AngularJS module** to be used as the root module for the application. This
1724
+ * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1725
+ * should contain the application code needed or have dependencies on other modules that will
1726
+ * contain the code. See {@link angular.module} for more information.
1727
+ *
1728
+ * In the example below if the `ngApp` directive were not placed on the `html` element then the
1729
+ * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1730
+ * would not be resolved to `3`.
1731
+ *
1732
+ * `ngApp` is the easiest, and most common way to bootstrap an application.
1733
+ *
1734
+ <example module="ngAppDemo" name="ng-app">
1735
+ <file name="index.html">
1736
+ <div ng-controller="ngAppDemoController">
1737
+ I can add: {{a}} + {{b}} = {{ a+b }}
1738
+ </div>
1739
+ </file>
1740
+ <file name="script.js">
1741
+ angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1742
+ $scope.a = 1;
1743
+ $scope.b = 2;
1744
+ });
1745
+ </file>
1746
+ </example>
1747
+ *
1748
+ * Using `ngStrictDi`, you would see something like this:
1749
+ *
1750
+ <example ng-app-included="true" name="strict-di">
1751
+ <file name="index.html">
1752
+ <div ng-app="ngAppStrictDemo" ng-strict-di>
1753
+ <div ng-controller="GoodController1">
1754
+ I can add: {{a}} + {{b}} = {{ a+b }}
1755
+
1756
+ <p>This renders because the controller does not fail to
1757
+ instantiate, by using explicit annotation style (see
1758
+ script.js for details)
1759
+ </p>
1760
+ </div>
1761
+
1762
+ <div ng-controller="GoodController2">
1763
+ Name: <input ng-model="name"><br />
1764
+ Hello, {{name}}!
1765
+
1766
+ <p>This renders because the controller does not fail to
1767
+ instantiate, by using explicit annotation style
1768
+ (see script.js for details)
1769
+ </p>
1770
+ </div>
1771
+
1772
+ <div ng-controller="BadController">
1773
+ I can add: {{a}} + {{b}} = {{ a+b }}
1774
+
1775
+ <p>The controller could not be instantiated, due to relying
1776
+ on automatic function annotations (which are disabled in
1777
+ strict mode). As such, the content of this section is not
1778
+ interpolated, and there should be an error in your web console.
1779
+ </p>
1780
+ </div>
1781
+ </div>
1782
+ </file>
1783
+ <file name="script.js">
1784
+ angular.module('ngAppStrictDemo', [])
1785
+ // BadController will fail to instantiate, due to relying on automatic function annotation,
1786
+ // rather than an explicit annotation
1787
+ .controller('BadController', function($scope) {
1788
+ $scope.a = 1;
1789
+ $scope.b = 2;
1790
+ })
1791
+ // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1792
+ // due to using explicit annotations using the array style and $inject property, respectively.
1793
+ .controller('GoodController1', ['$scope', function($scope) {
1794
+ $scope.a = 1;
1795
+ $scope.b = 2;
1796
+ }])
1797
+ .controller('GoodController2', GoodController2);
1798
+ function GoodController2($scope) {
1799
+ $scope.name = 'World';
1800
+ }
1801
+ GoodController2.$inject = ['$scope'];
1802
+ </file>
1803
+ <file name="style.css">
1804
+ div[ng-controller] {
1805
+ margin-bottom: 1em;
1806
+ -webkit-border-radius: 4px;
1807
+ border-radius: 4px;
1808
+ border: 1px solid;
1809
+ padding: .5em;
1810
+ }
1811
+ div[ng-controller^=Good] {
1812
+ border-color: #d6e9c6;
1813
+ background-color: #dff0d8;
1814
+ color: #3c763d;
1815
+ }
1816
+ div[ng-controller^=Bad] {
1817
+ border-color: #ebccd1;
1818
+ background-color: #f2dede;
1819
+ color: #a94442;
1820
+ margin-bottom: 0;
1821
+ }
1822
+ </file>
1823
+ </example>
1824
+ */
1825
+ function angularInit(element, bootstrap) {
1826
+ var appElement,
1827
+ module,
1828
+ config = {};
1829
+
1830
+ // The element `element` has priority over any other element.
1831
+ forEach(ngAttrPrefixes, function(prefix) {
1832
+ var name = prefix + 'app';
1833
+
1834
+ if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1835
+ appElement = element;
1836
+ module = element.getAttribute(name);
1837
+ }
1838
+ });
1839
+ forEach(ngAttrPrefixes, function(prefix) {
1840
+ var name = prefix + 'app';
1841
+ var candidate;
1842
+
1843
+ if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1844
+ appElement = candidate;
1845
+ module = candidate.getAttribute(name);
1846
+ }
1847
+ });
1848
+ if (appElement) {
1849
+ if (!isAutoBootstrapAllowed) {
1850
+ window.console.error('Angular: disabling automatic bootstrap. <script> protocol indicates ' +
1851
+ 'an extension, document.location.href does not match.');
1852
+ return;
1853
+ }
1854
+ config.strictDi = getNgAttribute(appElement, 'strict-di') !== null;
1855
+ bootstrap(appElement, module ? [module] : [], config);
1856
+ }
1857
+ }
1858
+
1859
+ /**
1860
+ * @ngdoc function
1861
+ * @name angular.bootstrap
1862
+ * @module ng
1863
+ * @description
1864
+ * Use this function to manually start up angular application.
1865
+ *
1866
+ * For more information, see the {@link guide/bootstrap Bootstrap guide}.
1867
+ *
1868
+ * Angular will detect if it has been loaded into the browser more than once and only allow the
1869
+ * first loaded script to be bootstrapped and will report a warning to the browser console for
1870
+ * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1871
+ * multiple instances of Angular try to work on the DOM.
1872
+ *
1873
+ * <div class="alert alert-warning">
1874
+ * **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually.
1875
+ * They must use {@link ng.directive:ngApp ngApp}.
1876
+ * </div>
1877
+ *
1878
+ * <div class="alert alert-warning">
1879
+ * **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion},
1880
+ * such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}.
1881
+ * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
1882
+ * causing animations to stop working and making the injector inaccessible from outside the app.
1883
+ * </div>
1884
+ *
1885
+ * ```html
1886
+ * <!doctype html>
1887
+ * <html>
1888
+ * <body>
1889
+ * <div ng-controller="WelcomeController">
1890
+ * {{greeting}}
1891
+ * </div>
1892
+ *
1893
+ * <script src="angular.js"></script>
1894
+ * <script>
1895
+ * var app = angular.module('demo', [])
1896
+ * .controller('WelcomeController', function($scope) {
1897
+ * $scope.greeting = 'Welcome!';
1898
+ * });
1899
+ * angular.bootstrap(document, ['demo']);
1900
+ * </script>
1901
+ * </body>
1902
+ * </html>
1903
+ * ```
1904
+ *
1905
+ * @param {DOMElement} element DOM element which is the root of angular application.
1906
+ * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1907
+ * Each item in the array should be the name of a predefined module or a (DI annotated)
1908
+ * function that will be invoked by the injector as a `config` block.
1909
+ * See: {@link angular.module modules}
1910
+ * @param {Object=} config an object for defining configuration options for the application. The
1911
+ * following keys are supported:
1912
+ *
1913
+ * * `strictDi` - disable automatic function annotation for the application. This is meant to
1914
+ * assist in finding bugs which break minified code. Defaults to `false`.
1915
+ *
1916
+ * @returns {auto.$injector} Returns the newly created injector for this app.
1917
+ */
1918
+ function bootstrap(element, modules, config) {
1919
+ if (!isObject(config)) config = {};
1920
+ var defaultConfig = {
1921
+ strictDi: false
1922
+ };
1923
+ config = extend(defaultConfig, config);
1924
+ var doBootstrap = function() {
1925
+ element = jqLite(element);
1926
+
1927
+ if (element.injector()) {
1928
+ var tag = (element[0] === window.document) ? 'document' : startingTag(element);
1929
+ // Encode angle brackets to prevent input from being sanitized to empty string #8683.
1930
+ throw ngMinErr(
1931
+ 'btstrpd',
1932
+ 'App already bootstrapped with this element \'{0}\'',
1933
+ tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
1934
+ }
1935
+
1936
+ modules = modules || [];
1937
+ modules.unshift(['$provide', function($provide) {
1938
+ $provide.value('$rootElement', element);
1939
+ }]);
1940
+
1941
+ if (config.debugInfoEnabled) {
1942
+ // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1943
+ modules.push(['$compileProvider', function($compileProvider) {
1944
+ $compileProvider.debugInfoEnabled(true);
1945
+ }]);
1946
+ }
1947
+
1948
+ modules.unshift('ng');
1949
+ var injector = createInjector(modules, config.strictDi);
1950
+ injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1951
+ function bootstrapApply(scope, element, compile, injector) {
1952
+ scope.$apply(function() {
1953
+ element.data('$injector', injector);
1954
+ compile(element)(scope);
1955
+ });
1956
+ }]
1957
+ );
1958
+ return injector;
1959
+ };
1960
+
1961
+ var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1962
+ var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1963
+
1964
+ if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1965
+ config.debugInfoEnabled = true;
1966
+ window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1967
+ }
1968
+
1969
+ if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1970
+ return doBootstrap();
1971
+ }
1972
+
1973
+ window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1974
+ angular.resumeBootstrap = function(extraModules) {
1975
+ forEach(extraModules, function(module) {
1976
+ modules.push(module);
1977
+ });
1978
+ return doBootstrap();
1979
+ };
1980
+
1981
+ if (isFunction(angular.resumeDeferredBootstrap)) {
1982
+ angular.resumeDeferredBootstrap();
1983
+ }
1984
+ }
1985
+
1986
+ /**
1987
+ * @ngdoc function
1988
+ * @name angular.reloadWithDebugInfo
1989
+ * @module ng
1990
+ * @description
1991
+ * Use this function to reload the current application with debug information turned on.
1992
+ * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
1993
+ *
1994
+ * See {@link ng.$compileProvider#debugInfoEnabled} for more.
1995
+ */
1996
+ function reloadWithDebugInfo() {
1997
+ window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
1998
+ window.location.reload();
1999
+ }
2000
+
2001
+ /**
2002
+ * @name angular.getTestability
2003
+ * @module ng
2004
+ * @description
2005
+ * Get the testability service for the instance of Angular on the given
2006
+ * element.
2007
+ * @param {DOMElement} element DOM element which is the root of angular application.
2008
+ */
2009
+ function getTestability(rootElement) {
2010
+ var injector = angular.element(rootElement).injector();
2011
+ if (!injector) {
2012
+ throw ngMinErr('test',
2013
+ 'no injector found for element argument to getTestability');
2014
+ }
2015
+ return injector.get('$$testability');
2016
+ }
2017
+
2018
+ var SNAKE_CASE_REGEXP = /[A-Z]/g;
2019
+ function snake_case(name, separator) {
2020
+ separator = separator || '_';
2021
+ return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
2022
+ return (pos ? separator : '') + letter.toLowerCase();
2023
+ });
2024
+ }
2025
+
2026
+ var bindJQueryFired = false;
2027
+ function bindJQuery() {
2028
+ var originalCleanData;
2029
+
2030
+ if (bindJQueryFired) {
2031
+ return;
2032
+ }
2033
+
2034
+ // bind to jQuery if present;
2035
+ var jqName = jq();
2036
+ jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
2037
+ !jqName ? undefined : // use jqLite
2038
+ window[jqName]; // use jQuery specified by `ngJq`
2039
+
2040
+ // Use jQuery if it exists with proper functionality, otherwise default to us.
2041
+ // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
2042
+ // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
2043
+ // versions. It will not work for sure with jQuery <1.7, though.
2044
+ if (jQuery && jQuery.fn.on) {
2045
+ jqLite = jQuery;
2046
+ extend(jQuery.fn, {
2047
+ scope: JQLitePrototype.scope,
2048
+ isolateScope: JQLitePrototype.isolateScope,
2049
+ controller: /** @type {?} */ (JQLitePrototype).controller,
2050
+ injector: JQLitePrototype.injector,
2051
+ inheritedData: JQLitePrototype.inheritedData
2052
+ });
2053
+
2054
+ // All nodes removed from the DOM via various jQuery APIs like .remove()
2055
+ // are passed through jQuery.cleanData. Monkey-patch this method to fire
2056
+ // the $destroy event on all removed nodes.
2057
+ originalCleanData = jQuery.cleanData;
2058
+ jQuery.cleanData = function(elems) {
2059
+ var events;
2060
+ for (var i = 0, elem; (elem = elems[i]) != null; i++) {
2061
+ events = jQuery._data(elem, 'events');
2062
+ if (events && events.$destroy) {
2063
+ jQuery(elem).triggerHandler('$destroy');
2064
+ }
2065
+ }
2066
+ originalCleanData(elems);
2067
+ };
2068
+ } else {
2069
+ jqLite = JQLite;
2070
+ }
2071
+
2072
+ angular.element = jqLite;
2073
+
2074
+ // Prevent double-proxying.
2075
+ bindJQueryFired = true;
2076
+ }
2077
+
2078
+ /**
2079
+ * throw error if the argument is falsy.
2080
+ */
2081
+ function assertArg(arg, name, reason) {
2082
+ if (!arg) {
2083
+ throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required'));
2084
+ }
2085
+ return arg;
2086
+ }
2087
+
2088
+ function assertArgFn(arg, name, acceptArrayAnnotation) {
2089
+ if (acceptArrayAnnotation && isArray(arg)) {
2090
+ arg = arg[arg.length - 1];
2091
+ }
2092
+
2093
+ assertArg(isFunction(arg), name, 'not a function, got ' +
2094
+ (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
2095
+ return arg;
2096
+ }
2097
+
2098
+ /**
2099
+ * throw error if the name given is hasOwnProperty
2100
+ * @param {String} name the name to test
2101
+ * @param {String} context the context in which the name is used, such as module or directive
2102
+ */
2103
+ function assertNotHasOwnProperty(name, context) {
2104
+ if (name === 'hasOwnProperty') {
2105
+ throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2106
+ }
2107
+ }
2108
+
2109
+ /**
2110
+ * Return the value accessible from the object by path. Any undefined traversals are ignored
2111
+ * @param {Object} obj starting object
2112
+ * @param {String} path path to traverse
2113
+ * @param {boolean} [bindFnToScope=true]
2114
+ * @returns {Object} value as accessible by path
2115
+ */
2116
+ //TODO(misko): this function needs to be removed
2117
+ function getter(obj, path, bindFnToScope) {
2118
+ if (!path) return obj;
2119
+ var keys = path.split('.');
2120
+ var key;
2121
+ var lastInstance = obj;
2122
+ var len = keys.length;
2123
+
2124
+ for (var i = 0; i < len; i++) {
2125
+ key = keys[i];
2126
+ if (obj) {
2127
+ obj = (lastInstance = obj)[key];
2128
+ }
2129
+ }
2130
+ if (!bindFnToScope && isFunction(obj)) {
2131
+ return bind(lastInstance, obj);
2132
+ }
2133
+ return obj;
2134
+ }
2135
+
2136
+ /**
2137
+ * Return the DOM siblings between the first and last node in the given array.
2138
+ * @param {Array} array like object
2139
+ * @returns {Array} the inputted object or a jqLite collection containing the nodes
2140
+ */
2141
+ function getBlockNodes(nodes) {
2142
+ // TODO(perf): update `nodes` instead of creating a new object?
2143
+ var node = nodes[0];
2144
+ var endNode = nodes[nodes.length - 1];
2145
+ var blockNodes;
2146
+
2147
+ for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
2148
+ if (blockNodes || nodes[i] !== node) {
2149
+ if (!blockNodes) {
2150
+ blockNodes = jqLite(slice.call(nodes, 0, i));
2151
+ }
2152
+ blockNodes.push(node);
2153
+ }
2154
+ }
2155
+
2156
+ return blockNodes || nodes;
2157
+ }
2158
+
2159
+
2160
+ /**
2161
+ * Creates a new object without a prototype. This object is useful for lookup without having to
2162
+ * guard against prototypically inherited properties via hasOwnProperty.
2163
+ *
2164
+ * Related micro-benchmarks:
2165
+ * - http://jsperf.com/object-create2
2166
+ * - http://jsperf.com/proto-map-lookup/2
2167
+ * - http://jsperf.com/for-in-vs-object-keys2
2168
+ *
2169
+ * @returns {Object}
2170
+ */
2171
+ function createMap() {
2172
+ return Object.create(null);
2173
+ }
2174
+
2175
+ function stringify(value) {
2176
+ if (value == null) { // null || undefined
2177
+ return '';
2178
+ }
2179
+ switch (typeof value) {
2180
+ case 'string':
2181
+ break;
2182
+ case 'number':
2183
+ value = '' + value;
2184
+ break;
2185
+ default:
2186
+ if (hasCustomToString(value) && !isArray(value) && !isDate(value)) {
2187
+ value = value.toString();
2188
+ } else {
2189
+ value = toJson(value);
2190
+ }
2191
+ }
2192
+
2193
+ return value;
2194
+ }
2195
+
2196
+ var NODE_TYPE_ELEMENT = 1;
2197
+ var NODE_TYPE_ATTRIBUTE = 2;
2198
+ var NODE_TYPE_TEXT = 3;
2199
+ var NODE_TYPE_COMMENT = 8;
2200
+ var NODE_TYPE_DOCUMENT = 9;
2201
+ var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
2202
+
2203
+ /**
2204
+ * @ngdoc type
2205
+ * @name angular.Module
2206
+ * @module ng
2207
+ * @description
2208
+ *
2209
+ * Interface for configuring angular {@link angular.module modules}.
2210
+ */
2211
+
2212
+ function setupModuleLoader(window) {
2213
+
2214
+ var $injectorMinErr = minErr('$injector');
2215
+ var ngMinErr = minErr('ng');
2216
+
2217
+ function ensure(obj, name, factory) {
2218
+ return obj[name] || (obj[name] = factory());
2219
+ }
2220
+
2221
+ var angular = ensure(window, 'angular', Object);
2222
+
2223
+ // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
2224
+ angular.$$minErr = angular.$$minErr || minErr;
2225
+
2226
+ return ensure(angular, 'module', function() {
2227
+ /** @type {Object.<string, angular.Module>} */
2228
+ var modules = {};
2229
+
2230
+ /**
2231
+ * @ngdoc function
2232
+ * @name angular.module
2233
+ * @module ng
2234
+ * @description
2235
+ *
2236
+ * The `angular.module` is a global place for creating, registering and retrieving Angular
2237
+ * modules.
2238
+ * All modules (angular core or 3rd party) that should be available to an application must be
2239
+ * registered using this mechanism.
2240
+ *
2241
+ * Passing one argument retrieves an existing {@link angular.Module},
2242
+ * whereas passing more than one argument creates a new {@link angular.Module}
2243
+ *
2244
+ *
2245
+ * # Module
2246
+ *
2247
+ * A module is a collection of services, directives, controllers, filters, and configuration information.
2248
+ * `angular.module` is used to configure the {@link auto.$injector $injector}.
2249
+ *
2250
+ * ```js
2251
+ * // Create a new module
2252
+ * var myModule = angular.module('myModule', []);
2253
+ *
2254
+ * // register a new service
2255
+ * myModule.value('appName', 'MyCoolApp');
2256
+ *
2257
+ * // configure existing services inside initialization blocks.
2258
+ * myModule.config(['$locationProvider', function($locationProvider) {
2259
+ * // Configure existing providers
2260
+ * $locationProvider.hashPrefix('!');
2261
+ * }]);
2262
+ * ```
2263
+ *
2264
+ * Then you can create an injector and load your modules like this:
2265
+ *
2266
+ * ```js
2267
+ * var injector = angular.injector(['ng', 'myModule'])
2268
+ * ```
2269
+ *
2270
+ * However it's more likely that you'll just use
2271
+ * {@link ng.directive:ngApp ngApp} or
2272
+ * {@link angular.bootstrap} to simplify this process for you.
2273
+ *
2274
+ * @param {!string} name The name of the module to create or retrieve.
2275
+ * @param {!Array.<string>=} requires If specified then new module is being created. If
2276
+ * unspecified then the module is being retrieved for further configuration.
2277
+ * @param {Function=} configFn Optional configuration function for the module. Same as
2278
+ * {@link angular.Module#config Module#config()}.
2279
+ * @returns {angular.Module} new module with the {@link angular.Module} api.
2280
+ */
2281
+ return function module(name, requires, configFn) {
2282
+
2283
+ var info = {};
2284
+
2285
+ var assertNotHasOwnProperty = function(name, context) {
2286
+ if (name === 'hasOwnProperty') {
2287
+ throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2288
+ }
2289
+ };
2290
+
2291
+ assertNotHasOwnProperty(name, 'module');
2292
+ if (requires && modules.hasOwnProperty(name)) {
2293
+ modules[name] = null;
2294
+ }
2295
+ return ensure(modules, name, function() {
2296
+ if (!requires) {
2297
+ throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' +
2298
+ 'the module name or forgot to load it. If registering a module ensure that you ' +
2299
+ 'specify the dependencies as the second argument.', name);
2300
+ }
2301
+
2302
+ /** @type {!Array.<Array.<*>>} */
2303
+ var invokeQueue = [];
2304
+
2305
+ /** @type {!Array.<Function>} */
2306
+ var configBlocks = [];
2307
+
2308
+ /** @type {!Array.<Function>} */
2309
+ var runBlocks = [];
2310
+
2311
+ var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2312
+
2313
+ /** @type {angular.Module} */
2314
+ var moduleInstance = {
2315
+ // Private state
2316
+ _invokeQueue: invokeQueue,
2317
+ _configBlocks: configBlocks,
2318
+ _runBlocks: runBlocks,
2319
+
2320
+ /**
2321
+ * @ngdoc method
2322
+ * @name angular.Module#info
2323
+ * @module ng
2324
+ *
2325
+ * @param {Object=} info Information about the module
2326
+ * @returns {Object|Module} The current info object for this module if called as a getter,
2327
+ * or `this` if called as a setter.
2328
+ *
2329
+ * @description
2330
+ * Read and write custom information about this module.
2331
+ * For example you could put the version of the module in here.
2332
+ *
2333
+ * ```js
2334
+ * angular.module('myModule', []).info({ version: '1.0.0' });
2335
+ * ```
2336
+ *
2337
+ * The version could then be read back out by accessing the module elsewhere:
2338
+ *
2339
+ * ```
2340
+ * var version = angular.module('myModule').info().version;
2341
+ * ```
2342
+ *
2343
+ * You can also retrieve this information during runtime via the
2344
+ * {@link $injector#modules `$injector.modules`} property:
2345
+ *
2346
+ * ```js
2347
+ * var version = $injector.modules['myModule'].info().version;
2348
+ * ```
2349
+ */
2350
+ info: function(value) {
2351
+ if (isDefined(value)) {
2352
+ if (!isObject(value)) throw ngMinErr('aobj', 'Argument \'{0}\' must be an object', 'value');
2353
+ info = value;
2354
+ return this;
2355
+ }
2356
+ return info;
2357
+ },
2358
+
2359
+ /**
2360
+ * @ngdoc property
2361
+ * @name angular.Module#requires
2362
+ * @module ng
2363
+ *
2364
+ * @description
2365
+ * Holds the list of modules which the injector will load before the current module is
2366
+ * loaded.
2367
+ */
2368
+ requires: requires,
2369
+
2370
+ /**
2371
+ * @ngdoc property
2372
+ * @name angular.Module#name
2373
+ * @module ng
2374
+ *
2375
+ * @description
2376
+ * Name of the module.
2377
+ */
2378
+ name: name,
2379
+
2380
+
2381
+ /**
2382
+ * @ngdoc method
2383
+ * @name angular.Module#provider
2384
+ * @module ng
2385
+ * @param {string} name service name
2386
+ * @param {Function} providerType Construction function for creating new instance of the
2387
+ * service.
2388
+ * @description
2389
+ * See {@link auto.$provide#provider $provide.provider()}.
2390
+ */
2391
+ provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2392
+
2393
+ /**
2394
+ * @ngdoc method
2395
+ * @name angular.Module#factory
2396
+ * @module ng
2397
+ * @param {string} name service name
2398
+ * @param {Function} providerFunction Function for creating new instance of the service.
2399
+ * @description
2400
+ * See {@link auto.$provide#factory $provide.factory()}.
2401
+ */
2402
+ factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2403
+
2404
+ /**
2405
+ * @ngdoc method
2406
+ * @name angular.Module#service
2407
+ * @module ng
2408
+ * @param {string} name service name
2409
+ * @param {Function} constructor A constructor function that will be instantiated.
2410
+ * @description
2411
+ * See {@link auto.$provide#service $provide.service()}.
2412
+ */
2413
+ service: invokeLaterAndSetModuleName('$provide', 'service'),
2414
+
2415
+ /**
2416
+ * @ngdoc method
2417
+ * @name angular.Module#value
2418
+ * @module ng
2419
+ * @param {string} name service name
2420
+ * @param {*} object Service instance object.
2421
+ * @description
2422
+ * See {@link auto.$provide#value $provide.value()}.
2423
+ */
2424
+ value: invokeLater('$provide', 'value'),
2425
+
2426
+ /**
2427
+ * @ngdoc method
2428
+ * @name angular.Module#constant
2429
+ * @module ng
2430
+ * @param {string} name constant name
2431
+ * @param {*} object Constant value.
2432
+ * @description
2433
+ * Because the constants are fixed, they get applied before other provide methods.
2434
+ * See {@link auto.$provide#constant $provide.constant()}.
2435
+ */
2436
+ constant: invokeLater('$provide', 'constant', 'unshift'),
2437
+
2438
+ /**
2439
+ * @ngdoc method
2440
+ * @name angular.Module#decorator
2441
+ * @module ng
2442
+ * @param {string} name The name of the service to decorate.
2443
+ * @param {Function} decorFn This function will be invoked when the service needs to be
2444
+ * instantiated and should return the decorated service instance.
2445
+ * @description
2446
+ * See {@link auto.$provide#decorator $provide.decorator()}.
2447
+ */
2448
+ decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks),
2449
+
2450
+ /**
2451
+ * @ngdoc method
2452
+ * @name angular.Module#animation
2453
+ * @module ng
2454
+ * @param {string} name animation name
2455
+ * @param {Function} animationFactory Factory function for creating new instance of an
2456
+ * animation.
2457
+ * @description
2458
+ *
2459
+ * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2460
+ *
2461
+ *
2462
+ * Defines an animation hook that can be later used with
2463
+ * {@link $animate $animate} service and directives that use this service.
2464
+ *
2465
+ * ```js
2466
+ * module.animation('.animation-name', function($inject1, $inject2) {
2467
+ * return {
2468
+ * eventName : function(element, done) {
2469
+ * //code to run the animation
2470
+ * //once complete, then run done()
2471
+ * return function cancellationFunction(element) {
2472
+ * //code to cancel the animation
2473
+ * }
2474
+ * }
2475
+ * }
2476
+ * })
2477
+ * ```
2478
+ *
2479
+ * See {@link ng.$animateProvider#register $animateProvider.register()} and
2480
+ * {@link ngAnimate ngAnimate module} for more information.
2481
+ */
2482
+ animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2483
+
2484
+ /**
2485
+ * @ngdoc method
2486
+ * @name angular.Module#filter
2487
+ * @module ng
2488
+ * @param {string} name Filter name - this must be a valid angular expression identifier
2489
+ * @param {Function} filterFactory Factory function for creating new instance of filter.
2490
+ * @description
2491
+ * See {@link ng.$filterProvider#register $filterProvider.register()}.
2492
+ *
2493
+ * <div class="alert alert-warning">
2494
+ * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
2495
+ * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2496
+ * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2497
+ * (`myapp_subsection_filterx`).
2498
+ * </div>
2499
+ */
2500
+ filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2501
+
2502
+ /**
2503
+ * @ngdoc method
2504
+ * @name angular.Module#controller
2505
+ * @module ng
2506
+ * @param {string|Object} name Controller name, or an object map of controllers where the
2507
+ * keys are the names and the values are the constructors.
2508
+ * @param {Function} constructor Controller constructor function.
2509
+ * @description
2510
+ * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2511
+ */
2512
+ controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2513
+
2514
+ /**
2515
+ * @ngdoc method
2516
+ * @name angular.Module#directive
2517
+ * @module ng
2518
+ * @param {string|Object} name Directive name, or an object map of directives where the
2519
+ * keys are the names and the values are the factories.
2520
+ * @param {Function} directiveFactory Factory function for creating new instance of
2521
+ * directives.
2522
+ * @description
2523
+ * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2524
+ */
2525
+ directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2526
+
2527
+ /**
2528
+ * @ngdoc method
2529
+ * @name angular.Module#component
2530
+ * @module ng
2531
+ * @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp)
2532
+ * @param {Object} options Component definition object (a simplified
2533
+ * {@link ng.$compile#directive-definition-object directive definition object})
2534
+ *
2535
+ * @description
2536
+ * See {@link ng.$compileProvider#component $compileProvider.component()}.
2537
+ */
2538
+ component: invokeLaterAndSetModuleName('$compileProvider', 'component'),
2539
+
2540
+ /**
2541
+ * @ngdoc method
2542
+ * @name angular.Module#config
2543
+ * @module ng
2544
+ * @param {Function} configFn Execute this function on module load. Useful for service
2545
+ * configuration.
2546
+ * @description
2547
+ * Use this method to register work which needs to be performed on module loading.
2548
+ * For more about how to configure services, see
2549
+ * {@link providers#provider-recipe Provider Recipe}.
2550
+ */
2551
+ config: config,
2552
+
2553
+ /**
2554
+ * @ngdoc method
2555
+ * @name angular.Module#run
2556
+ * @module ng
2557
+ * @param {Function} initializationFn Execute this function after injector creation.
2558
+ * Useful for application initialization.
2559
+ * @description
2560
+ * Use this method to register work which should be performed when the injector is done
2561
+ * loading all modules.
2562
+ */
2563
+ run: function(block) {
2564
+ runBlocks.push(block);
2565
+ return this;
2566
+ }
2567
+ };
2568
+
2569
+ if (configFn) {
2570
+ config(configFn);
2571
+ }
2572
+
2573
+ return moduleInstance;
2574
+
2575
+ /**
2576
+ * @param {string} provider
2577
+ * @param {string} method
2578
+ * @param {String=} insertMethod
2579
+ * @returns {angular.Module}
2580
+ */
2581
+ function invokeLater(provider, method, insertMethod, queue) {
2582
+ if (!queue) queue = invokeQueue;
2583
+ return function() {
2584
+ queue[insertMethod || 'push']([provider, method, arguments]);
2585
+ return moduleInstance;
2586
+ };
2587
+ }
2588
+
2589
+ /**
2590
+ * @param {string} provider
2591
+ * @param {string} method
2592
+ * @returns {angular.Module}
2593
+ */
2594
+ function invokeLaterAndSetModuleName(provider, method, queue) {
2595
+ if (!queue) queue = invokeQueue;
2596
+ return function(recipeName, factoryFunction) {
2597
+ if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
2598
+ queue.push([provider, method, arguments]);
2599
+ return moduleInstance;
2600
+ };
2601
+ }
2602
+ });
2603
+ };
2604
+ });
2605
+
2606
+ }
2607
+
2608
+ /* global shallowCopy: true */
2609
+
2610
+ /**
2611
+ * Creates a shallow copy of an object, an array or a primitive.
2612
+ *
2613
+ * Assumes that there are no proto properties for objects.
2614
+ */
2615
+ function shallowCopy(src, dst) {
2616
+ if (isArray(src)) {
2617
+ dst = dst || [];
2618
+
2619
+ for (var i = 0, ii = src.length; i < ii; i++) {
2620
+ dst[i] = src[i];
2621
+ }
2622
+ } else if (isObject(src)) {
2623
+ dst = dst || {};
2624
+
2625
+ for (var key in src) {
2626
+ if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
2627
+ dst[key] = src[key];
2628
+ }
2629
+ }
2630
+ }
2631
+
2632
+ return dst || src;
2633
+ }
2634
+
2635
+ /* exported toDebugString */
2636
+
2637
+ function serializeObject(obj, maxDepth) {
2638
+ var seen = [];
2639
+
2640
+ // There is no direct way to stringify object until reaching a specific depth
2641
+ // and a very deep object can cause a performance issue, so we copy the object
2642
+ // based on this specific depth and then stringify it.
2643
+ if (isValidObjectMaxDepth(maxDepth)) {
2644
+ // This file is also included in `angular-loader`, so `copy()` might not always be available in
2645
+ // the closure. Therefore, it is lazily retrieved as `angular.copy()` when needed.
2646
+ obj = angular.copy(obj, null, maxDepth);
2647
+ }
2648
+ return JSON.stringify(obj, function(key, val) {
2649
+ val = toJsonReplacer(key, val);
2650
+ if (isObject(val)) {
2651
+
2652
+ if (seen.indexOf(val) >= 0) return '...';
2653
+
2654
+ seen.push(val);
2655
+ }
2656
+ return val;
2657
+ });
2658
+ }
2659
+
2660
+ function toDebugString(obj, maxDepth) {
2661
+ if (typeof obj === 'function') {
2662
+ return obj.toString().replace(/ \{[\s\S]*$/, '');
2663
+ } else if (isUndefined(obj)) {
2664
+ return 'undefined';
2665
+ } else if (typeof obj !== 'string') {
2666
+ return serializeObject(obj, maxDepth);
2667
+ }
2668
+ return obj;
2669
+ }
2670
+
2671
+ /* global angularModule: true,
2672
+ version: true,
2673
+
2674
+ $CompileProvider,
2675
+
2676
+ htmlAnchorDirective,
2677
+ inputDirective,
2678
+ inputDirective,
2679
+ formDirective,
2680
+ scriptDirective,
2681
+ selectDirective,
2682
+ optionDirective,
2683
+ ngBindDirective,
2684
+ ngBindHtmlDirective,
2685
+ ngBindTemplateDirective,
2686
+ ngClassDirective,
2687
+ ngClassEvenDirective,
2688
+ ngClassOddDirective,
2689
+ ngCloakDirective,
2690
+ ngControllerDirective,
2691
+ ngFormDirective,
2692
+ ngHideDirective,
2693
+ ngIfDirective,
2694
+ ngIncludeDirective,
2695
+ ngIncludeFillContentDirective,
2696
+ ngInitDirective,
2697
+ ngNonBindableDirective,
2698
+ ngPluralizeDirective,
2699
+ ngRepeatDirective,
2700
+ ngShowDirective,
2701
+ ngStyleDirective,
2702
+ ngSwitchDirective,
2703
+ ngSwitchWhenDirective,
2704
+ ngSwitchDefaultDirective,
2705
+ ngOptionsDirective,
2706
+ ngTranscludeDirective,
2707
+ ngModelDirective,
2708
+ ngListDirective,
2709
+ ngChangeDirective,
2710
+ patternDirective,
2711
+ patternDirective,
2712
+ requiredDirective,
2713
+ requiredDirective,
2714
+ minlengthDirective,
2715
+ minlengthDirective,
2716
+ maxlengthDirective,
2717
+ maxlengthDirective,
2718
+ ngValueDirective,
2719
+ ngModelOptionsDirective,
2720
+ ngAttributeAliasDirectives,
2721
+ ngEventDirectives,
2722
+
2723
+ $AnchorScrollProvider,
2724
+ $AnimateProvider,
2725
+ $CoreAnimateCssProvider,
2726
+ $$CoreAnimateJsProvider,
2727
+ $$CoreAnimateQueueProvider,
2728
+ $$AnimateRunnerFactoryProvider,
2729
+ $$AnimateAsyncRunFactoryProvider,
2730
+ $BrowserProvider,
2731
+ $CacheFactoryProvider,
2732
+ $ControllerProvider,
2733
+ $DateProvider,
2734
+ $DocumentProvider,
2735
+ $$IsDocumentHiddenProvider,
2736
+ $ExceptionHandlerProvider,
2737
+ $FilterProvider,
2738
+ $$ForceReflowProvider,
2739
+ $InterpolateProvider,
2740
+ $IntervalProvider,
2741
+ $HttpProvider,
2742
+ $HttpParamSerializerProvider,
2743
+ $HttpParamSerializerJQLikeProvider,
2744
+ $HttpBackendProvider,
2745
+ $xhrFactoryProvider,
2746
+ $jsonpCallbacksProvider,
2747
+ $LocationProvider,
2748
+ $LogProvider,
2749
+ $$MapProvider,
2750
+ $ParseProvider,
2751
+ $RootScopeProvider,
2752
+ $QProvider,
2753
+ $$QProvider,
2754
+ $$SanitizeUriProvider,
2755
+ $SceProvider,
2756
+ $SceDelegateProvider,
2757
+ $SnifferProvider,
2758
+ $TemplateCacheProvider,
2759
+ $TemplateRequestProvider,
2760
+ $$TestabilityProvider,
2761
+ $TimeoutProvider,
2762
+ $$RAFProvider,
2763
+ $WindowProvider,
2764
+ $$jqLiteProvider,
2765
+ $$CookieReaderProvider
2766
+ */
2767
+
2768
+
2769
+ /**
2770
+ * @ngdoc object
2771
+ * @name angular.version
2772
+ * @module ng
2773
+ * @description
2774
+ * An object that contains information about the current AngularJS version.
2775
+ *
2776
+ * This object has the following properties:
2777
+ *
2778
+ * - `full` – `{string}` – Full version string, such as "0.9.18".
2779
+ * - `major` – `{number}` – Major version number, such as "0".
2780
+ * - `minor` – `{number}` – Minor version number, such as "9".
2781
+ * - `dot` – `{number}` – Dot version number, such as "18".
2782
+ * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2783
+ */
2784
+ var version = {
2785
+ // These placeholder strings will be replaced by grunt's `build` task.
2786
+ // They need to be double- or single-quoted.
2787
+ full: '1.6.6',
2788
+ major: 1,
2789
+ minor: 6,
2790
+ dot: 6,
2791
+ codeName: 'interdimensional-cable'
2792
+ };
2793
+
2794
+
2795
+ function publishExternalAPI(angular) {
2796
+ extend(angular, {
2797
+ 'errorHandlingConfig': errorHandlingConfig,
2798
+ 'bootstrap': bootstrap,
2799
+ 'copy': copy,
2800
+ 'extend': extend,
2801
+ 'merge': merge,
2802
+ 'equals': equals,
2803
+ 'element': jqLite,
2804
+ 'forEach': forEach,
2805
+ 'injector': createInjector,
2806
+ 'noop': noop,
2807
+ 'bind': bind,
2808
+ 'toJson': toJson,
2809
+ 'fromJson': fromJson,
2810
+ 'identity': identity,
2811
+ 'isUndefined': isUndefined,
2812
+ 'isDefined': isDefined,
2813
+ 'isString': isString,
2814
+ 'isFunction': isFunction,
2815
+ 'isObject': isObject,
2816
+ 'isNumber': isNumber,
2817
+ 'isElement': isElement,
2818
+ 'isArray': isArray,
2819
+ 'version': version,
2820
+ 'isDate': isDate,
2821
+ 'lowercase': lowercase,
2822
+ 'uppercase': uppercase,
2823
+ 'callbacks': {$$counter: 0},
2824
+ 'getTestability': getTestability,
2825
+ 'reloadWithDebugInfo': reloadWithDebugInfo,
2826
+ '$$minErr': minErr,
2827
+ '$$csp': csp,
2828
+ '$$encodeUriSegment': encodeUriSegment,
2829
+ '$$encodeUriQuery': encodeUriQuery,
2830
+ '$$stringify': stringify
2831
+ });
2832
+
2833
+ angularModule = setupModuleLoader(window);
2834
+
2835
+ angularModule('ng', ['ngLocale'], ['$provide',
2836
+ function ngModule($provide) {
2837
+ // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2838
+ $provide.provider({
2839
+ $$sanitizeUri: $$SanitizeUriProvider
2840
+ });
2841
+ $provide.provider('$compile', $CompileProvider).
2842
+ directive({
2843
+ a: htmlAnchorDirective,
2844
+ input: inputDirective,
2845
+ textarea: inputDirective,
2846
+ form: formDirective,
2847
+ script: scriptDirective,
2848
+ select: selectDirective,
2849
+ option: optionDirective,
2850
+ ngBind: ngBindDirective,
2851
+ ngBindHtml: ngBindHtmlDirective,
2852
+ ngBindTemplate: ngBindTemplateDirective,
2853
+ ngClass: ngClassDirective,
2854
+ ngClassEven: ngClassEvenDirective,
2855
+ ngClassOdd: ngClassOddDirective,
2856
+ ngCloak: ngCloakDirective,
2857
+ ngController: ngControllerDirective,
2858
+ ngForm: ngFormDirective,
2859
+ ngHide: ngHideDirective,
2860
+ ngIf: ngIfDirective,
2861
+ ngInclude: ngIncludeDirective,
2862
+ ngInit: ngInitDirective,
2863
+ ngNonBindable: ngNonBindableDirective,
2864
+ ngPluralize: ngPluralizeDirective,
2865
+ ngRepeat: ngRepeatDirective,
2866
+ ngShow: ngShowDirective,
2867
+ ngStyle: ngStyleDirective,
2868
+ ngSwitch: ngSwitchDirective,
2869
+ ngSwitchWhen: ngSwitchWhenDirective,
2870
+ ngSwitchDefault: ngSwitchDefaultDirective,
2871
+ ngOptions: ngOptionsDirective,
2872
+ ngTransclude: ngTranscludeDirective,
2873
+ ngModel: ngModelDirective,
2874
+ ngList: ngListDirective,
2875
+ ngChange: ngChangeDirective,
2876
+ pattern: patternDirective,
2877
+ ngPattern: patternDirective,
2878
+ required: requiredDirective,
2879
+ ngRequired: requiredDirective,
2880
+ minlength: minlengthDirective,
2881
+ ngMinlength: minlengthDirective,
2882
+ maxlength: maxlengthDirective,
2883
+ ngMaxlength: maxlengthDirective,
2884
+ ngValue: ngValueDirective,
2885
+ ngModelOptions: ngModelOptionsDirective
2886
+ }).
2887
+ directive({
2888
+ ngInclude: ngIncludeFillContentDirective
2889
+ }).
2890
+ directive(ngAttributeAliasDirectives).
2891
+ directive(ngEventDirectives);
2892
+ $provide.provider({
2893
+ $anchorScroll: $AnchorScrollProvider,
2894
+ $animate: $AnimateProvider,
2895
+ $animateCss: $CoreAnimateCssProvider,
2896
+ $$animateJs: $$CoreAnimateJsProvider,
2897
+ $$animateQueue: $$CoreAnimateQueueProvider,
2898
+ $$AnimateRunner: $$AnimateRunnerFactoryProvider,
2899
+ $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider,
2900
+ $browser: $BrowserProvider,
2901
+ $cacheFactory: $CacheFactoryProvider,
2902
+ $controller: $ControllerProvider,
2903
+ $document: $DocumentProvider,
2904
+ $$isDocumentHidden: $$IsDocumentHiddenProvider,
2905
+ $exceptionHandler: $ExceptionHandlerProvider,
2906
+ $filter: $FilterProvider,
2907
+ $$forceReflow: $$ForceReflowProvider,
2908
+ $interpolate: $InterpolateProvider,
2909
+ $interval: $IntervalProvider,
2910
+ $http: $HttpProvider,
2911
+ $httpParamSerializer: $HttpParamSerializerProvider,
2912
+ $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2913
+ $httpBackend: $HttpBackendProvider,
2914
+ $xhrFactory: $xhrFactoryProvider,
2915
+ $jsonpCallbacks: $jsonpCallbacksProvider,
2916
+ $location: $LocationProvider,
2917
+ $log: $LogProvider,
2918
+ $parse: $ParseProvider,
2919
+ $rootScope: $RootScopeProvider,
2920
+ $q: $QProvider,
2921
+ $$q: $$QProvider,
2922
+ $sce: $SceProvider,
2923
+ $sceDelegate: $SceDelegateProvider,
2924
+ $sniffer: $SnifferProvider,
2925
+ $templateCache: $TemplateCacheProvider,
2926
+ $templateRequest: $TemplateRequestProvider,
2927
+ $$testability: $$TestabilityProvider,
2928
+ $timeout: $TimeoutProvider,
2929
+ $window: $WindowProvider,
2930
+ $$rAF: $$RAFProvider,
2931
+ $$jqLite: $$jqLiteProvider,
2932
+ $$Map: $$MapProvider,
2933
+ $$cookieReader: $$CookieReaderProvider
2934
+ });
2935
+ }
2936
+ ])
2937
+ .info({ angularVersion: '1.6.6' });
2938
+ }
2939
+
2940
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2941
+ * Any commits to this file should be reviewed with security in mind. *
2942
+ * Changes to this file can potentially create security vulnerabilities. *
2943
+ * An approval from 2 Core members with history of modifying *
2944
+ * this file is required. *
2945
+ * *
2946
+ * Does the change somehow allow for arbitrary javascript to be executed? *
2947
+ * Or allows for someone to change the prototype of built-in objects? *
2948
+ * Or gives undesired access to variables likes document or window? *
2949
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2950
+
2951
+ /* global
2952
+ JQLitePrototype: true,
2953
+ BOOLEAN_ATTR: true,
2954
+ ALIASED_ATTR: true
2955
+ */
2956
+
2957
+ //////////////////////////////////
2958
+ //JQLite
2959
+ //////////////////////////////////
2960
+
2961
+ /**
2962
+ * @ngdoc function
2963
+ * @name angular.element
2964
+ * @module ng
2965
+ * @kind function
2966
+ *
2967
+ * @description
2968
+ * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
2969
+ *
2970
+ * If jQuery is available, `angular.element` is an alias for the
2971
+ * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2972
+ * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**.
2973
+ *
2974
+ * jqLite is a tiny, API-compatible subset of jQuery that allows
2975
+ * Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most
2976
+ * commonly needed functionality with the goal of having a very small footprint.
2977
+ *
2978
+ * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the
2979
+ * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a
2980
+ * specific version of jQuery if multiple versions exist on the page.
2981
+ *
2982
+ * <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with jQuery or
2983
+ * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div>
2984
+ *
2985
+ * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements
2986
+ * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)`
2987
+ * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div>
2988
+ *
2989
+ * ## Angular's jqLite
2990
+ * jqLite provides only the following jQuery methods:
2991
+ *
2992
+ * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument
2993
+ * - [`after()`](http://api.jquery.com/after/)
2994
+ * - [`append()`](http://api.jquery.com/append/)
2995
+ * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
2996
+ * - [`bind()`](http://api.jquery.com/bind/) (_deprecated_, use [`on()`](http://api.jquery.com/on/)) - Does not support namespaces, selectors or eventData
2997
+ * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2998
+ * - [`clone()`](http://api.jquery.com/clone/)
2999
+ * - [`contents()`](http://api.jquery.com/contents/)
3000
+ * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`.
3001
+ * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing.
3002
+ * - [`data()`](http://api.jquery.com/data/)
3003
+ * - [`detach()`](http://api.jquery.com/detach/)
3004
+ * - [`empty()`](http://api.jquery.com/empty/)
3005
+ * - [`eq()`](http://api.jquery.com/eq/)
3006
+ * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
3007
+ * - [`hasClass()`](http://api.jquery.com/hasClass/)
3008
+ * - [`html()`](http://api.jquery.com/html/)
3009
+ * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
3010
+ * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
3011
+ * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
3012
+ * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
3013
+ * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
3014
+ * - [`prepend()`](http://api.jquery.com/prepend/)
3015
+ * - [`prop()`](http://api.jquery.com/prop/)
3016
+ * - [`ready()`](http://api.jquery.com/ready/) (_deprecated_, use `angular.element(callback)` instead of `angular.element(document).ready(callback)`)
3017
+ * - [`remove()`](http://api.jquery.com/remove/)
3018
+ * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - Does not support multiple attributes
3019
+ * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument
3020
+ * - [`removeData()`](http://api.jquery.com/removeData/)
3021
+ * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
3022
+ * - [`text()`](http://api.jquery.com/text/)
3023
+ * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument
3024
+ * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers
3025
+ * - [`unbind()`](http://api.jquery.com/unbind/) (_deprecated_, use [`off()`](http://api.jquery.com/off/)) - Does not support namespaces or event object as parameter
3026
+ * - [`val()`](http://api.jquery.com/val/)
3027
+ * - [`wrap()`](http://api.jquery.com/wrap/)
3028
+ *
3029
+ * ## jQuery/jqLite Extras
3030
+ * Angular also provides the following additional methods and events to both jQuery and jqLite:
3031
+ *
3032
+ * ### Events
3033
+ * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
3034
+ * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
3035
+ * element before it is removed.
3036
+ *
3037
+ * ### Methods
3038
+ * - `controller(name)` - retrieves the controller of the current element or its parent. By default
3039
+ * retrieves controller associated with the `ngController` directive. If `name` is provided as
3040
+ * camelCase directive name, then the controller for this directive will be retrieved (e.g.
3041
+ * `'ngModel'`).
3042
+ * - `injector()` - retrieves the injector of the current element or its parent.
3043
+ * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
3044
+ * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
3045
+ * be enabled.
3046
+ * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
3047
+ * current element. This getter should be used only on elements that contain a directive which starts a new isolate
3048
+ * scope. Calling `scope()` on this element always returns the original non-isolate scope.
3049
+ * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
3050
+ * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
3051
+ * parent element is reached.
3052
+ *
3053
+ * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See
3054
+ * https://github.com/angular/angular.js/issues/14251 for more information.
3055
+ *
3056
+ * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
3057
+ * @returns {Object} jQuery object.
3058
+ */
3059
+
3060
+ JQLite.expando = 'ng339';
3061
+
3062
+ var jqCache = JQLite.cache = {},
3063
+ jqId = 1;
3064
+
3065
+ /*
3066
+ * !!! This is an undocumented "private" function !!!
3067
+ */
3068
+ JQLite._data = function(node) {
3069
+ //jQuery always returns an object on cache miss
3070
+ return this.cache[node[this.expando]] || {};
3071
+ };
3072
+
3073
+ function jqNextId() { return ++jqId; }
3074
+
3075
+
3076
+ var DASH_LOWERCASE_REGEXP = /-([a-z])/g;
3077
+ var MS_HACK_REGEXP = /^-ms-/;
3078
+ var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' };
3079
+ var jqLiteMinErr = minErr('jqLite');
3080
+
3081
+ /**
3082
+ * Converts kebab-case to camelCase.
3083
+ * There is also a special case for the ms prefix starting with a lowercase letter.
3084
+ * @param name Name to normalize
3085
+ */
3086
+ function cssKebabToCamel(name) {
3087
+ return kebabToCamel(name.replace(MS_HACK_REGEXP, 'ms-'));
3088
+ }
3089
+
3090
+ function fnCamelCaseReplace(all, letter) {
3091
+ return letter.toUpperCase();
3092
+ }
3093
+
3094
+ /**
3095
+ * Converts kebab-case to camelCase.
3096
+ * @param name Name to normalize
3097
+ */
3098
+ function kebabToCamel(name) {
3099
+ return name
3100
+ .replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace);
3101
+ }
3102
+
3103
+ var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
3104
+ var HTML_REGEXP = /<|&#?\w+;/;
3105
+ var TAG_NAME_REGEXP = /<([\w:-]+)/;
3106
+ var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
3107
+
3108
+ var wrapMap = {
3109
+ 'option': [1, '<select multiple="multiple">', '</select>'],
3110
+
3111
+ 'thead': [1, '<table>', '</table>'],
3112
+ 'col': [2, '<table><colgroup>', '</colgroup></table>'],
3113
+ 'tr': [2, '<table><tbody>', '</tbody></table>'],
3114
+ 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
3115
+ '_default': [0, '', '']
3116
+ };
3117
+
3118
+ wrapMap.optgroup = wrapMap.option;
3119
+ wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
3120
+ wrapMap.th = wrapMap.td;
3121
+
3122
+
3123
+ function jqLiteIsTextNode(html) {
3124
+ return !HTML_REGEXP.test(html);
3125
+ }
3126
+
3127
+ function jqLiteAcceptsData(node) {
3128
+ // The window object can accept data but has no nodeType
3129
+ // Otherwise we are only interested in elements (1) and documents (9)
3130
+ var nodeType = node.nodeType;
3131
+ return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
3132
+ }
3133
+
3134
+ function jqLiteHasData(node) {
3135
+ for (var key in jqCache[node.ng339]) {
3136
+ return true;
3137
+ }
3138
+ return false;
3139
+ }
3140
+
3141
+ function jqLiteBuildFragment(html, context) {
3142
+ var tmp, tag, wrap,
3143
+ fragment = context.createDocumentFragment(),
3144
+ nodes = [], i;
3145
+
3146
+ if (jqLiteIsTextNode(html)) {
3147
+ // Convert non-html into a text node
3148
+ nodes.push(context.createTextNode(html));
3149
+ } else {
3150
+ // Convert html into DOM nodes
3151
+ tmp = fragment.appendChild(context.createElement('div'));
3152
+ tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase();
3153
+ wrap = wrapMap[tag] || wrapMap._default;
3154
+ tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, '<$1></$2>') + wrap[2];
3155
+
3156
+ // Descend through wrappers to the right content
3157
+ i = wrap[0];
3158
+ while (i--) {
3159
+ tmp = tmp.lastChild;
3160
+ }
3161
+
3162
+ nodes = concat(nodes, tmp.childNodes);
3163
+
3164
+ tmp = fragment.firstChild;
3165
+ tmp.textContent = '';
3166
+ }
3167
+
3168
+ // Remove wrapper from fragment
3169
+ fragment.textContent = '';
3170
+ fragment.innerHTML = ''; // Clear inner HTML
3171
+ forEach(nodes, function(node) {
3172
+ fragment.appendChild(node);
3173
+ });
3174
+
3175
+ return fragment;
3176
+ }
3177
+
3178
+ function jqLiteParseHTML(html, context) {
3179
+ context = context || window.document;
3180
+ var parsed;
3181
+
3182
+ if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
3183
+ return [context.createElement(parsed[1])];
3184
+ }
3185
+
3186
+ if ((parsed = jqLiteBuildFragment(html, context))) {
3187
+ return parsed.childNodes;
3188
+ }
3189
+
3190
+ return [];
3191
+ }
3192
+
3193
+ function jqLiteWrapNode(node, wrapper) {
3194
+ var parent = node.parentNode;
3195
+
3196
+ if (parent) {
3197
+ parent.replaceChild(wrapper, node);
3198
+ }
3199
+
3200
+ wrapper.appendChild(node);
3201
+ }
3202
+
3203
+
3204
+ // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
3205
+ var jqLiteContains = window.Node.prototype.contains || /** @this */ function(arg) {
3206
+ // eslint-disable-next-line no-bitwise
3207
+ return !!(this.compareDocumentPosition(arg) & 16);
3208
+ };
3209
+
3210
+ /////////////////////////////////////////////
3211
+ function JQLite(element) {
3212
+ if (element instanceof JQLite) {
3213
+ return element;
3214
+ }
3215
+
3216
+ var argIsString;
3217
+
3218
+ if (isString(element)) {
3219
+ element = trim(element);
3220
+ argIsString = true;
3221
+ }
3222
+ if (!(this instanceof JQLite)) {
3223
+ if (argIsString && element.charAt(0) !== '<') {
3224
+ throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
3225
+ }
3226
+ return new JQLite(element);
3227
+ }
3228
+
3229
+ if (argIsString) {
3230
+ jqLiteAddNodes(this, jqLiteParseHTML(element));
3231
+ } else if (isFunction(element)) {
3232
+ jqLiteReady(element);
3233
+ } else {
3234
+ jqLiteAddNodes(this, element);
3235
+ }
3236
+ }
3237
+
3238
+ function jqLiteClone(element) {
3239
+ return element.cloneNode(true);
3240
+ }
3241
+
3242
+ function jqLiteDealoc(element, onlyDescendants) {
3243
+ if (!onlyDescendants && jqLiteAcceptsData(element)) jqLite.cleanData([element]);
3244
+
3245
+ if (element.querySelectorAll) {
3246
+ jqLite.cleanData(element.querySelectorAll('*'));
3247
+ }
3248
+ }
3249
+
3250
+ function jqLiteOff(element, type, fn, unsupported) {
3251
+ if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
3252
+
3253
+ var expandoStore = jqLiteExpandoStore(element);
3254
+ var events = expandoStore && expandoStore.events;
3255
+ var handle = expandoStore && expandoStore.handle;
3256
+
3257
+ if (!handle) return; //no listeners registered
3258
+
3259
+ if (!type) {
3260
+ for (type in events) {
3261
+ if (type !== '$destroy') {
3262
+ element.removeEventListener(type, handle);
3263
+ }
3264
+ delete events[type];
3265
+ }
3266
+ } else {
3267
+
3268
+ var removeHandler = function(type) {
3269
+ var listenerFns = events[type];
3270
+ if (isDefined(fn)) {
3271
+ arrayRemove(listenerFns || [], fn);
3272
+ }
3273
+ if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
3274
+ element.removeEventListener(type, handle);
3275
+ delete events[type];
3276
+ }
3277
+ };
3278
+
3279
+ forEach(type.split(' '), function(type) {
3280
+ removeHandler(type);
3281
+ if (MOUSE_EVENT_MAP[type]) {
3282
+ removeHandler(MOUSE_EVENT_MAP[type]);
3283
+ }
3284
+ });
3285
+ }
3286
+ }
3287
+
3288
+ function jqLiteRemoveData(element, name) {
3289
+ var expandoId = element.ng339;
3290
+ var expandoStore = expandoId && jqCache[expandoId];
3291
+
3292
+ if (expandoStore) {
3293
+ if (name) {
3294
+ delete expandoStore.data[name];
3295
+ return;
3296
+ }
3297
+
3298
+ if (expandoStore.handle) {
3299
+ if (expandoStore.events.$destroy) {
3300
+ expandoStore.handle({}, '$destroy');
3301
+ }
3302
+ jqLiteOff(element);
3303
+ }
3304
+ delete jqCache[expandoId];
3305
+ element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
3306
+ }
3307
+ }
3308
+
3309
+
3310
+ function jqLiteExpandoStore(element, createIfNecessary) {
3311
+ var expandoId = element.ng339,
3312
+ expandoStore = expandoId && jqCache[expandoId];
3313
+
3314
+ if (createIfNecessary && !expandoStore) {
3315
+ element.ng339 = expandoId = jqNextId();
3316
+ expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
3317
+ }
3318
+
3319
+ return expandoStore;
3320
+ }
3321
+
3322
+
3323
+ function jqLiteData(element, key, value) {
3324
+ if (jqLiteAcceptsData(element)) {
3325
+ var prop;
3326
+
3327
+ var isSimpleSetter = isDefined(value);
3328
+ var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
3329
+ var massGetter = !key;
3330
+ var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
3331
+ var data = expandoStore && expandoStore.data;
3332
+
3333
+ if (isSimpleSetter) { // data('key', value)
3334
+ data[kebabToCamel(key)] = value;
3335
+ } else {
3336
+ if (massGetter) { // data()
3337
+ return data;
3338
+ } else {
3339
+ if (isSimpleGetter) { // data('key')
3340
+ // don't force creation of expandoStore if it doesn't exist yet
3341
+ return data && data[kebabToCamel(key)];
3342
+ } else { // mass-setter: data({key1: val1, key2: val2})
3343
+ for (prop in key) {
3344
+ data[kebabToCamel(prop)] = key[prop];
3345
+ }
3346
+ }
3347
+ }
3348
+ }
3349
+ }
3350
+ }
3351
+
3352
+ function jqLiteHasClass(element, selector) {
3353
+ if (!element.getAttribute) return false;
3354
+ return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' ').
3355
+ indexOf(' ' + selector + ' ') > -1);
3356
+ }
3357
+
3358
+ function jqLiteRemoveClass(element, cssClasses) {
3359
+ if (cssClasses && element.setAttribute) {
3360
+ forEach(cssClasses.split(' '), function(cssClass) {
3361
+ element.setAttribute('class', trim(
3362
+ (' ' + (element.getAttribute('class') || '') + ' ')
3363
+ .replace(/[\n\t]/g, ' ')
3364
+ .replace(' ' + trim(cssClass) + ' ', ' '))
3365
+ );
3366
+ });
3367
+ }
3368
+ }
3369
+
3370
+ function jqLiteAddClass(element, cssClasses) {
3371
+ if (cssClasses && element.setAttribute) {
3372
+ var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
3373
+ .replace(/[\n\t]/g, ' ');
3374
+
3375
+ forEach(cssClasses.split(' '), function(cssClass) {
3376
+ cssClass = trim(cssClass);
3377
+ if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
3378
+ existingClasses += cssClass + ' ';
3379
+ }
3380
+ });
3381
+
3382
+ element.setAttribute('class', trim(existingClasses));
3383
+ }
3384
+ }
3385
+
3386
+
3387
+ function jqLiteAddNodes(root, elements) {
3388
+ // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
3389
+
3390
+ if (elements) {
3391
+
3392
+ // if a Node (the most common case)
3393
+ if (elements.nodeType) {
3394
+ root[root.length++] = elements;
3395
+ } else {
3396
+ var length = elements.length;
3397
+
3398
+ // if an Array or NodeList and not a Window
3399
+ if (typeof length === 'number' && elements.window !== elements) {
3400
+ if (length) {
3401
+ for (var i = 0; i < length; i++) {
3402
+ root[root.length++] = elements[i];
3403
+ }
3404
+ }
3405
+ } else {
3406
+ root[root.length++] = elements;
3407
+ }
3408
+ }
3409
+ }
3410
+ }
3411
+
3412
+
3413
+ function jqLiteController(element, name) {
3414
+ return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
3415
+ }
3416
+
3417
+ function jqLiteInheritedData(element, name, value) {
3418
+ // if element is the document object work with the html element instead
3419
+ // this makes $(document).scope() possible
3420
+ if (element.nodeType === NODE_TYPE_DOCUMENT) {
3421
+ element = element.documentElement;
3422
+ }
3423
+ var names = isArray(name) ? name : [name];
3424
+
3425
+ while (element) {
3426
+ for (var i = 0, ii = names.length; i < ii; i++) {
3427
+ if (isDefined(value = jqLite.data(element, names[i]))) return value;
3428
+ }
3429
+
3430
+ // If dealing with a document fragment node with a host element, and no parent, use the host
3431
+ // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
3432
+ // to lookup parent controllers.
3433
+ element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
3434
+ }
3435
+ }
3436
+
3437
+ function jqLiteEmpty(element) {
3438
+ jqLiteDealoc(element, true);
3439
+ while (element.firstChild) {
3440
+ element.removeChild(element.firstChild);
3441
+ }
3442
+ }
3443
+
3444
+ function jqLiteRemove(element, keepData) {
3445
+ if (!keepData) jqLiteDealoc(element);
3446
+ var parent = element.parentNode;
3447
+ if (parent) parent.removeChild(element);
3448
+ }
3449
+
3450
+
3451
+ function jqLiteDocumentLoaded(action, win) {
3452
+ win = win || window;
3453
+ if (win.document.readyState === 'complete') {
3454
+ // Force the action to be run async for consistent behavior
3455
+ // from the action's point of view
3456
+ // i.e. it will definitely not be in a $apply
3457
+ win.setTimeout(action);
3458
+ } else {
3459
+ // No need to unbind this handler as load is only ever called once
3460
+ jqLite(win).on('load', action);
3461
+ }
3462
+ }
3463
+
3464
+ function jqLiteReady(fn) {
3465
+ function trigger() {
3466
+ window.document.removeEventListener('DOMContentLoaded', trigger);
3467
+ window.removeEventListener('load', trigger);
3468
+ fn();
3469
+ }
3470
+
3471
+ // check if document is already loaded
3472
+ if (window.document.readyState === 'complete') {
3473
+ window.setTimeout(fn);
3474
+ } else {
3475
+ // We can not use jqLite since we are not done loading and jQuery could be loaded later.
3476
+
3477
+ // Works for modern browsers and IE9
3478
+ window.document.addEventListener('DOMContentLoaded', trigger);
3479
+
3480
+ // Fallback to window.onload for others
3481
+ window.addEventListener('load', trigger);
3482
+ }
3483
+ }
3484
+
3485
+ //////////////////////////////////////////
3486
+ // Functions which are declared directly.
3487
+ //////////////////////////////////////////
3488
+ var JQLitePrototype = JQLite.prototype = {
3489
+ ready: jqLiteReady,
3490
+ toString: function() {
3491
+ var value = [];
3492
+ forEach(this, function(e) { value.push('' + e);});
3493
+ return '[' + value.join(', ') + ']';
3494
+ },
3495
+
3496
+ eq: function(index) {
3497
+ return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3498
+ },
3499
+
3500
+ length: 0,
3501
+ push: push,
3502
+ sort: [].sort,
3503
+ splice: [].splice
3504
+ };
3505
+
3506
+ //////////////////////////////////////////
3507
+ // Functions iterating getter/setters.
3508
+ // these functions return self on setter and
3509
+ // value on get.
3510
+ //////////////////////////////////////////
3511
+ var BOOLEAN_ATTR = {};
3512
+ forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3513
+ BOOLEAN_ATTR[lowercase(value)] = value;
3514
+ });
3515
+ var BOOLEAN_ELEMENTS = {};
3516
+ forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3517
+ BOOLEAN_ELEMENTS[value] = true;
3518
+ });
3519
+ var ALIASED_ATTR = {
3520
+ 'ngMinlength': 'minlength',
3521
+ 'ngMaxlength': 'maxlength',
3522
+ 'ngMin': 'min',
3523
+ 'ngMax': 'max',
3524
+ 'ngPattern': 'pattern',
3525
+ 'ngStep': 'step'
3526
+ };
3527
+
3528
+ function getBooleanAttrName(element, name) {
3529
+ // check dom last since we will most likely fail on name
3530
+ var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3531
+
3532
+ // booleanAttr is here twice to minimize DOM access
3533
+ return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3534
+ }
3535
+
3536
+ function getAliasedAttrName(name) {
3537
+ return ALIASED_ATTR[name];
3538
+ }
3539
+
3540
+ forEach({
3541
+ data: jqLiteData,
3542
+ removeData: jqLiteRemoveData,
3543
+ hasData: jqLiteHasData,
3544
+ cleanData: function jqLiteCleanData(nodes) {
3545
+ for (var i = 0, ii = nodes.length; i < ii; i++) {
3546
+ jqLiteRemoveData(nodes[i]);
3547
+ }
3548
+ }
3549
+ }, function(fn, name) {
3550
+ JQLite[name] = fn;
3551
+ });
3552
+
3553
+ forEach({
3554
+ data: jqLiteData,
3555
+ inheritedData: jqLiteInheritedData,
3556
+
3557
+ scope: function(element) {
3558
+ // Can't use jqLiteData here directly so we stay compatible with jQuery!
3559
+ return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3560
+ },
3561
+
3562
+ isolateScope: function(element) {
3563
+ // Can't use jqLiteData here directly so we stay compatible with jQuery!
3564
+ return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3565
+ },
3566
+
3567
+ controller: jqLiteController,
3568
+
3569
+ injector: function(element) {
3570
+ return jqLiteInheritedData(element, '$injector');
3571
+ },
3572
+
3573
+ removeAttr: function(element, name) {
3574
+ element.removeAttribute(name);
3575
+ },
3576
+
3577
+ hasClass: jqLiteHasClass,
3578
+
3579
+ css: function(element, name, value) {
3580
+ name = cssKebabToCamel(name);
3581
+
3582
+ if (isDefined(value)) {
3583
+ element.style[name] = value;
3584
+ } else {
3585
+ return element.style[name];
3586
+ }
3587
+ },
3588
+
3589
+ attr: function(element, name, value) {
3590
+ var ret;
3591
+ var nodeType = element.nodeType;
3592
+ if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT ||
3593
+ !element.getAttribute) {
3594
+ return;
3595
+ }
3596
+
3597
+ var lowercasedName = lowercase(name);
3598
+ var isBooleanAttr = BOOLEAN_ATTR[lowercasedName];
3599
+
3600
+ if (isDefined(value)) {
3601
+ // setter
3602
+
3603
+ if (value === null || (value === false && isBooleanAttr)) {
3604
+ element.removeAttribute(name);
3605
+ } else {
3606
+ element.setAttribute(name, isBooleanAttr ? lowercasedName : value);
3607
+ }
3608
+ } else {
3609
+ // getter
3610
+
3611
+ ret = element.getAttribute(name);
3612
+
3613
+ if (isBooleanAttr && ret !== null) {
3614
+ ret = lowercasedName;
3615
+ }
3616
+ // Normalize non-existing attributes to undefined (as jQuery).
3617
+ return ret === null ? undefined : ret;
3618
+ }
3619
+ },
3620
+
3621
+ prop: function(element, name, value) {
3622
+ if (isDefined(value)) {
3623
+ element[name] = value;
3624
+ } else {
3625
+ return element[name];
3626
+ }
3627
+ },
3628
+
3629
+ text: (function() {
3630
+ getText.$dv = '';
3631
+ return getText;
3632
+
3633
+ function getText(element, value) {
3634
+ if (isUndefined(value)) {
3635
+ var nodeType = element.nodeType;
3636
+ return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3637
+ }
3638
+ element.textContent = value;
3639
+ }
3640
+ })(),
3641
+
3642
+ val: function(element, value) {
3643
+ if (isUndefined(value)) {
3644
+ if (element.multiple && nodeName_(element) === 'select') {
3645
+ var result = [];
3646
+ forEach(element.options, function(option) {
3647
+ if (option.selected) {
3648
+ result.push(option.value || option.text);
3649
+ }
3650
+ });
3651
+ return result;
3652
+ }
3653
+ return element.value;
3654
+ }
3655
+ element.value = value;
3656
+ },
3657
+
3658
+ html: function(element, value) {
3659
+ if (isUndefined(value)) {
3660
+ return element.innerHTML;
3661
+ }
3662
+ jqLiteDealoc(element, true);
3663
+ element.innerHTML = value;
3664
+ },
3665
+
3666
+ empty: jqLiteEmpty
3667
+ }, function(fn, name) {
3668
+ /**
3669
+ * Properties: writes return selection, reads return first value
3670
+ */
3671
+ JQLite.prototype[name] = function(arg1, arg2) {
3672
+ var i, key;
3673
+ var nodeCount = this.length;
3674
+
3675
+ // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3676
+ // in a way that survives minification.
3677
+ // jqLiteEmpty takes no arguments but is a setter.
3678
+ if (fn !== jqLiteEmpty &&
3679
+ (isUndefined((fn.length === 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
3680
+ if (isObject(arg1)) {
3681
+
3682
+ // we are a write, but the object properties are the key/values
3683
+ for (i = 0; i < nodeCount; i++) {
3684
+ if (fn === jqLiteData) {
3685
+ // data() takes the whole object in jQuery
3686
+ fn(this[i], arg1);
3687
+ } else {
3688
+ for (key in arg1) {
3689
+ fn(this[i], key, arg1[key]);
3690
+ }
3691
+ }
3692
+ }
3693
+ // return self for chaining
3694
+ return this;
3695
+ } else {
3696
+ // we are a read, so read the first child.
3697
+ // TODO: do we still need this?
3698
+ var value = fn.$dv;
3699
+ // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3700
+ var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3701
+ for (var j = 0; j < jj; j++) {
3702
+ var nodeValue = fn(this[j], arg1, arg2);
3703
+ value = value ? value + nodeValue : nodeValue;
3704
+ }
3705
+ return value;
3706
+ }
3707
+ } else {
3708
+ // we are a write, so apply to all children
3709
+ for (i = 0; i < nodeCount; i++) {
3710
+ fn(this[i], arg1, arg2);
3711
+ }
3712
+ // return self for chaining
3713
+ return this;
3714
+ }
3715
+ };
3716
+ });
3717
+
3718
+ function createEventHandler(element, events) {
3719
+ var eventHandler = function(event, type) {
3720
+ // jQuery specific api
3721
+ event.isDefaultPrevented = function() {
3722
+ return event.defaultPrevented;
3723
+ };
3724
+
3725
+ var eventFns = events[type || event.type];
3726
+ var eventFnsLength = eventFns ? eventFns.length : 0;
3727
+
3728
+ if (!eventFnsLength) return;
3729
+
3730
+ if (isUndefined(event.immediatePropagationStopped)) {
3731
+ var originalStopImmediatePropagation = event.stopImmediatePropagation;
3732
+ event.stopImmediatePropagation = function() {
3733
+ event.immediatePropagationStopped = true;
3734
+
3735
+ if (event.stopPropagation) {
3736
+ event.stopPropagation();
3737
+ }
3738
+
3739
+ if (originalStopImmediatePropagation) {
3740
+ originalStopImmediatePropagation.call(event);
3741
+ }
3742
+ };
3743
+ }
3744
+
3745
+ event.isImmediatePropagationStopped = function() {
3746
+ return event.immediatePropagationStopped === true;
3747
+ };
3748
+
3749
+ // Some events have special handlers that wrap the real handler
3750
+ var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
3751
+
3752
+ // Copy event handlers in case event handlers array is modified during execution.
3753
+ if ((eventFnsLength > 1)) {
3754
+ eventFns = shallowCopy(eventFns);
3755
+ }
3756
+
3757
+ for (var i = 0; i < eventFnsLength; i++) {
3758
+ if (!event.isImmediatePropagationStopped()) {
3759
+ handlerWrapper(element, event, eventFns[i]);
3760
+ }
3761
+ }
3762
+ };
3763
+
3764
+ // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3765
+ // events on `element`
3766
+ eventHandler.elem = element;
3767
+ return eventHandler;
3768
+ }
3769
+
3770
+ function defaultHandlerWrapper(element, event, handler) {
3771
+ handler.call(element, event);
3772
+ }
3773
+
3774
+ function specialMouseHandlerWrapper(target, event, handler) {
3775
+ // Refer to jQuery's implementation of mouseenter & mouseleave
3776
+ // Read about mouseenter and mouseleave:
3777
+ // http://www.quirksmode.org/js/events_mouse.html#link8
3778
+ var related = event.relatedTarget;
3779
+ // For mousenter/leave call the handler if related is outside the target.
3780
+ // NB: No relatedTarget if the mouse left/entered the browser window
3781
+ if (!related || (related !== target && !jqLiteContains.call(target, related))) {
3782
+ handler.call(target, event);
3783
+ }
3784
+ }
3785
+
3786
+ //////////////////////////////////////////
3787
+ // Functions iterating traversal.
3788
+ // These functions chain results into a single
3789
+ // selector.
3790
+ //////////////////////////////////////////
3791
+ forEach({
3792
+ removeData: jqLiteRemoveData,
3793
+
3794
+ on: function jqLiteOn(element, type, fn, unsupported) {
3795
+ if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3796
+
3797
+ // Do not add event handlers to non-elements because they will not be cleaned up.
3798
+ if (!jqLiteAcceptsData(element)) {
3799
+ return;
3800
+ }
3801
+
3802
+ var expandoStore = jqLiteExpandoStore(element, true);
3803
+ var events = expandoStore.events;
3804
+ var handle = expandoStore.handle;
3805
+
3806
+ if (!handle) {
3807
+ handle = expandoStore.handle = createEventHandler(element, events);
3808
+ }
3809
+
3810
+ // http://jsperf.com/string-indexof-vs-split
3811
+ var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3812
+ var i = types.length;
3813
+
3814
+ var addHandler = function(type, specialHandlerWrapper, noEventListener) {
3815
+ var eventFns = events[type];
3816
+
3817
+ if (!eventFns) {
3818
+ eventFns = events[type] = [];
3819
+ eventFns.specialHandlerWrapper = specialHandlerWrapper;
3820
+ if (type !== '$destroy' && !noEventListener) {
3821
+ element.addEventListener(type, handle);
3822
+ }
3823
+ }
3824
+
3825
+ eventFns.push(fn);
3826
+ };
3827
+
3828
+ while (i--) {
3829
+ type = types[i];
3830
+ if (MOUSE_EVENT_MAP[type]) {
3831
+ addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
3832
+ addHandler(type, undefined, true);
3833
+ } else {
3834
+ addHandler(type);
3835
+ }
3836
+ }
3837
+ },
3838
+
3839
+ off: jqLiteOff,
3840
+
3841
+ one: function(element, type, fn) {
3842
+ element = jqLite(element);
3843
+
3844
+ //add the listener twice so that when it is called
3845
+ //you can remove the original function and still be
3846
+ //able to call element.off(ev, fn) normally
3847
+ element.on(type, function onFn() {
3848
+ element.off(type, fn);
3849
+ element.off(type, onFn);
3850
+ });
3851
+ element.on(type, fn);
3852
+ },
3853
+
3854
+ replaceWith: function(element, replaceNode) {
3855
+ var index, parent = element.parentNode;
3856
+ jqLiteDealoc(element);
3857
+ forEach(new JQLite(replaceNode), function(node) {
3858
+ if (index) {
3859
+ parent.insertBefore(node, index.nextSibling);
3860
+ } else {
3861
+ parent.replaceChild(node, element);
3862
+ }
3863
+ index = node;
3864
+ });
3865
+ },
3866
+
3867
+ children: function(element) {
3868
+ var children = [];
3869
+ forEach(element.childNodes, function(element) {
3870
+ if (element.nodeType === NODE_TYPE_ELEMENT) {
3871
+ children.push(element);
3872
+ }
3873
+ });
3874
+ return children;
3875
+ },
3876
+
3877
+ contents: function(element) {
3878
+ return element.contentDocument || element.childNodes || [];
3879
+ },
3880
+
3881
+ append: function(element, node) {
3882
+ var nodeType = element.nodeType;
3883
+ if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3884
+
3885
+ node = new JQLite(node);
3886
+
3887
+ for (var i = 0, ii = node.length; i < ii; i++) {
3888
+ var child = node[i];
3889
+ element.appendChild(child);
3890
+ }
3891
+ },
3892
+
3893
+ prepend: function(element, node) {
3894
+ if (element.nodeType === NODE_TYPE_ELEMENT) {
3895
+ var index = element.firstChild;
3896
+ forEach(new JQLite(node), function(child) {
3897
+ element.insertBefore(child, index);
3898
+ });
3899
+ }
3900
+ },
3901
+
3902
+ wrap: function(element, wrapNode) {
3903
+ jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]);
3904
+ },
3905
+
3906
+ remove: jqLiteRemove,
3907
+
3908
+ detach: function(element) {
3909
+ jqLiteRemove(element, true);
3910
+ },
3911
+
3912
+ after: function(element, newElement) {
3913
+ var index = element, parent = element.parentNode;
3914
+
3915
+ if (parent) {
3916
+ newElement = new JQLite(newElement);
3917
+
3918
+ for (var i = 0, ii = newElement.length; i < ii; i++) {
3919
+ var node = newElement[i];
3920
+ parent.insertBefore(node, index.nextSibling);
3921
+ index = node;
3922
+ }
3923
+ }
3924
+ },
3925
+
3926
+ addClass: jqLiteAddClass,
3927
+ removeClass: jqLiteRemoveClass,
3928
+
3929
+ toggleClass: function(element, selector, condition) {
3930
+ if (selector) {
3931
+ forEach(selector.split(' '), function(className) {
3932
+ var classCondition = condition;
3933
+ if (isUndefined(classCondition)) {
3934
+ classCondition = !jqLiteHasClass(element, className);
3935
+ }
3936
+ (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
3937
+ });
3938
+ }
3939
+ },
3940
+
3941
+ parent: function(element) {
3942
+ var parent = element.parentNode;
3943
+ return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3944
+ },
3945
+
3946
+ next: function(element) {
3947
+ return element.nextElementSibling;
3948
+ },
3949
+
3950
+ find: function(element, selector) {
3951
+ if (element.getElementsByTagName) {
3952
+ return element.getElementsByTagName(selector);
3953
+ } else {
3954
+ return [];
3955
+ }
3956
+ },
3957
+
3958
+ clone: jqLiteClone,
3959
+
3960
+ triggerHandler: function(element, event, extraParameters) {
3961
+
3962
+ var dummyEvent, eventFnsCopy, handlerArgs;
3963
+ var eventName = event.type || event;
3964
+ var expandoStore = jqLiteExpandoStore(element);
3965
+ var events = expandoStore && expandoStore.events;
3966
+ var eventFns = events && events[eventName];
3967
+
3968
+ if (eventFns) {
3969
+ // Create a dummy event to pass to the handlers
3970
+ dummyEvent = {
3971
+ preventDefault: function() { this.defaultPrevented = true; },
3972
+ isDefaultPrevented: function() { return this.defaultPrevented === true; },
3973
+ stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
3974
+ isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
3975
+ stopPropagation: noop,
3976
+ type: eventName,
3977
+ target: element
3978
+ };
3979
+
3980
+ // If a custom event was provided then extend our dummy event with it
3981
+ if (event.type) {
3982
+ dummyEvent = extend(dummyEvent, event);
3983
+ }
3984
+
3985
+ // Copy event handlers in case event handlers array is modified during execution.
3986
+ eventFnsCopy = shallowCopy(eventFns);
3987
+ handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
3988
+
3989
+ forEach(eventFnsCopy, function(fn) {
3990
+ if (!dummyEvent.isImmediatePropagationStopped()) {
3991
+ fn.apply(element, handlerArgs);
3992
+ }
3993
+ });
3994
+ }
3995
+ }
3996
+ }, function(fn, name) {
3997
+ /**
3998
+ * chaining functions
3999
+ */
4000
+ JQLite.prototype[name] = function(arg1, arg2, arg3) {
4001
+ var value;
4002
+
4003
+ for (var i = 0, ii = this.length; i < ii; i++) {
4004
+ if (isUndefined(value)) {
4005
+ value = fn(this[i], arg1, arg2, arg3);
4006
+ if (isDefined(value)) {
4007
+ // any function which returns a value needs to be wrapped
4008
+ value = jqLite(value);
4009
+ }
4010
+ } else {
4011
+ jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
4012
+ }
4013
+ }
4014
+ return isDefined(value) ? value : this;
4015
+ };
4016
+ });
4017
+
4018
+ // bind legacy bind/unbind to on/off
4019
+ JQLite.prototype.bind = JQLite.prototype.on;
4020
+ JQLite.prototype.unbind = JQLite.prototype.off;
4021
+
4022
+
4023
+ // Provider for private $$jqLite service
4024
+ /** @this */
4025
+ function $$jqLiteProvider() {
4026
+ this.$get = function $$jqLite() {
4027
+ return extend(JQLite, {
4028
+ hasClass: function(node, classes) {
4029
+ if (node.attr) node = node[0];
4030
+ return jqLiteHasClass(node, classes);
4031
+ },
4032
+ addClass: function(node, classes) {
4033
+ if (node.attr) node = node[0];
4034
+ return jqLiteAddClass(node, classes);
4035
+ },
4036
+ removeClass: function(node, classes) {
4037
+ if (node.attr) node = node[0];
4038
+ return jqLiteRemoveClass(node, classes);
4039
+ }
4040
+ });
4041
+ };
4042
+ }
4043
+
4044
+ /**
4045
+ * Computes a hash of an 'obj'.
4046
+ * Hash of a:
4047
+ * string is string
4048
+ * number is number as string
4049
+ * object is either result of calling $$hashKey function on the object or uniquely generated id,
4050
+ * that is also assigned to the $$hashKey property of the object.
4051
+ *
4052
+ * @param obj
4053
+ * @returns {string} hash string such that the same input will have the same hash string.
4054
+ * The resulting string key is in 'type:hashKey' format.
4055
+ */
4056
+ function hashKey(obj, nextUidFn) {
4057
+ var key = obj && obj.$$hashKey;
4058
+
4059
+ if (key) {
4060
+ if (typeof key === 'function') {
4061
+ key = obj.$$hashKey();
4062
+ }
4063
+ return key;
4064
+ }
4065
+
4066
+ var objType = typeof obj;
4067
+ if (objType === 'function' || (objType === 'object' && obj !== null)) {
4068
+ key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
4069
+ } else {
4070
+ key = objType + ':' + obj;
4071
+ }
4072
+
4073
+ return key;
4074
+ }
4075
+
4076
+ // A minimal ES2015 Map implementation.
4077
+ // Should be bug/feature equivalent to the native implementations of supported browsers
4078
+ // (for the features required in Angular).
4079
+ // See https://kangax.github.io/compat-table/es6/#test-Map
4080
+ var nanKey = Object.create(null);
4081
+ function NgMapShim() {
4082
+ this._keys = [];
4083
+ this._values = [];
4084
+ this._lastKey = NaN;
4085
+ this._lastIndex = -1;
4086
+ }
4087
+ NgMapShim.prototype = {
4088
+ _idx: function(key) {
4089
+ if (key === this._lastKey) {
4090
+ return this._lastIndex;
4091
+ }
4092
+ this._lastKey = key;
4093
+ this._lastIndex = this._keys.indexOf(key);
4094
+ return this._lastIndex;
4095
+ },
4096
+ _transformKey: function(key) {
4097
+ return isNumberNaN(key) ? nanKey : key;
4098
+ },
4099
+ get: function(key) {
4100
+ key = this._transformKey(key);
4101
+ var idx = this._idx(key);
4102
+ if (idx !== -1) {
4103
+ return this._values[idx];
4104
+ }
4105
+ },
4106
+ set: function(key, value) {
4107
+ key = this._transformKey(key);
4108
+ var idx = this._idx(key);
4109
+ if (idx === -1) {
4110
+ idx = this._lastIndex = this._keys.length;
4111
+ }
4112
+ this._keys[idx] = key;
4113
+ this._values[idx] = value;
4114
+
4115
+ // Support: IE11
4116
+ // Do not `return this` to simulate the partial IE11 implementation
4117
+ },
4118
+ delete: function(key) {
4119
+ key = this._transformKey(key);
4120
+ var idx = this._idx(key);
4121
+ if (idx === -1) {
4122
+ return false;
4123
+ }
4124
+ this._keys.splice(idx, 1);
4125
+ this._values.splice(idx, 1);
4126
+ this._lastKey = NaN;
4127
+ this._lastIndex = -1;
4128
+ return true;
4129
+ }
4130
+ };
4131
+
4132
+ // For now, always use `NgMapShim`, even if `window.Map` is available. Some native implementations
4133
+ // are still buggy (often in subtle ways) and can cause hard-to-debug failures. When native `Map`
4134
+ // implementations get more stable, we can reconsider switching to `window.Map` (when available).
4135
+ var NgMap = NgMapShim;
4136
+
4137
+ var $$MapProvider = [/** @this */function() {
4138
+ this.$get = [function() {
4139
+ return NgMap;
4140
+ }];
4141
+ }];
4142
+
4143
+ /**
4144
+ * @ngdoc function
4145
+ * @module ng
4146
+ * @name angular.injector
4147
+ * @kind function
4148
+ *
4149
+ * @description
4150
+ * Creates an injector object that can be used for retrieving services as well as for
4151
+ * dependency injection (see {@link guide/di dependency injection}).
4152
+ *
4153
+ * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
4154
+ * {@link angular.module}. The `ng` module must be explicitly added.
4155
+ * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
4156
+ * disallows argument name annotation inference.
4157
+ * @returns {injector} Injector object. See {@link auto.$injector $injector}.
4158
+ *
4159
+ * @example
4160
+ * Typical usage
4161
+ * ```js
4162
+ * // create an injector
4163
+ * var $injector = angular.injector(['ng']);
4164
+ *
4165
+ * // use the injector to kick off your application
4166
+ * // use the type inference to auto inject arguments, or use implicit injection
4167
+ * $injector.invoke(function($rootScope, $compile, $document) {
4168
+ * $compile($document)($rootScope);
4169
+ * $rootScope.$digest();
4170
+ * });
4171
+ * ```
4172
+ *
4173
+ * Sometimes you want to get access to the injector of a currently running Angular app
4174
+ * from outside Angular. Perhaps, you want to inject and compile some markup after the
4175
+ * application has been bootstrapped. You can do this using the extra `injector()` added
4176
+ * to JQuery/jqLite elements. See {@link angular.element}.
4177
+ *
4178
+ * *This is fairly rare but could be the case if a third party library is injecting the
4179
+ * markup.*
4180
+ *
4181
+ * In the following example a new block of HTML containing a `ng-controller`
4182
+ * directive is added to the end of the document body by JQuery. We then compile and link
4183
+ * it into the current AngularJS scope.
4184
+ *
4185
+ * ```js
4186
+ * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
4187
+ * $(document.body).append($div);
4188
+ *
4189
+ * angular.element(document).injector().invoke(function($compile) {
4190
+ * var scope = angular.element($div).scope();
4191
+ * $compile($div)(scope);
4192
+ * });
4193
+ * ```
4194
+ */
4195
+
4196
+
4197
+ /**
4198
+ * @ngdoc module
4199
+ * @name auto
4200
+ * @installation
4201
+ * @description
4202
+ *
4203
+ * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
4204
+ */
4205
+
4206
+ var ARROW_ARG = /^([^(]+?)=>/;
4207
+ var FN_ARGS = /^[^(]*\(\s*([^)]*)\)/m;
4208
+ var FN_ARG_SPLIT = /,/;
4209
+ var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
4210
+ var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
4211
+ var $injectorMinErr = minErr('$injector');
4212
+
4213
+ function stringifyFn(fn) {
4214
+ return Function.prototype.toString.call(fn);
4215
+ }
4216
+
4217
+ function extractArgs(fn) {
4218
+ var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''),
4219
+ args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
4220
+ return args;
4221
+ }
4222
+
4223
+ function anonFn(fn) {
4224
+ // For anonymous functions, showing at the very least the function signature can help in
4225
+ // debugging.
4226
+ var args = extractArgs(fn);
4227
+ if (args) {
4228
+ return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
4229
+ }
4230
+ return 'fn';
4231
+ }
4232
+
4233
+ function annotate(fn, strictDi, name) {
4234
+ var $inject,
4235
+ argDecl,
4236
+ last;
4237
+
4238
+ if (typeof fn === 'function') {
4239
+ if (!($inject = fn.$inject)) {
4240
+ $inject = [];
4241
+ if (fn.length) {
4242
+ if (strictDi) {
4243
+ if (!isString(name) || !name) {
4244
+ name = fn.name || anonFn(fn);
4245
+ }
4246
+ throw $injectorMinErr('strictdi',
4247
+ '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
4248
+ }
4249
+ argDecl = extractArgs(fn);
4250
+ forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
4251
+ arg.replace(FN_ARG, function(all, underscore, name) {
4252
+ $inject.push(name);
4253
+ });
4254
+ });
4255
+ }
4256
+ fn.$inject = $inject;
4257
+ }
4258
+ } else if (isArray(fn)) {
4259
+ last = fn.length - 1;
4260
+ assertArgFn(fn[last], 'fn');
4261
+ $inject = fn.slice(0, last);
4262
+ } else {
4263
+ assertArgFn(fn, 'fn', true);
4264
+ }
4265
+ return $inject;
4266
+ }
4267
+
4268
+ ///////////////////////////////////////
4269
+
4270
+ /**
4271
+ * @ngdoc service
4272
+ * @name $injector
4273
+ *
4274
+ * @description
4275
+ *
4276
+ * `$injector` is used to retrieve object instances as defined by
4277
+ * {@link auto.$provide provider}, instantiate types, invoke methods,
4278
+ * and load modules.
4279
+ *
4280
+ * The following always holds true:
4281
+ *
4282
+ * ```js
4283
+ * var $injector = angular.injector();
4284
+ * expect($injector.get('$injector')).toBe($injector);
4285
+ * expect($injector.invoke(function($injector) {
4286
+ * return $injector;
4287
+ * })).toBe($injector);
4288
+ * ```
4289
+ *
4290
+ * # Injection Function Annotation
4291
+ *
4292
+ * JavaScript does not have annotations, and annotations are needed for dependency injection. The
4293
+ * following are all valid ways of annotating function with injection arguments and are equivalent.
4294
+ *
4295
+ * ```js
4296
+ * // inferred (only works if code not minified/obfuscated)
4297
+ * $injector.invoke(function(serviceA){});
4298
+ *
4299
+ * // annotated
4300
+ * function explicit(serviceA) {};
4301
+ * explicit.$inject = ['serviceA'];
4302
+ * $injector.invoke(explicit);
4303
+ *
4304
+ * // inline
4305
+ * $injector.invoke(['serviceA', function(serviceA){}]);
4306
+ * ```
4307
+ *
4308
+ * ## Inference
4309
+ *
4310
+ * In JavaScript calling `toString()` on a function returns the function definition. The definition
4311
+ * can then be parsed and the function arguments can be extracted. This method of discovering
4312
+ * annotations is disallowed when the injector is in strict mode.
4313
+ * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
4314
+ * argument names.
4315
+ *
4316
+ * ## `$inject` Annotation
4317
+ * By adding an `$inject` property onto a function the injection parameters can be specified.
4318
+ *
4319
+ * ## Inline
4320
+ * As an array of injection names, where the last item in the array is the function to call.
4321
+ */
4322
+
4323
+ /**
4324
+ * @ngdoc property
4325
+ * @name $injector#modules
4326
+ * @type {Object}
4327
+ * @description
4328
+ * A hash containing all the modules that have been loaded into the
4329
+ * $injector.
4330
+ *
4331
+ * You can use this property to find out information about a module via the
4332
+ * {@link angular.Module#info `myModule.info(...)`} method.
4333
+ *
4334
+ * For example:
4335
+ *
4336
+ * ```
4337
+ * var info = $injector.modules['ngAnimate'].info();
4338
+ * ```
4339
+ *
4340
+ * **Do not use this property to attempt to modify the modules after the application
4341
+ * has been bootstrapped.**
4342
+ */
4343
+
4344
+
4345
+ /**
4346
+ * @ngdoc method
4347
+ * @name $injector#get
4348
+ *
4349
+ * @description
4350
+ * Return an instance of the service.
4351
+ *
4352
+ * @param {string} name The name of the instance to retrieve.
4353
+ * @param {string=} caller An optional string to provide the origin of the function call for error messages.
4354
+ * @return {*} The instance.
4355
+ */
4356
+
4357
+ /**
4358
+ * @ngdoc method
4359
+ * @name $injector#invoke
4360
+ *
4361
+ * @description
4362
+ * Invoke the method and supply the method arguments from the `$injector`.
4363
+ *
4364
+ * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
4365
+ * injected according to the {@link guide/di $inject Annotation} rules.
4366
+ * @param {Object=} self The `this` for the invoked method.
4367
+ * @param {Object=} locals Optional object. If preset then any argument names are read from this
4368
+ * object first, before the `$injector` is consulted.
4369
+ * @returns {*} the value returned by the invoked `fn` function.
4370
+ */
4371
+
4372
+ /**
4373
+ * @ngdoc method
4374
+ * @name $injector#has
4375
+ *
4376
+ * @description
4377
+ * Allows the user to query if the particular service exists.
4378
+ *
4379
+ * @param {string} name Name of the service to query.
4380
+ * @returns {boolean} `true` if injector has given service.
4381
+ */
4382
+
4383
+ /**
4384
+ * @ngdoc method
4385
+ * @name $injector#instantiate
4386
+ * @description
4387
+ * Create a new instance of JS type. The method takes a constructor function, invokes the new
4388
+ * operator, and supplies all of the arguments to the constructor function as specified by the
4389
+ * constructor annotation.
4390
+ *
4391
+ * @param {Function} Type Annotated constructor function.
4392
+ * @param {Object=} locals Optional object. If preset then any argument names are read from this
4393
+ * object first, before the `$injector` is consulted.
4394
+ * @returns {Object} new instance of `Type`.
4395
+ */
4396
+
4397
+ /**
4398
+ * @ngdoc method
4399
+ * @name $injector#annotate
4400
+ *
4401
+ * @description
4402
+ * Returns an array of service names which the function is requesting for injection. This API is
4403
+ * used by the injector to determine which services need to be injected into the function when the
4404
+ * function is invoked. There are three ways in which the function can be annotated with the needed
4405
+ * dependencies.
4406
+ *
4407
+ * # Argument names
4408
+ *
4409
+ * The simplest form is to extract the dependencies from the arguments of the function. This is done
4410
+ * by converting the function into a string using `toString()` method and extracting the argument
4411
+ * names.
4412
+ * ```js
4413
+ * // Given
4414
+ * function MyController($scope, $route) {
4415
+ * // ...
4416
+ * }
4417
+ *
4418
+ * // Then
4419
+ * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4420
+ * ```
4421
+ *
4422
+ * You can disallow this method by using strict injection mode.
4423
+ *
4424
+ * This method does not work with code minification / obfuscation. For this reason the following
4425
+ * annotation strategies are supported.
4426
+ *
4427
+ * # The `$inject` property
4428
+ *
4429
+ * If a function has an `$inject` property and its value is an array of strings, then the strings
4430
+ * represent names of services to be injected into the function.
4431
+ * ```js
4432
+ * // Given
4433
+ * var MyController = function(obfuscatedScope, obfuscatedRoute) {
4434
+ * // ...
4435
+ * }
4436
+ * // Define function dependencies
4437
+ * MyController['$inject'] = ['$scope', '$route'];
4438
+ *
4439
+ * // Then
4440
+ * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4441
+ * ```
4442
+ *
4443
+ * # The array notation
4444
+ *
4445
+ * It is often desirable to inline Injected functions and that's when setting the `$inject` property
4446
+ * is very inconvenient. In these situations using the array notation to specify the dependencies in
4447
+ * a way that survives minification is a better choice:
4448
+ *
4449
+ * ```js
4450
+ * // We wish to write this (not minification / obfuscation safe)
4451
+ * injector.invoke(function($compile, $rootScope) {
4452
+ * // ...
4453
+ * });
4454
+ *
4455
+ * // We are forced to write break inlining
4456
+ * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
4457
+ * // ...
4458
+ * };
4459
+ * tmpFn.$inject = ['$compile', '$rootScope'];
4460
+ * injector.invoke(tmpFn);
4461
+ *
4462
+ * // To better support inline function the inline annotation is supported
4463
+ * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
4464
+ * // ...
4465
+ * }]);
4466
+ *
4467
+ * // Therefore
4468
+ * expect(injector.annotate(
4469
+ * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
4470
+ * ).toEqual(['$compile', '$rootScope']);
4471
+ * ```
4472
+ *
4473
+ * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
4474
+ * be retrieved as described above.
4475
+ *
4476
+ * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
4477
+ *
4478
+ * @returns {Array.<string>} The names of the services which the function requires.
4479
+ */
4480
+
4481
+
4482
+
4483
+ /**
4484
+ * @ngdoc service
4485
+ * @name $provide
4486
+ *
4487
+ * @description
4488
+ *
4489
+ * The {@link auto.$provide $provide} service has a number of methods for registering components
4490
+ * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
4491
+ * {@link angular.Module}.
4492
+ *
4493
+ * An Angular **service** is a singleton object created by a **service factory**. These **service
4494
+ * factories** are functions which, in turn, are created by a **service provider**.
4495
+ * The **service providers** are constructor functions. When instantiated they must contain a
4496
+ * property called `$get`, which holds the **service factory** function.
4497
+ *
4498
+ * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
4499
+ * correct **service provider**, instantiating it and then calling its `$get` **service factory**
4500
+ * function to get the instance of the **service**.
4501
+ *
4502
+ * Often services have no configuration options and there is no need to add methods to the service
4503
+ * provider. The provider will be no more than a constructor function with a `$get` property. For
4504
+ * these cases the {@link auto.$provide $provide} service has additional helper methods to register
4505
+ * services without specifying a provider.
4506
+ *
4507
+ * * {@link auto.$provide#provider provider(name, provider)} - registers a **service provider** with the
4508
+ * {@link auto.$injector $injector}
4509
+ * * {@link auto.$provide#constant constant(name, obj)} - registers a value/object that can be accessed by
4510
+ * providers and services.
4511
+ * * {@link auto.$provide#value value(name, obj)} - registers a value/object that can only be accessed by
4512
+ * services, not providers.
4513
+ * * {@link auto.$provide#factory factory(name, fn)} - registers a service **factory function**
4514
+ * that will be wrapped in a **service provider** object, whose `$get` property will contain the
4515
+ * given factory function.
4516
+ * * {@link auto.$provide#service service(name, Fn)} - registers a **constructor function**
4517
+ * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
4518
+ * a new object using the given constructor function.
4519
+ * * {@link auto.$provide#decorator decorator(name, decorFn)} - registers a **decorator function** that
4520
+ * will be able to modify or replace the implementation of another service.
4521
+ *
4522
+ * See the individual methods for more information and examples.
4523
+ */
4524
+
4525
+ /**
4526
+ * @ngdoc method
4527
+ * @name $provide#provider
4528
+ * @description
4529
+ *
4530
+ * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4531
+ * are constructor functions, whose instances are responsible for "providing" a factory for a
4532
+ * service.
4533
+ *
4534
+ * Service provider names start with the name of the service they provide followed by `Provider`.
4535
+ * For example, the {@link ng.$log $log} service has a provider called
4536
+ * {@link ng.$logProvider $logProvider}.
4537
+ *
4538
+ * Service provider objects can have additional methods which allow configuration of the provider
4539
+ * and its service. Importantly, you can configure what kind of service is created by the `$get`
4540
+ * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4541
+ * method {@link ng.$logProvider#debugEnabled debugEnabled}
4542
+ * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4543
+ * console or not.
4544
+ *
4545
+ * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4546
+ 'Provider'` key.
4547
+ * @param {(Object|function())} provider If the provider is:
4548
+ *
4549
+ * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4550
+ * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4551
+ * - `Constructor`: a new instance of the provider will be created using
4552
+ * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4553
+ *
4554
+ * @returns {Object} registered provider instance
4555
+
4556
+ * @example
4557
+ *
4558
+ * The following example shows how to create a simple event tracking service and register it using
4559
+ * {@link auto.$provide#provider $provide.provider()}.
4560
+ *
4561
+ * ```js
4562
+ * // Define the eventTracker provider
4563
+ * function EventTrackerProvider() {
4564
+ * var trackingUrl = '/track';
4565
+ *
4566
+ * // A provider method for configuring where the tracked events should been saved
4567
+ * this.setTrackingUrl = function(url) {
4568
+ * trackingUrl = url;
4569
+ * };
4570
+ *
4571
+ * // The service factory function
4572
+ * this.$get = ['$http', function($http) {
4573
+ * var trackedEvents = {};
4574
+ * return {
4575
+ * // Call this to track an event
4576
+ * event: function(event) {
4577
+ * var count = trackedEvents[event] || 0;
4578
+ * count += 1;
4579
+ * trackedEvents[event] = count;
4580
+ * return count;
4581
+ * },
4582
+ * // Call this to save the tracked events to the trackingUrl
4583
+ * save: function() {
4584
+ * $http.post(trackingUrl, trackedEvents);
4585
+ * }
4586
+ * };
4587
+ * }];
4588
+ * }
4589
+ *
4590
+ * describe('eventTracker', function() {
4591
+ * var postSpy;
4592
+ *
4593
+ * beforeEach(module(function($provide) {
4594
+ * // Register the eventTracker provider
4595
+ * $provide.provider('eventTracker', EventTrackerProvider);
4596
+ * }));
4597
+ *
4598
+ * beforeEach(module(function(eventTrackerProvider) {
4599
+ * // Configure eventTracker provider
4600
+ * eventTrackerProvider.setTrackingUrl('/custom-track');
4601
+ * }));
4602
+ *
4603
+ * it('tracks events', inject(function(eventTracker) {
4604
+ * expect(eventTracker.event('login')).toEqual(1);
4605
+ * expect(eventTracker.event('login')).toEqual(2);
4606
+ * }));
4607
+ *
4608
+ * it('saves to the tracking url', inject(function(eventTracker, $http) {
4609
+ * postSpy = spyOn($http, 'post');
4610
+ * eventTracker.event('login');
4611
+ * eventTracker.save();
4612
+ * expect(postSpy).toHaveBeenCalled();
4613
+ * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4614
+ * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4615
+ * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4616
+ * }));
4617
+ * });
4618
+ * ```
4619
+ */
4620
+
4621
+ /**
4622
+ * @ngdoc method
4623
+ * @name $provide#factory
4624
+ * @description
4625
+ *
4626
+ * Register a **service factory**, which will be called to return the service instance.
4627
+ * This is short for registering a service where its provider consists of only a `$get` property,
4628
+ * which is the given service factory function.
4629
+ * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4630
+ * configure your service in a provider.
4631
+ *
4632
+ * @param {string} name The name of the instance.
4633
+ * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4634
+ * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4635
+ * @returns {Object} registered provider instance
4636
+ *
4637
+ * @example
4638
+ * Here is an example of registering a service
4639
+ * ```js
4640
+ * $provide.factory('ping', ['$http', function($http) {
4641
+ * return function ping() {
4642
+ * return $http.send('/ping');
4643
+ * };
4644
+ * }]);
4645
+ * ```
4646
+ * You would then inject and use this service like this:
4647
+ * ```js
4648
+ * someModule.controller('Ctrl', ['ping', function(ping) {
4649
+ * ping();
4650
+ * }]);
4651
+ * ```
4652
+ */
4653
+
4654
+
4655
+ /**
4656
+ * @ngdoc method
4657
+ * @name $provide#service
4658
+ * @description
4659
+ *
4660
+ * Register a **service constructor**, which will be invoked with `new` to create the service
4661
+ * instance.
4662
+ * This is short for registering a service where its provider's `$get` property is a factory
4663
+ * function that returns an instance instantiated by the injector from the service constructor
4664
+ * function.
4665
+ *
4666
+ * Internally it looks a bit like this:
4667
+ *
4668
+ * ```
4669
+ * {
4670
+ * $get: function() {
4671
+ * return $injector.instantiate(constructor);
4672
+ * }
4673
+ * }
4674
+ * ```
4675
+ *
4676
+ *
4677
+ * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4678
+ * as a type/class.
4679
+ *
4680
+ * @param {string} name The name of the instance.
4681
+ * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4682
+ * that will be instantiated.
4683
+ * @returns {Object} registered provider instance
4684
+ *
4685
+ * @example
4686
+ * Here is an example of registering a service using
4687
+ * {@link auto.$provide#service $provide.service(class)}.
4688
+ * ```js
4689
+ * var Ping = function($http) {
4690
+ * this.$http = $http;
4691
+ * };
4692
+ *
4693
+ * Ping.$inject = ['$http'];
4694
+ *
4695
+ * Ping.prototype.send = function() {
4696
+ * return this.$http.get('/ping');
4697
+ * };
4698
+ * $provide.service('ping', Ping);
4699
+ * ```
4700
+ * You would then inject and use this service like this:
4701
+ * ```js
4702
+ * someModule.controller('Ctrl', ['ping', function(ping) {
4703
+ * ping.send();
4704
+ * }]);
4705
+ * ```
4706
+ */
4707
+
4708
+
4709
+ /**
4710
+ * @ngdoc method
4711
+ * @name $provide#value
4712
+ * @description
4713
+ *
4714
+ * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4715
+ * number, an array, an object or a function. This is short for registering a service where its
4716
+ * provider's `$get` property is a factory function that takes no arguments and returns the **value
4717
+ * service**. That also means it is not possible to inject other services into a value service.
4718
+ *
4719
+ * Value services are similar to constant services, except that they cannot be injected into a
4720
+ * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4721
+ * an Angular {@link auto.$provide#decorator decorator}.
4722
+ *
4723
+ * @param {string} name The name of the instance.
4724
+ * @param {*} value The value.
4725
+ * @returns {Object} registered provider instance
4726
+ *
4727
+ * @example
4728
+ * Here are some examples of creating value services.
4729
+ * ```js
4730
+ * $provide.value('ADMIN_USER', 'admin');
4731
+ *
4732
+ * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4733
+ *
4734
+ * $provide.value('halfOf', function(value) {
4735
+ * return value / 2;
4736
+ * });
4737
+ * ```
4738
+ */
4739
+
4740
+
4741
+ /**
4742
+ * @ngdoc method
4743
+ * @name $provide#constant
4744
+ * @description
4745
+ *
4746
+ * Register a **constant service** with the {@link auto.$injector $injector}, such as a string,
4747
+ * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not
4748
+ * possible to inject other services into a constant.
4749
+ *
4750
+ * But unlike {@link auto.$provide#value value}, a constant can be
4751
+ * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4752
+ * be overridden by an Angular {@link auto.$provide#decorator decorator}.
4753
+ *
4754
+ * @param {string} name The name of the constant.
4755
+ * @param {*} value The constant value.
4756
+ * @returns {Object} registered instance
4757
+ *
4758
+ * @example
4759
+ * Here a some examples of creating constants:
4760
+ * ```js
4761
+ * $provide.constant('SHARD_HEIGHT', 306);
4762
+ *
4763
+ * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4764
+ *
4765
+ * $provide.constant('double', function(value) {
4766
+ * return value * 2;
4767
+ * });
4768
+ * ```
4769
+ */
4770
+
4771
+
4772
+ /**
4773
+ * @ngdoc method
4774
+ * @name $provide#decorator
4775
+ * @description
4776
+ *
4777
+ * Register a **decorator function** with the {@link auto.$injector $injector}. A decorator function
4778
+ * intercepts the creation of a service, allowing it to override or modify the behavior of the
4779
+ * service. The return value of the decorator function may be the original service, or a new service
4780
+ * that replaces (or wraps and delegates to) the original service.
4781
+ *
4782
+ * You can find out more about using decorators in the {@link guide/decorators} guide.
4783
+ *
4784
+ * @param {string} name The name of the service to decorate.
4785
+ * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4786
+ * provided and should return the decorated service instance. The function is called using
4787
+ * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4788
+ * Local injection arguments:
4789
+ *
4790
+ * * `$delegate` - The original service instance, which can be replaced, monkey patched, configured,
4791
+ * decorated or delegated to.
4792
+ *
4793
+ * @example
4794
+ * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4795
+ * calls to {@link ng.$log#error $log.warn()}.
4796
+ * ```js
4797
+ * $provide.decorator('$log', ['$delegate', function($delegate) {
4798
+ * $delegate.warn = $delegate.error;
4799
+ * return $delegate;
4800
+ * }]);
4801
+ * ```
4802
+ */
4803
+
4804
+
4805
+ function createInjector(modulesToLoad, strictDi) {
4806
+ strictDi = (strictDi === true);
4807
+ var INSTANTIATING = {},
4808
+ providerSuffix = 'Provider',
4809
+ path = [],
4810
+ loadedModules = new NgMap(),
4811
+ providerCache = {
4812
+ $provide: {
4813
+ provider: supportObject(provider),
4814
+ factory: supportObject(factory),
4815
+ service: supportObject(service),
4816
+ value: supportObject(value),
4817
+ constant: supportObject(constant),
4818
+ decorator: decorator
4819
+ }
4820
+ },
4821
+ providerInjector = (providerCache.$injector =
4822
+ createInternalInjector(providerCache, function(serviceName, caller) {
4823
+ if (angular.isString(caller)) {
4824
+ path.push(caller);
4825
+ }
4826
+ throw $injectorMinErr('unpr', 'Unknown provider: {0}', path.join(' <- '));
4827
+ })),
4828
+ instanceCache = {},
4829
+ protoInstanceInjector =
4830
+ createInternalInjector(instanceCache, function(serviceName, caller) {
4831
+ var provider = providerInjector.get(serviceName + providerSuffix, caller);
4832
+ return instanceInjector.invoke(
4833
+ provider.$get, provider, undefined, serviceName);
4834
+ }),
4835
+ instanceInjector = protoInstanceInjector;
4836
+
4837
+ providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };
4838
+ instanceInjector.modules = providerInjector.modules = createMap();
4839
+ var runBlocks = loadModules(modulesToLoad);
4840
+ instanceInjector = protoInstanceInjector.get('$injector');
4841
+ instanceInjector.strictDi = strictDi;
4842
+ forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });
4843
+
4844
+ return instanceInjector;
4845
+
4846
+ ////////////////////////////////////
4847
+ // $provider
4848
+ ////////////////////////////////////
4849
+
4850
+ function supportObject(delegate) {
4851
+ return function(key, value) {
4852
+ if (isObject(key)) {
4853
+ forEach(key, reverseParams(delegate));
4854
+ } else {
4855
+ return delegate(key, value);
4856
+ }
4857
+ };
4858
+ }
4859
+
4860
+ function provider(name, provider_) {
4861
+ assertNotHasOwnProperty(name, 'service');
4862
+ if (isFunction(provider_) || isArray(provider_)) {
4863
+ provider_ = providerInjector.instantiate(provider_);
4864
+ }
4865
+ if (!provider_.$get) {
4866
+ throw $injectorMinErr('pget', 'Provider \'{0}\' must define $get factory method.', name);
4867
+ }
4868
+ return (providerCache[name + providerSuffix] = provider_);
4869
+ }
4870
+
4871
+ function enforceReturnValue(name, factory) {
4872
+ return /** @this */ function enforcedReturnValue() {
4873
+ var result = instanceInjector.invoke(factory, this);
4874
+ if (isUndefined(result)) {
4875
+ throw $injectorMinErr('undef', 'Provider \'{0}\' must return a value from $get factory method.', name);
4876
+ }
4877
+ return result;
4878
+ };
4879
+ }
4880
+
4881
+ function factory(name, factoryFn, enforce) {
4882
+ return provider(name, {
4883
+ $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
4884
+ });
4885
+ }
4886
+
4887
+ function service(name, constructor) {
4888
+ return factory(name, ['$injector', function($injector) {
4889
+ return $injector.instantiate(constructor);
4890
+ }]);
4891
+ }
4892
+
4893
+ function value(name, val) { return factory(name, valueFn(val), false); }
4894
+
4895
+ function constant(name, value) {
4896
+ assertNotHasOwnProperty(name, 'constant');
4897
+ providerCache[name] = value;
4898
+ instanceCache[name] = value;
4899
+ }
4900
+
4901
+ function decorator(serviceName, decorFn) {
4902
+ var origProvider = providerInjector.get(serviceName + providerSuffix),
4903
+ orig$get = origProvider.$get;
4904
+
4905
+ origProvider.$get = function() {
4906
+ var origInstance = instanceInjector.invoke(orig$get, origProvider);
4907
+ return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
4908
+ };
4909
+ }
4910
+
4911
+ ////////////////////////////////////
4912
+ // Module Loading
4913
+ ////////////////////////////////////
4914
+ function loadModules(modulesToLoad) {
4915
+ assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
4916
+ var runBlocks = [], moduleFn;
4917
+ forEach(modulesToLoad, function(module) {
4918
+ if (loadedModules.get(module)) return;
4919
+ loadedModules.set(module, true);
4920
+
4921
+ function runInvokeQueue(queue) {
4922
+ var i, ii;
4923
+ for (i = 0, ii = queue.length; i < ii; i++) {
4924
+ var invokeArgs = queue[i],
4925
+ provider = providerInjector.get(invokeArgs[0]);
4926
+
4927
+ provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
4928
+ }
4929
+ }
4930
+
4931
+ try {
4932
+ if (isString(module)) {
4933
+ moduleFn = angularModule(module);
4934
+ instanceInjector.modules[module] = moduleFn;
4935
+ runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
4936
+ runInvokeQueue(moduleFn._invokeQueue);
4937
+ runInvokeQueue(moduleFn._configBlocks);
4938
+ } else if (isFunction(module)) {
4939
+ runBlocks.push(providerInjector.invoke(module));
4940
+ } else if (isArray(module)) {
4941
+ runBlocks.push(providerInjector.invoke(module));
4942
+ } else {
4943
+ assertArgFn(module, 'module');
4944
+ }
4945
+ } catch (e) {
4946
+ if (isArray(module)) {
4947
+ module = module[module.length - 1];
4948
+ }
4949
+ if (e.message && e.stack && e.stack.indexOf(e.message) === -1) {
4950
+ // Safari & FF's stack traces don't contain error.message content
4951
+ // unlike those of Chrome and IE
4952
+ // So if stack doesn't contain message, we create a new string that contains both.
4953
+ // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
4954
+ // eslint-disable-next-line no-ex-assign
4955
+ e = e.message + '\n' + e.stack;
4956
+ }
4957
+ throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}',
4958
+ module, e.stack || e.message || e);
4959
+ }
4960
+ });
4961
+ return runBlocks;
4962
+ }
4963
+
4964
+ ////////////////////////////////////
4965
+ // internal Injector
4966
+ ////////////////////////////////////
4967
+
4968
+ function createInternalInjector(cache, factory) {
4969
+
4970
+ function getService(serviceName, caller) {
4971
+ if (cache.hasOwnProperty(serviceName)) {
4972
+ if (cache[serviceName] === INSTANTIATING) {
4973
+ throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
4974
+ serviceName + ' <- ' + path.join(' <- '));
4975
+ }
4976
+ return cache[serviceName];
4977
+ } else {
4978
+ try {
4979
+ path.unshift(serviceName);
4980
+ cache[serviceName] = INSTANTIATING;
4981
+ cache[serviceName] = factory(serviceName, caller);
4982
+ return cache[serviceName];
4983
+ } catch (err) {
4984
+ if (cache[serviceName] === INSTANTIATING) {
4985
+ delete cache[serviceName];
4986
+ }
4987
+ throw err;
4988
+ } finally {
4989
+ path.shift();
4990
+ }
4991
+ }
4992
+ }
4993
+
4994
+
4995
+ function injectionArgs(fn, locals, serviceName) {
4996
+ var args = [],
4997
+ $inject = createInjector.$$annotate(fn, strictDi, serviceName);
4998
+
4999
+ for (var i = 0, length = $inject.length; i < length; i++) {
5000
+ var key = $inject[i];
5001
+ if (typeof key !== 'string') {
5002
+ throw $injectorMinErr('itkn',
5003
+ 'Incorrect injection token! Expected service name as string, got {0}', key);
5004
+ }
5005
+ args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
5006
+ getService(key, serviceName));
5007
+ }
5008
+ return args;
5009
+ }
5010
+
5011
+ function isClass(func) {
5012
+ // Support: IE 9-11 only
5013
+ // IE 9-11 do not support classes and IE9 leaks with the code below.
5014
+ if (msie || typeof func !== 'function') {
5015
+ return false;
5016
+ }
5017
+ var result = func.$$ngIsClass;
5018
+ if (!isBoolean(result)) {
5019
+ // Support: Edge 12-13 only
5020
+ // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6156135/
5021
+ result = func.$$ngIsClass = /^(?:class\b|constructor\()/.test(stringifyFn(func));
5022
+ }
5023
+ return result;
5024
+ }
5025
+
5026
+ function invoke(fn, self, locals, serviceName) {
5027
+ if (typeof locals === 'string') {
5028
+ serviceName = locals;
5029
+ locals = null;
5030
+ }
5031
+
5032
+ var args = injectionArgs(fn, locals, serviceName);
5033
+ if (isArray(fn)) {
5034
+ fn = fn[fn.length - 1];
5035
+ }
5036
+
5037
+ if (!isClass(fn)) {
5038
+ // http://jsperf.com/angularjs-invoke-apply-vs-switch
5039
+ // #5388
5040
+ return fn.apply(self, args);
5041
+ } else {
5042
+ args.unshift(null);
5043
+ return new (Function.prototype.bind.apply(fn, args))();
5044
+ }
5045
+ }
5046
+
5047
+
5048
+ function instantiate(Type, locals, serviceName) {
5049
+ // Check if Type is annotated and use just the given function at n-1 as parameter
5050
+ // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
5051
+ var ctor = (isArray(Type) ? Type[Type.length - 1] : Type);
5052
+ var args = injectionArgs(Type, locals, serviceName);
5053
+ // Empty object at position 0 is ignored for invocation with `new`, but required.
5054
+ args.unshift(null);
5055
+ return new (Function.prototype.bind.apply(ctor, args))();
5056
+ }
5057
+
5058
+
5059
+ return {
5060
+ invoke: invoke,
5061
+ instantiate: instantiate,
5062
+ get: getService,
5063
+ annotate: createInjector.$$annotate,
5064
+ has: function(name) {
5065
+ return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
5066
+ }
5067
+ };
5068
+ }
5069
+ }
5070
+
5071
+ createInjector.$$annotate = annotate;
5072
+
5073
+ /**
5074
+ * @ngdoc provider
5075
+ * @name $anchorScrollProvider
5076
+ * @this
5077
+ *
5078
+ * @description
5079
+ * Use `$anchorScrollProvider` to disable automatic scrolling whenever
5080
+ * {@link ng.$location#hash $location.hash()} changes.
5081
+ */
5082
+ function $AnchorScrollProvider() {
5083
+
5084
+ var autoScrollingEnabled = true;
5085
+
5086
+ /**
5087
+ * @ngdoc method
5088
+ * @name $anchorScrollProvider#disableAutoScrolling
5089
+ *
5090
+ * @description
5091
+ * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
5092
+ * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
5093
+ * Use this method to disable automatic scrolling.
5094
+ *
5095
+ * If automatic scrolling is disabled, one must explicitly call
5096
+ * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
5097
+ * current hash.
5098
+ */
5099
+ this.disableAutoScrolling = function() {
5100
+ autoScrollingEnabled = false;
5101
+ };
5102
+
5103
+ /**
5104
+ * @ngdoc service
5105
+ * @name $anchorScroll
5106
+ * @kind function
5107
+ * @requires $window
5108
+ * @requires $location
5109
+ * @requires $rootScope
5110
+ *
5111
+ * @description
5112
+ * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
5113
+ * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
5114
+ * in the
5115
+ * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document).
5116
+ *
5117
+ * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
5118
+ * match any anchor whenever it changes. This can be disabled by calling
5119
+ * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
5120
+ *
5121
+ * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
5122
+ * vertical scroll-offset (either fixed or dynamic).
5123
+ *
5124
+ * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
5125
+ * {@link ng.$location#hash $location.hash()} will be used.
5126
+ *
5127
+ * @property {(number|function|jqLite)} yOffset
5128
+ * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
5129
+ * positioned elements at the top of the page, such as navbars, headers etc.
5130
+ *
5131
+ * `yOffset` can be specified in various ways:
5132
+ * - **number**: A fixed number of pixels to be used as offset.<br /><br />
5133
+ * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
5134
+ * a number representing the offset (in pixels).<br /><br />
5135
+ * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
5136
+ * the top of the page to the element's bottom will be used as offset.<br />
5137
+ * **Note**: The element will be taken into account only as long as its `position` is set to
5138
+ * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
5139
+ * their height and/or positioning according to the viewport's size.
5140
+ *
5141
+ * <br />
5142
+ * <div class="alert alert-warning">
5143
+ * In order for `yOffset` to work properly, scrolling should take place on the document's root and
5144
+ * not some child element.
5145
+ * </div>
5146
+ *
5147
+ * @example
5148
+ <example module="anchorScrollExample" name="anchor-scroll">
5149
+ <file name="index.html">
5150
+ <div id="scrollArea" ng-controller="ScrollController">
5151
+ <a ng-click="gotoBottom()">Go to bottom</a>
5152
+ <a id="bottom"></a> You're at the bottom!
5153
+ </div>
5154
+ </file>
5155
+ <file name="script.js">
5156
+ angular.module('anchorScrollExample', [])
5157
+ .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
5158
+ function($scope, $location, $anchorScroll) {
5159
+ $scope.gotoBottom = function() {
5160
+ // set the location.hash to the id of
5161
+ // the element you wish to scroll to.
5162
+ $location.hash('bottom');
5163
+
5164
+ // call $anchorScroll()
5165
+ $anchorScroll();
5166
+ };
5167
+ }]);
5168
+ </file>
5169
+ <file name="style.css">
5170
+ #scrollArea {
5171
+ height: 280px;
5172
+ overflow: auto;
5173
+ }
5174
+
5175
+ #bottom {
5176
+ display: block;
5177
+ margin-top: 2000px;
5178
+ }
5179
+ </file>
5180
+ </example>
5181
+ *
5182
+ * <hr />
5183
+ * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
5184
+ * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
5185
+ *
5186
+ * @example
5187
+ <example module="anchorScrollOffsetExample" name="anchor-scroll-offset">
5188
+ <file name="index.html">
5189
+ <div class="fixed-header" ng-controller="headerCtrl">
5190
+ <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
5191
+ Go to anchor {{x}}
5192
+ </a>
5193
+ </div>
5194
+ <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
5195
+ Anchor {{x}} of 5
5196
+ </div>
5197
+ </file>
5198
+ <file name="script.js">
5199
+ angular.module('anchorScrollOffsetExample', [])
5200
+ .run(['$anchorScroll', function($anchorScroll) {
5201
+ $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
5202
+ }])
5203
+ .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
5204
+ function($anchorScroll, $location, $scope) {
5205
+ $scope.gotoAnchor = function(x) {
5206
+ var newHash = 'anchor' + x;
5207
+ if ($location.hash() !== newHash) {
5208
+ // set the $location.hash to `newHash` and
5209
+ // $anchorScroll will automatically scroll to it
5210
+ $location.hash('anchor' + x);
5211
+ } else {
5212
+ // call $anchorScroll() explicitly,
5213
+ // since $location.hash hasn't changed
5214
+ $anchorScroll();
5215
+ }
5216
+ };
5217
+ }
5218
+ ]);
5219
+ </file>
5220
+ <file name="style.css">
5221
+ body {
5222
+ padding-top: 50px;
5223
+ }
5224
+
5225
+ .anchor {
5226
+ border: 2px dashed DarkOrchid;
5227
+ padding: 10px 10px 200px 10px;
5228
+ }
5229
+
5230
+ .fixed-header {
5231
+ background-color: rgba(0, 0, 0, 0.2);
5232
+ height: 50px;
5233
+ position: fixed;
5234
+ top: 0; left: 0; right: 0;
5235
+ }
5236
+
5237
+ .fixed-header > a {
5238
+ display: inline-block;
5239
+ margin: 5px 15px;
5240
+ }
5241
+ </file>
5242
+ </example>
5243
+ */
5244
+ this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
5245
+ var document = $window.document;
5246
+
5247
+ // Helper function to get first anchor from a NodeList
5248
+ // (using `Array#some()` instead of `angular#forEach()` since it's more performant
5249
+ // and working in all supported browsers.)
5250
+ function getFirstAnchor(list) {
5251
+ var result = null;
5252
+ Array.prototype.some.call(list, function(element) {
5253
+ if (nodeName_(element) === 'a') {
5254
+ result = element;
5255
+ return true;
5256
+ }
5257
+ });
5258
+ return result;
5259
+ }
5260
+
5261
+ function getYOffset() {
5262
+
5263
+ var offset = scroll.yOffset;
5264
+
5265
+ if (isFunction(offset)) {
5266
+ offset = offset();
5267
+ } else if (isElement(offset)) {
5268
+ var elem = offset[0];
5269
+ var style = $window.getComputedStyle(elem);
5270
+ if (style.position !== 'fixed') {
5271
+ offset = 0;
5272
+ } else {
5273
+ offset = elem.getBoundingClientRect().bottom;
5274
+ }
5275
+ } else if (!isNumber(offset)) {
5276
+ offset = 0;
5277
+ }
5278
+
5279
+ return offset;
5280
+ }
5281
+
5282
+ function scrollTo(elem) {
5283
+ if (elem) {
5284
+ elem.scrollIntoView();
5285
+
5286
+ var offset = getYOffset();
5287
+
5288
+ if (offset) {
5289
+ // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
5290
+ // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
5291
+ // top of the viewport.
5292
+ //
5293
+ // IF the number of pixels from the top of `elem` to the end of the page's content is less
5294
+ // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
5295
+ // way down the page.
5296
+ //
5297
+ // This is often the case for elements near the bottom of the page.
5298
+ //
5299
+ // In such cases we do not need to scroll the whole `offset` up, just the difference between
5300
+ // the top of the element and the offset, which is enough to align the top of `elem` at the
5301
+ // desired position.
5302
+ var elemTop = elem.getBoundingClientRect().top;
5303
+ $window.scrollBy(0, elemTop - offset);
5304
+ }
5305
+ } else {
5306
+ $window.scrollTo(0, 0);
5307
+ }
5308
+ }
5309
+
5310
+ function scroll(hash) {
5311
+ // Allow numeric hashes
5312
+ hash = isString(hash) ? hash : isNumber(hash) ? hash.toString() : $location.hash();
5313
+ var elm;
5314
+
5315
+ // empty hash, scroll to the top of the page
5316
+ if (!hash) scrollTo(null);
5317
+
5318
+ // element with given id
5319
+ else if ((elm = document.getElementById(hash))) scrollTo(elm);
5320
+
5321
+ // first anchor with given name :-D
5322
+ else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
5323
+
5324
+ // no element and hash === 'top', scroll to the top of the page
5325
+ else if (hash === 'top') scrollTo(null);
5326
+ }
5327
+
5328
+ // does not scroll when user clicks on anchor link that is currently on
5329
+ // (no url change, no $location.hash() change), browser native does scroll
5330
+ if (autoScrollingEnabled) {
5331
+ $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
5332
+ function autoScrollWatchAction(newVal, oldVal) {
5333
+ // skip the initial scroll if $location.hash is empty
5334
+ if (newVal === oldVal && newVal === '') return;
5335
+
5336
+ jqLiteDocumentLoaded(function() {
5337
+ $rootScope.$evalAsync(scroll);
5338
+ });
5339
+ });
5340
+ }
5341
+
5342
+ return scroll;
5343
+ }];
5344
+ }
5345
+
5346
+ var $animateMinErr = minErr('$animate');
5347
+ var ELEMENT_NODE = 1;
5348
+ var NG_ANIMATE_CLASSNAME = 'ng-animate';
5349
+
5350
+ function mergeClasses(a,b) {
5351
+ if (!a && !b) return '';
5352
+ if (!a) return b;
5353
+ if (!b) return a;
5354
+ if (isArray(a)) a = a.join(' ');
5355
+ if (isArray(b)) b = b.join(' ');
5356
+ return a + ' ' + b;
5357
+ }
5358
+
5359
+ function extractElementNode(element) {
5360
+ for (var i = 0; i < element.length; i++) {
5361
+ var elm = element[i];
5362
+ if (elm.nodeType === ELEMENT_NODE) {
5363
+ return elm;
5364
+ }
5365
+ }
5366
+ }
5367
+
5368
+ function splitClasses(classes) {
5369
+ if (isString(classes)) {
5370
+ classes = classes.split(' ');
5371
+ }
5372
+
5373
+ // Use createMap() to prevent class assumptions involving property names in
5374
+ // Object.prototype
5375
+ var obj = createMap();
5376
+ forEach(classes, function(klass) {
5377
+ // sometimes the split leaves empty string values
5378
+ // incase extra spaces were applied to the options
5379
+ if (klass.length) {
5380
+ obj[klass] = true;
5381
+ }
5382
+ });
5383
+ return obj;
5384
+ }
5385
+
5386
+ // if any other type of options value besides an Object value is
5387
+ // passed into the $animate.method() animation then this helper code
5388
+ // will be run which will ignore it. While this patch is not the
5389
+ // greatest solution to this, a lot of existing plugins depend on
5390
+ // $animate to either call the callback (< 1.2) or return a promise
5391
+ // that can be changed. This helper function ensures that the options
5392
+ // are wiped clean incase a callback function is provided.
5393
+ function prepareAnimateOptions(options) {
5394
+ return isObject(options)
5395
+ ? options
5396
+ : {};
5397
+ }
5398
+
5399
+ var $$CoreAnimateJsProvider = /** @this */ function() {
5400
+ this.$get = noop;
5401
+ };
5402
+
5403
+ // this is prefixed with Core since it conflicts with
5404
+ // the animateQueueProvider defined in ngAnimate/animateQueue.js
5405
+ var $$CoreAnimateQueueProvider = /** @this */ function() {
5406
+ var postDigestQueue = new NgMap();
5407
+ var postDigestElements = [];
5408
+
5409
+ this.$get = ['$$AnimateRunner', '$rootScope',
5410
+ function($$AnimateRunner, $rootScope) {
5411
+ return {
5412
+ enabled: noop,
5413
+ on: noop,
5414
+ off: noop,
5415
+ pin: noop,
5416
+
5417
+ push: function(element, event, options, domOperation) {
5418
+ if (domOperation) {
5419
+ domOperation();
5420
+ }
5421
+
5422
+ options = options || {};
5423
+ if (options.from) {
5424
+ element.css(options.from);
5425
+ }
5426
+ if (options.to) {
5427
+ element.css(options.to);
5428
+ }
5429
+
5430
+ if (options.addClass || options.removeClass) {
5431
+ addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
5432
+ }
5433
+
5434
+ var runner = new $$AnimateRunner();
5435
+
5436
+ // since there are no animations to run the runner needs to be
5437
+ // notified that the animation call is complete.
5438
+ runner.complete();
5439
+ return runner;
5440
+ }
5441
+ };
5442
+
5443
+
5444
+ function updateData(data, classes, value) {
5445
+ var changed = false;
5446
+ if (classes) {
5447
+ classes = isString(classes) ? classes.split(' ') :
5448
+ isArray(classes) ? classes : [];
5449
+ forEach(classes, function(className) {
5450
+ if (className) {
5451
+ changed = true;
5452
+ data[className] = value;
5453
+ }
5454
+ });
5455
+ }
5456
+ return changed;
5457
+ }
5458
+
5459
+ function handleCSSClassChanges() {
5460
+ forEach(postDigestElements, function(element) {
5461
+ var data = postDigestQueue.get(element);
5462
+ if (data) {
5463
+ var existing = splitClasses(element.attr('class'));
5464
+ var toAdd = '';
5465
+ var toRemove = '';
5466
+ forEach(data, function(status, className) {
5467
+ var hasClass = !!existing[className];
5468
+ if (status !== hasClass) {
5469
+ if (status) {
5470
+ toAdd += (toAdd.length ? ' ' : '') + className;
5471
+ } else {
5472
+ toRemove += (toRemove.length ? ' ' : '') + className;
5473
+ }
5474
+ }
5475
+ });
5476
+
5477
+ forEach(element, function(elm) {
5478
+ if (toAdd) {
5479
+ jqLiteAddClass(elm, toAdd);
5480
+ }
5481
+ if (toRemove) {
5482
+ jqLiteRemoveClass(elm, toRemove);
5483
+ }
5484
+ });
5485
+ postDigestQueue.delete(element);
5486
+ }
5487
+ });
5488
+ postDigestElements.length = 0;
5489
+ }
5490
+
5491
+
5492
+ function addRemoveClassesPostDigest(element, add, remove) {
5493
+ var data = postDigestQueue.get(element) || {};
5494
+
5495
+ var classesAdded = updateData(data, add, true);
5496
+ var classesRemoved = updateData(data, remove, false);
5497
+
5498
+ if (classesAdded || classesRemoved) {
5499
+
5500
+ postDigestQueue.set(element, data);
5501
+ postDigestElements.push(element);
5502
+
5503
+ if (postDigestElements.length === 1) {
5504
+ $rootScope.$$postDigest(handleCSSClassChanges);
5505
+ }
5506
+ }
5507
+ }
5508
+ }];
5509
+ };
5510
+
5511
+ /**
5512
+ * @ngdoc provider
5513
+ * @name $animateProvider
5514
+ *
5515
+ * @description
5516
+ * Default implementation of $animate that doesn't perform any animations, instead just
5517
+ * synchronously performs DOM updates and resolves the returned runner promise.
5518
+ *
5519
+ * In order to enable animations the `ngAnimate` module has to be loaded.
5520
+ *
5521
+ * To see the functional implementation check out `src/ngAnimate/animate.js`.
5522
+ */
5523
+ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
5524
+ var provider = this;
5525
+ var classNameFilter = null;
5526
+ var customFilter = null;
5527
+
5528
+ this.$$registeredAnimations = Object.create(null);
5529
+
5530
+ /**
5531
+ * @ngdoc method
5532
+ * @name $animateProvider#register
5533
+ *
5534
+ * @description
5535
+ * Registers a new injectable animation factory function. The factory function produces the
5536
+ * animation object which contains callback functions for each event that is expected to be
5537
+ * animated.
5538
+ *
5539
+ * * `eventFn`: `function(element, ... , doneFunction, options)`
5540
+ * The element to animate, the `doneFunction` and the options fed into the animation. Depending
5541
+ * on the type of animation additional arguments will be injected into the animation function. The
5542
+ * list below explains the function signatures for the different animation methods:
5543
+ *
5544
+ * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
5545
+ * - addClass: function(element, addedClasses, doneFunction, options)
5546
+ * - removeClass: function(element, removedClasses, doneFunction, options)
5547
+ * - enter, leave, move: function(element, doneFunction, options)
5548
+ * - animate: function(element, fromStyles, toStyles, doneFunction, options)
5549
+ *
5550
+ * Make sure to trigger the `doneFunction` once the animation is fully complete.
5551
+ *
5552
+ * ```js
5553
+ * return {
5554
+ * //enter, leave, move signature
5555
+ * eventFn : function(element, done, options) {
5556
+ * //code to run the animation
5557
+ * //once complete, then run done()
5558
+ * return function endFunction(wasCancelled) {
5559
+ * //code to cancel the animation
5560
+ * }
5561
+ * }
5562
+ * }
5563
+ * ```
5564
+ *
5565
+ * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
5566
+ * @param {Function} factory The factory function that will be executed to return the animation
5567
+ * object.
5568
+ */
5569
+ this.register = function(name, factory) {
5570
+ if (name && name.charAt(0) !== '.') {
5571
+ throw $animateMinErr('notcsel', 'Expecting class selector starting with \'.\' got \'{0}\'.', name);
5572
+ }
5573
+
5574
+ var key = name + '-animation';
5575
+ provider.$$registeredAnimations[name.substr(1)] = key;
5576
+ $provide.factory(key, factory);
5577
+ };
5578
+
5579
+ /**
5580
+ * @ngdoc method
5581
+ * @name $animateProvider#customFilter
5582
+ *
5583
+ * @description
5584
+ * Sets and/or returns the custom filter function that is used to "filter" animations, i.e.
5585
+ * determine if an animation is allowed or not. When no filter is specified (the default), no
5586
+ * animation will be blocked. Setting the `customFilter` value will only allow animations for
5587
+ * which the filter function's return value is truthy.
5588
+ *
5589
+ * This allows to easily create arbitrarily complex rules for filtering animations, such as
5590
+ * allowing specific events only, or enabling animations on specific subtrees of the DOM, etc.
5591
+ * Filtering animations can also boost performance for low-powered devices, as well as
5592
+ * applications containing a lot of structural operations.
5593
+ *
5594
+ * <div class="alert alert-success">
5595
+ * **Best Practice:**
5596
+ * Keep the filtering function as lean as possible, because it will be called for each DOM
5597
+ * action (e.g. insertion, removal, class change) performed by "animation-aware" directives.
5598
+ * See {@link guide/animations#which-directives-support-animations- here} for a list of built-in
5599
+ * directives that support animations.
5600
+ * Performing computationally expensive or time-consuming operations on each call of the
5601
+ * filtering function can make your animations sluggish.
5602
+ * </div>
5603
+ *
5604
+ * **Note:** If present, `customFilter` will be checked before
5605
+ * {@link $animateProvider#classNameFilter classNameFilter}.
5606
+ *
5607
+ * @param {Function=} filterFn - The filter function which will be used to filter all animations.
5608
+ * If a falsy value is returned, no animation will be performed. The function will be called
5609
+ * with the following arguments:
5610
+ * - **node** `{DOMElement}` - The DOM element to be animated.
5611
+ * - **event** `{String}` - The name of the animation event (e.g. `enter`, `leave`, `addClass`
5612
+ * etc).
5613
+ * - **options** `{Object}` - A collection of options/styles used for the animation.
5614
+ * @return {Function} The current filter function or `null` if there is none set.
5615
+ */
5616
+ this.customFilter = function(filterFn) {
5617
+ if (arguments.length === 1) {
5618
+ customFilter = isFunction(filterFn) ? filterFn : null;
5619
+ }
5620
+
5621
+ return customFilter;
5622
+ };
5623
+
5624
+ /**
5625
+ * @ngdoc method
5626
+ * @name $animateProvider#classNameFilter
5627
+ *
5628
+ * @description
5629
+ * Sets and/or returns the CSS class regular expression that is checked when performing
5630
+ * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5631
+ * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5632
+ * When setting the `classNameFilter` value, animations will only be performed on elements
5633
+ * that successfully match the filter expression. This in turn can boost performance
5634
+ * for low-powered devices as well as applications containing a lot of structural operations.
5635
+ *
5636
+ * **Note:** If present, `classNameFilter` will be checked after
5637
+ * {@link $animateProvider#customFilter customFilter}. If `customFilter` is present and returns
5638
+ * false, `classNameFilter` will not be checked.
5639
+ *
5640
+ * @param {RegExp=} expression The className expression which will be checked against all animations
5641
+ * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5642
+ */
5643
+ this.classNameFilter = function(expression) {
5644
+ if (arguments.length === 1) {
5645
+ classNameFilter = (expression instanceof RegExp) ? expression : null;
5646
+ if (classNameFilter) {
5647
+ var reservedRegex = new RegExp('[(\\s|\\/)]' + NG_ANIMATE_CLASSNAME + '[(\\s|\\/)]');
5648
+ if (reservedRegex.test(classNameFilter.toString())) {
5649
+ classNameFilter = null;
5650
+ throw $animateMinErr('nongcls', '$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
5651
+ }
5652
+ }
5653
+ }
5654
+ return classNameFilter;
5655
+ };
5656
+
5657
+ this.$get = ['$$animateQueue', function($$animateQueue) {
5658
+ function domInsert(element, parentElement, afterElement) {
5659
+ // if for some reason the previous element was removed
5660
+ // from the dom sometime before this code runs then let's
5661
+ // just stick to using the parent element as the anchor
5662
+ if (afterElement) {
5663
+ var afterNode = extractElementNode(afterElement);
5664
+ if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5665
+ afterElement = null;
5666
+ }
5667
+ }
5668
+ if (afterElement) {
5669
+ afterElement.after(element);
5670
+ } else {
5671
+ parentElement.prepend(element);
5672
+ }
5673
+ }
5674
+
5675
+ /**
5676
+ * @ngdoc service
5677
+ * @name $animate
5678
+ * @description The $animate service exposes a series of DOM utility methods that provide support
5679
+ * for animation hooks. The default behavior is the application of DOM operations, however,
5680
+ * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5681
+ * to ensure that animation runs with the triggered DOM operation.
5682
+ *
5683
+ * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
5684
+ * included and only when it is active then the animation hooks that `$animate` triggers will be
5685
+ * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5686
+ * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5687
+ * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5688
+ *
5689
+ * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5690
+ *
5691
+ * To learn more about enabling animation support, click here to visit the
5692
+ * {@link ngAnimate ngAnimate module page}.
5693
+ */
5694
+ return {
5695
+ // we don't call it directly since non-existant arguments may
5696
+ // be interpreted as null within the sub enabled function
5697
+
5698
+ /**
5699
+ *
5700
+ * @ngdoc method
5701
+ * @name $animate#on
5702
+ * @kind function
5703
+ * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5704
+ * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5705
+ * is fired with the following params:
5706
+ *
5707
+ * ```js
5708
+ * $animate.on('enter', container,
5709
+ * function callback(element, phase) {
5710
+ * // cool we detected an enter animation within the container
5711
+ * }
5712
+ * );
5713
+ * ```
5714
+ *
5715
+ * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5716
+ * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5717
+ * as well as among its children
5718
+ * @param {Function} callback the callback function that will be fired when the listener is triggered
5719
+ *
5720
+ * The arguments present in the callback function are:
5721
+ * * `element` - The captured DOM element that the animation was fired on.
5722
+ * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5723
+ */
5724
+ on: $$animateQueue.on,
5725
+
5726
+ /**
5727
+ *
5728
+ * @ngdoc method
5729
+ * @name $animate#off
5730
+ * @kind function
5731
+ * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5732
+ * can be used in three different ways depending on the arguments:
5733
+ *
5734
+ * ```js
5735
+ * // remove all the animation event listeners listening for `enter`
5736
+ * $animate.off('enter');
5737
+ *
5738
+ * // remove listeners for all animation events from the container element
5739
+ * $animate.off(container);
5740
+ *
5741
+ * // remove all the animation event listeners listening for `enter` on the given element and its children
5742
+ * $animate.off('enter', container);
5743
+ *
5744
+ * // remove the event listener function provided by `callback` that is set
5745
+ * // to listen for `enter` on the given `container` as well as its children
5746
+ * $animate.off('enter', container, callback);
5747
+ * ```
5748
+ *
5749
+ * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move,
5750
+ * addClass, removeClass, etc...), or the container element. If it is the element, all other
5751
+ * arguments are ignored.
5752
+ * @param {DOMElement=} container the container element the event listener was placed on
5753
+ * @param {Function=} callback the callback function that was registered as the listener
5754
+ */
5755
+ off: $$animateQueue.off,
5756
+
5757
+ /**
5758
+ * @ngdoc method
5759
+ * @name $animate#pin
5760
+ * @kind function
5761
+ * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5762
+ * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
5763
+ * element despite being outside the realm of the application or within another application. Say for example if the application
5764
+ * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5765
+ * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5766
+ * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5767
+ *
5768
+ * Note that this feature is only active when the `ngAnimate` module is used.
5769
+ *
5770
+ * @param {DOMElement} element the external element that will be pinned
5771
+ * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5772
+ */
5773
+ pin: $$animateQueue.pin,
5774
+
5775
+ /**
5776
+ *
5777
+ * @ngdoc method
5778
+ * @name $animate#enabled
5779
+ * @kind function
5780
+ * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5781
+ * function can be called in four ways:
5782
+ *
5783
+ * ```js
5784
+ * // returns true or false
5785
+ * $animate.enabled();
5786
+ *
5787
+ * // changes the enabled state for all animations
5788
+ * $animate.enabled(false);
5789
+ * $animate.enabled(true);
5790
+ *
5791
+ * // returns true or false if animations are enabled for an element
5792
+ * $animate.enabled(element);
5793
+ *
5794
+ * // changes the enabled state for an element and its children
5795
+ * $animate.enabled(element, true);
5796
+ * $animate.enabled(element, false);
5797
+ * ```
5798
+ *
5799
+ * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5800
+ * @param {boolean=} enabled whether or not the animations will be enabled for the element
5801
+ *
5802
+ * @return {boolean} whether or not animations are enabled
5803
+ */
5804
+ enabled: $$animateQueue.enabled,
5805
+
5806
+ /**
5807
+ * @ngdoc method
5808
+ * @name $animate#cancel
5809
+ * @kind function
5810
+ * @description Cancels the provided animation.
5811
+ *
5812
+ * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
5813
+ */
5814
+ cancel: function(runner) {
5815
+ if (runner.end) {
5816
+ runner.end();
5817
+ }
5818
+ },
5819
+
5820
+ /**
5821
+ *
5822
+ * @ngdoc method
5823
+ * @name $animate#enter
5824
+ * @kind function
5825
+ * @description Inserts the element into the DOM either after the `after` element (if provided) or
5826
+ * as the first child within the `parent` element and then triggers an animation.
5827
+ * A promise is returned that will be resolved during the next digest once the animation
5828
+ * has completed.
5829
+ *
5830
+ * @param {DOMElement} element the element which will be inserted into the DOM
5831
+ * @param {DOMElement} parent the parent element which will append the element as
5832
+ * a child (so long as the after element is not present)
5833
+ * @param {DOMElement=} after the sibling element after which the element will be appended
5834
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5835
+ * The object can have the following properties:
5836
+ *
5837
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5838
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5839
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5840
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5841
+ *
5842
+ * @return {Promise} the animation callback promise
5843
+ */
5844
+ enter: function(element, parent, after, options) {
5845
+ parent = parent && jqLite(parent);
5846
+ after = after && jqLite(after);
5847
+ parent = parent || after.parent();
5848
+ domInsert(element, parent, after);
5849
+ return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
5850
+ },
5851
+
5852
+ /**
5853
+ *
5854
+ * @ngdoc method
5855
+ * @name $animate#move
5856
+ * @kind function
5857
+ * @description Inserts (moves) the element into its new position in the DOM either after
5858
+ * the `after` element (if provided) or as the first child within the `parent` element
5859
+ * and then triggers an animation. A promise is returned that will be resolved
5860
+ * during the next digest once the animation has completed.
5861
+ *
5862
+ * @param {DOMElement} element the element which will be moved into the new DOM position
5863
+ * @param {DOMElement} parent the parent element which will append the element as
5864
+ * a child (so long as the after element is not present)
5865
+ * @param {DOMElement=} after the sibling element after which the element will be appended
5866
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5867
+ * The object can have the following properties:
5868
+ *
5869
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5870
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5871
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5872
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5873
+ *
5874
+ * @return {Promise} the animation callback promise
5875
+ */
5876
+ move: function(element, parent, after, options) {
5877
+ parent = parent && jqLite(parent);
5878
+ after = after && jqLite(after);
5879
+ parent = parent || after.parent();
5880
+ domInsert(element, parent, after);
5881
+ return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
5882
+ },
5883
+
5884
+ /**
5885
+ * @ngdoc method
5886
+ * @name $animate#leave
5887
+ * @kind function
5888
+ * @description Triggers an animation and then removes the element from the DOM.
5889
+ * When the function is called a promise is returned that will be resolved during the next
5890
+ * digest once the animation has completed.
5891
+ *
5892
+ * @param {DOMElement} element the element which will be removed from the DOM
5893
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5894
+ * The object can have the following properties:
5895
+ *
5896
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5897
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5898
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5899
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5900
+ *
5901
+ * @return {Promise} the animation callback promise
5902
+ */
5903
+ leave: function(element, options) {
5904
+ return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
5905
+ element.remove();
5906
+ });
5907
+ },
5908
+
5909
+ /**
5910
+ * @ngdoc method
5911
+ * @name $animate#addClass
5912
+ * @kind function
5913
+ *
5914
+ * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
5915
+ * execution, the addClass operation will only be handled after the next digest and it will not trigger an
5916
+ * animation if element already contains the CSS class or if the class is removed at a later step.
5917
+ * Note that class-based animations are treated differently compared to structural animations
5918
+ * (like enter, move and leave) since the CSS classes may be added/removed at different points
5919
+ * depending if CSS or JavaScript animations are used.
5920
+ *
5921
+ * @param {DOMElement} element the element which the CSS classes will be applied to
5922
+ * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
5923
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5924
+ * The object can have the following properties:
5925
+ *
5926
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5927
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5928
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5929
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5930
+ *
5931
+ * @return {Promise} the animation callback promise
5932
+ */
5933
+ addClass: function(element, className, options) {
5934
+ options = prepareAnimateOptions(options);
5935
+ options.addClass = mergeClasses(options.addclass, className);
5936
+ return $$animateQueue.push(element, 'addClass', options);
5937
+ },
5938
+
5939
+ /**
5940
+ * @ngdoc method
5941
+ * @name $animate#removeClass
5942
+ * @kind function
5943
+ *
5944
+ * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
5945
+ * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
5946
+ * animation if element does not contain the CSS class or if the class is added at a later step.
5947
+ * Note that class-based animations are treated differently compared to structural animations
5948
+ * (like enter, move and leave) since the CSS classes may be added/removed at different points
5949
+ * depending if CSS or JavaScript animations are used.
5950
+ *
5951
+ * @param {DOMElement} element the element which the CSS classes will be applied to
5952
+ * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5953
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5954
+ * The object can have the following properties:
5955
+ *
5956
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5957
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5958
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5959
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5960
+ *
5961
+ * @return {Promise} the animation callback promise
5962
+ */
5963
+ removeClass: function(element, className, options) {
5964
+ options = prepareAnimateOptions(options);
5965
+ options.removeClass = mergeClasses(options.removeClass, className);
5966
+ return $$animateQueue.push(element, 'removeClass', options);
5967
+ },
5968
+
5969
+ /**
5970
+ * @ngdoc method
5971
+ * @name $animate#setClass
5972
+ * @kind function
5973
+ *
5974
+ * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
5975
+ * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
5976
+ * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
5977
+ * passed. Note that class-based animations are treated differently compared to structural animations
5978
+ * (like enter, move and leave) since the CSS classes may be added/removed at different points
5979
+ * depending if CSS or JavaScript animations are used.
5980
+ *
5981
+ * @param {DOMElement} element the element which the CSS classes will be applied to
5982
+ * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5983
+ * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5984
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
5985
+ * The object can have the following properties:
5986
+ *
5987
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
5988
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5989
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5990
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5991
+ *
5992
+ * @return {Promise} the animation callback promise
5993
+ */
5994
+ setClass: function(element, add, remove, options) {
5995
+ options = prepareAnimateOptions(options);
5996
+ options.addClass = mergeClasses(options.addClass, add);
5997
+ options.removeClass = mergeClasses(options.removeClass, remove);
5998
+ return $$animateQueue.push(element, 'setClass', options);
5999
+ },
6000
+
6001
+ /**
6002
+ * @ngdoc method
6003
+ * @name $animate#animate
6004
+ * @kind function
6005
+ *
6006
+ * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
6007
+ * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take
6008
+ * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and
6009
+ * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding
6010
+ * style in `to`, the style in `from` is applied immediately, and no animation is run.
6011
+ * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate`
6012
+ * method (or as part of the `options` parameter):
6013
+ *
6014
+ * ```js
6015
+ * ngModule.animation('.my-inline-animation', function() {
6016
+ * return {
6017
+ * animate : function(element, from, to, done, options) {
6018
+ * //animation
6019
+ * done();
6020
+ * }
6021
+ * }
6022
+ * });
6023
+ * ```
6024
+ *
6025
+ * @param {DOMElement} element the element which the CSS styles will be applied to
6026
+ * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
6027
+ * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
6028
+ * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
6029
+ * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
6030
+ * (Note that if no animation is detected then this value will not be applied to the element.)
6031
+ * @param {object=} options an optional collection of options/styles that will be applied to the element.
6032
+ * The object can have the following properties:
6033
+ *
6034
+ * - **addClass** - `{string}` - space-separated CSS classes to add to element
6035
+ * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
6036
+ * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
6037
+ * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
6038
+ *
6039
+ * @return {Promise} the animation callback promise
6040
+ */
6041
+ animate: function(element, from, to, className, options) {
6042
+ options = prepareAnimateOptions(options);
6043
+ options.from = options.from ? extend(options.from, from) : from;
6044
+ options.to = options.to ? extend(options.to, to) : to;
6045
+
6046
+ className = className || 'ng-inline-animate';
6047
+ options.tempClasses = mergeClasses(options.tempClasses, className);
6048
+ return $$animateQueue.push(element, 'animate', options);
6049
+ }
6050
+ };
6051
+ }];
6052
+ }];
6053
+
6054
+ var $$AnimateAsyncRunFactoryProvider = /** @this */ function() {
6055
+ this.$get = ['$$rAF', function($$rAF) {
6056
+ var waitQueue = [];
6057
+
6058
+ function waitForTick(fn) {
6059
+ waitQueue.push(fn);
6060
+ if (waitQueue.length > 1) return;
6061
+ $$rAF(function() {
6062
+ for (var i = 0; i < waitQueue.length; i++) {
6063
+ waitQueue[i]();
6064
+ }
6065
+ waitQueue = [];
6066
+ });
6067
+ }
6068
+
6069
+ return function() {
6070
+ var passed = false;
6071
+ waitForTick(function() {
6072
+ passed = true;
6073
+ });
6074
+ return function(callback) {
6075
+ if (passed) {
6076
+ callback();
6077
+ } else {
6078
+ waitForTick(callback);
6079
+ }
6080
+ };
6081
+ };
6082
+ }];
6083
+ };
6084
+
6085
+ var $$AnimateRunnerFactoryProvider = /** @this */ function() {
6086
+ this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$$isDocumentHidden', '$timeout',
6087
+ function($q, $sniffer, $$animateAsyncRun, $$isDocumentHidden, $timeout) {
6088
+
6089
+ var INITIAL_STATE = 0;
6090
+ var DONE_PENDING_STATE = 1;
6091
+ var DONE_COMPLETE_STATE = 2;
6092
+
6093
+ AnimateRunner.chain = function(chain, callback) {
6094
+ var index = 0;
6095
+
6096
+ next();
6097
+ function next() {
6098
+ if (index === chain.length) {
6099
+ callback(true);
6100
+ return;
6101
+ }
6102
+
6103
+ chain[index](function(response) {
6104
+ if (response === false) {
6105
+ callback(false);
6106
+ return;
6107
+ }
6108
+ index++;
6109
+ next();
6110
+ });
6111
+ }
6112
+ };
6113
+
6114
+ AnimateRunner.all = function(runners, callback) {
6115
+ var count = 0;
6116
+ var status = true;
6117
+ forEach(runners, function(runner) {
6118
+ runner.done(onProgress);
6119
+ });
6120
+
6121
+ function onProgress(response) {
6122
+ status = status && response;
6123
+ if (++count === runners.length) {
6124
+ callback(status);
6125
+ }
6126
+ }
6127
+ };
6128
+
6129
+ function AnimateRunner(host) {
6130
+ this.setHost(host);
6131
+
6132
+ var rafTick = $$animateAsyncRun();
6133
+ var timeoutTick = function(fn) {
6134
+ $timeout(fn, 0, false);
6135
+ };
6136
+
6137
+ this._doneCallbacks = [];
6138
+ this._tick = function(fn) {
6139
+ if ($$isDocumentHidden()) {
6140
+ timeoutTick(fn);
6141
+ } else {
6142
+ rafTick(fn);
6143
+ }
6144
+ };
6145
+ this._state = 0;
6146
+ }
6147
+
6148
+ AnimateRunner.prototype = {
6149
+ setHost: function(host) {
6150
+ this.host = host || {};
6151
+ },
6152
+
6153
+ done: function(fn) {
6154
+ if (this._state === DONE_COMPLETE_STATE) {
6155
+ fn();
6156
+ } else {
6157
+ this._doneCallbacks.push(fn);
6158
+ }
6159
+ },
6160
+
6161
+ progress: noop,
6162
+
6163
+ getPromise: function() {
6164
+ if (!this.promise) {
6165
+ var self = this;
6166
+ this.promise = $q(function(resolve, reject) {
6167
+ self.done(function(status) {
6168
+ if (status === false) {
6169
+ reject();
6170
+ } else {
6171
+ resolve();
6172
+ }
6173
+ });
6174
+ });
6175
+ }
6176
+ return this.promise;
6177
+ },
6178
+
6179
+ then: function(resolveHandler, rejectHandler) {
6180
+ return this.getPromise().then(resolveHandler, rejectHandler);
6181
+ },
6182
+
6183
+ 'catch': function(handler) {
6184
+ return this.getPromise()['catch'](handler);
6185
+ },
6186
+
6187
+ 'finally': function(handler) {
6188
+ return this.getPromise()['finally'](handler);
6189
+ },
6190
+
6191
+ pause: function() {
6192
+ if (this.host.pause) {
6193
+ this.host.pause();
6194
+ }
6195
+ },
6196
+
6197
+ resume: function() {
6198
+ if (this.host.resume) {
6199
+ this.host.resume();
6200
+ }
6201
+ },
6202
+
6203
+ end: function() {
6204
+ if (this.host.end) {
6205
+ this.host.end();
6206
+ }
6207
+ this._resolve(true);
6208
+ },
6209
+
6210
+ cancel: function() {
6211
+ if (this.host.cancel) {
6212
+ this.host.cancel();
6213
+ }
6214
+ this._resolve(false);
6215
+ },
6216
+
6217
+ complete: function(response) {
6218
+ var self = this;
6219
+ if (self._state === INITIAL_STATE) {
6220
+ self._state = DONE_PENDING_STATE;
6221
+ self._tick(function() {
6222
+ self._resolve(response);
6223
+ });
6224
+ }
6225
+ },
6226
+
6227
+ _resolve: function(response) {
6228
+ if (this._state !== DONE_COMPLETE_STATE) {
6229
+ forEach(this._doneCallbacks, function(fn) {
6230
+ fn(response);
6231
+ });
6232
+ this._doneCallbacks.length = 0;
6233
+ this._state = DONE_COMPLETE_STATE;
6234
+ }
6235
+ }
6236
+ };
6237
+
6238
+ return AnimateRunner;
6239
+ }];
6240
+ };
6241
+
6242
+ /* exported $CoreAnimateCssProvider */
6243
+
6244
+ /**
6245
+ * @ngdoc service
6246
+ * @name $animateCss
6247
+ * @kind object
6248
+ * @this
6249
+ *
6250
+ * @description
6251
+ * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
6252
+ * then the `$animateCss` service will actually perform animations.
6253
+ *
6254
+ * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
6255
+ */
6256
+ var $CoreAnimateCssProvider = function() {
6257
+ this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) {
6258
+
6259
+ return function(element, initialOptions) {
6260
+ // all of the animation functions should create
6261
+ // a copy of the options data, however, if a
6262
+ // parent service has already created a copy then
6263
+ // we should stick to using that
6264
+ var options = initialOptions || {};
6265
+ if (!options.$$prepared) {
6266
+ options = copy(options);
6267
+ }
6268
+
6269
+ // there is no point in applying the styles since
6270
+ // there is no animation that goes on at all in
6271
+ // this version of $animateCss.
6272
+ if (options.cleanupStyles) {
6273
+ options.from = options.to = null;
6274
+ }
6275
+
6276
+ if (options.from) {
6277
+ element.css(options.from);
6278
+ options.from = null;
6279
+ }
6280
+
6281
+ var closed, runner = new $$AnimateRunner();
6282
+ return {
6283
+ start: run,
6284
+ end: run
6285
+ };
6286
+
6287
+ function run() {
6288
+ $$rAF(function() {
6289
+ applyAnimationContents();
6290
+ if (!closed) {
6291
+ runner.complete();
6292
+ }
6293
+ closed = true;
6294
+ });
6295
+ return runner;
6296
+ }
6297
+
6298
+ function applyAnimationContents() {
6299
+ if (options.addClass) {
6300
+ element.addClass(options.addClass);
6301
+ options.addClass = null;
6302
+ }
6303
+ if (options.removeClass) {
6304
+ element.removeClass(options.removeClass);
6305
+ options.removeClass = null;
6306
+ }
6307
+ if (options.to) {
6308
+ element.css(options.to);
6309
+ options.to = null;
6310
+ }
6311
+ }
6312
+ };
6313
+ }];
6314
+ };
6315
+
6316
+ /* global stripHash: true */
6317
+
6318
+ /**
6319
+ * ! This is a private undocumented service !
6320
+ *
6321
+ * @name $browser
6322
+ * @requires $log
6323
+ * @description
6324
+ * This object has two goals:
6325
+ *
6326
+ * - hide all the global state in the browser caused by the window object
6327
+ * - abstract away all the browser specific features and inconsistencies
6328
+ *
6329
+ * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
6330
+ * service, which can be used for convenient testing of the application without the interaction with
6331
+ * the real browser apis.
6332
+ */
6333
+ /**
6334
+ * @param {object} window The global window object.
6335
+ * @param {object} document jQuery wrapped document.
6336
+ * @param {object} $log window.console or an object with the same interface.
6337
+ * @param {object} $sniffer $sniffer service
6338
+ */
6339
+ function Browser(window, document, $log, $sniffer) {
6340
+ var self = this,
6341
+ location = window.location,
6342
+ history = window.history,
6343
+ setTimeout = window.setTimeout,
6344
+ clearTimeout = window.clearTimeout,
6345
+ pendingDeferIds = {};
6346
+
6347
+ self.isMock = false;
6348
+
6349
+ var outstandingRequestCount = 0;
6350
+ var outstandingRequestCallbacks = [];
6351
+
6352
+ // TODO(vojta): remove this temporary api
6353
+ self.$$completeOutstandingRequest = completeOutstandingRequest;
6354
+ self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
6355
+
6356
+ /**
6357
+ * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
6358
+ * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
6359
+ */
6360
+ function completeOutstandingRequest(fn) {
6361
+ try {
6362
+ fn.apply(null, sliceArgs(arguments, 1));
6363
+ } finally {
6364
+ outstandingRequestCount--;
6365
+ if (outstandingRequestCount === 0) {
6366
+ while (outstandingRequestCallbacks.length) {
6367
+ try {
6368
+ outstandingRequestCallbacks.pop()();
6369
+ } catch (e) {
6370
+ $log.error(e);
6371
+ }
6372
+ }
6373
+ }
6374
+ }
6375
+ }
6376
+
6377
+ function getHash(url) {
6378
+ var index = url.indexOf('#');
6379
+ return index === -1 ? '' : url.substr(index);
6380
+ }
6381
+
6382
+ /**
6383
+ * @private
6384
+ * Note: this method is used only by scenario runner
6385
+ * TODO(vojta): prefix this method with $$ ?
6386
+ * @param {function()} callback Function that will be called when no outstanding request
6387
+ */
6388
+ self.notifyWhenNoOutstandingRequests = function(callback) {
6389
+ if (outstandingRequestCount === 0) {
6390
+ callback();
6391
+ } else {
6392
+ outstandingRequestCallbacks.push(callback);
6393
+ }
6394
+ };
6395
+
6396
+ //////////////////////////////////////////////////////////////
6397
+ // URL API
6398
+ //////////////////////////////////////////////////////////////
6399
+
6400
+ var cachedState, lastHistoryState,
6401
+ lastBrowserUrl = location.href,
6402
+ baseElement = document.find('base'),
6403
+ pendingLocation = null,
6404
+ getCurrentState = !$sniffer.history ? noop : function getCurrentState() {
6405
+ try {
6406
+ return history.state;
6407
+ } catch (e) {
6408
+ // MSIE can reportedly throw when there is no state (UNCONFIRMED).
6409
+ }
6410
+ };
6411
+
6412
+ cacheState();
6413
+
6414
+ /**
6415
+ * @name $browser#url
6416
+ *
6417
+ * @description
6418
+ * GETTER:
6419
+ * Without any argument, this method just returns current value of location.href.
6420
+ *
6421
+ * SETTER:
6422
+ * With at least one argument, this method sets url to new value.
6423
+ * If html5 history api supported, pushState/replaceState is used, otherwise
6424
+ * location.href/location.replace is used.
6425
+ * Returns its own instance to allow chaining
6426
+ *
6427
+ * NOTE: this api is intended for use only by the $location service. Please use the
6428
+ * {@link ng.$location $location service} to change url.
6429
+ *
6430
+ * @param {string} url New url (when used as setter)
6431
+ * @param {boolean=} replace Should new url replace current history record?
6432
+ * @param {object=} state object to use with pushState/replaceState
6433
+ */
6434
+ self.url = function(url, replace, state) {
6435
+ // In modern browsers `history.state` is `null` by default; treating it separately
6436
+ // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
6437
+ // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
6438
+ if (isUndefined(state)) {
6439
+ state = null;
6440
+ }
6441
+
6442
+ // Android Browser BFCache causes location, history reference to become stale.
6443
+ if (location !== window.location) location = window.location;
6444
+ if (history !== window.history) history = window.history;
6445
+
6446
+ // setter
6447
+ if (url) {
6448
+ var sameState = lastHistoryState === state;
6449
+
6450
+ // Don't change anything if previous and current URLs and states match. This also prevents
6451
+ // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
6452
+ // See https://github.com/angular/angular.js/commit/ffb2701
6453
+ if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
6454
+ return self;
6455
+ }
6456
+ var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
6457
+ lastBrowserUrl = url;
6458
+ lastHistoryState = state;
6459
+ // Don't use history API if only the hash changed
6460
+ // due to a bug in IE10/IE11 which leads
6461
+ // to not firing a `hashchange` nor `popstate` event
6462
+ // in some cases (see #9143).
6463
+ if ($sniffer.history && (!sameBase || !sameState)) {
6464
+ history[replace ? 'replaceState' : 'pushState'](state, '', url);
6465
+ cacheState();
6466
+ } else {
6467
+ if (!sameBase) {
6468
+ pendingLocation = url;
6469
+ }
6470
+ if (replace) {
6471
+ location.replace(url);
6472
+ } else if (!sameBase) {
6473
+ location.href = url;
6474
+ } else {
6475
+ location.hash = getHash(url);
6476
+ }
6477
+ if (location.href !== url) {
6478
+ pendingLocation = url;
6479
+ }
6480
+ }
6481
+ if (pendingLocation) {
6482
+ pendingLocation = url;
6483
+ }
6484
+ return self;
6485
+ // getter
6486
+ } else {
6487
+ // - pendingLocation is needed as browsers don't allow to read out
6488
+ // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
6489
+ // https://openradar.appspot.com/22186109).
6490
+ // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
6491
+ return pendingLocation || location.href.replace(/%27/g,'\'');
6492
+ }
6493
+ };
6494
+
6495
+ /**
6496
+ * @name $browser#state
6497
+ *
6498
+ * @description
6499
+ * This method is a getter.
6500
+ *
6501
+ * Return history.state or null if history.state is undefined.
6502
+ *
6503
+ * @returns {object} state
6504
+ */
6505
+ self.state = function() {
6506
+ return cachedState;
6507
+ };
6508
+
6509
+ var urlChangeListeners = [],
6510
+ urlChangeInit = false;
6511
+
6512
+ function cacheStateAndFireUrlChange() {
6513
+ pendingLocation = null;
6514
+ fireStateOrUrlChange();
6515
+ }
6516
+
6517
+ // This variable should be used *only* inside the cacheState function.
6518
+ var lastCachedState = null;
6519
+ function cacheState() {
6520
+ // This should be the only place in $browser where `history.state` is read.
6521
+ cachedState = getCurrentState();
6522
+ cachedState = isUndefined(cachedState) ? null : cachedState;
6523
+
6524
+ // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
6525
+ if (equals(cachedState, lastCachedState)) {
6526
+ cachedState = lastCachedState;
6527
+ }
6528
+
6529
+ lastCachedState = cachedState;
6530
+ lastHistoryState = cachedState;
6531
+ }
6532
+
6533
+ function fireStateOrUrlChange() {
6534
+ var prevLastHistoryState = lastHistoryState;
6535
+ cacheState();
6536
+
6537
+ if (lastBrowserUrl === self.url() && prevLastHistoryState === cachedState) {
6538
+ return;
6539
+ }
6540
+
6541
+ lastBrowserUrl = self.url();
6542
+ lastHistoryState = cachedState;
6543
+ forEach(urlChangeListeners, function(listener) {
6544
+ listener(self.url(), cachedState);
6545
+ });
6546
+ }
6547
+
6548
+ /**
6549
+ * @name $browser#onUrlChange
6550
+ *
6551
+ * @description
6552
+ * Register callback function that will be called, when url changes.
6553
+ *
6554
+ * It's only called when the url is changed from outside of angular:
6555
+ * - user types different url into address bar
6556
+ * - user clicks on history (forward/back) button
6557
+ * - user clicks on a link
6558
+ *
6559
+ * It's not called when url is changed by $browser.url() method
6560
+ *
6561
+ * The listener gets called with new url as parameter.
6562
+ *
6563
+ * NOTE: this api is intended for use only by the $location service. Please use the
6564
+ * {@link ng.$location $location service} to monitor url changes in angular apps.
6565
+ *
6566
+ * @param {function(string)} listener Listener function to be called when url changes.
6567
+ * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
6568
+ */
6569
+ self.onUrlChange = function(callback) {
6570
+ // TODO(vojta): refactor to use node's syntax for events
6571
+ if (!urlChangeInit) {
6572
+ // We listen on both (hashchange/popstate) when available, as some browsers don't
6573
+ // fire popstate when user changes the address bar and don't fire hashchange when url
6574
+ // changed by push/replaceState
6575
+
6576
+ // html5 history api - popstate event
6577
+ if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
6578
+ // hashchange event
6579
+ jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
6580
+
6581
+ urlChangeInit = true;
6582
+ }
6583
+
6584
+ urlChangeListeners.push(callback);
6585
+ return callback;
6586
+ };
6587
+
6588
+ /**
6589
+ * @private
6590
+ * Remove popstate and hashchange handler from window.
6591
+ *
6592
+ * NOTE: this api is intended for use only by $rootScope.
6593
+ */
6594
+ self.$$applicationDestroyed = function() {
6595
+ jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
6596
+ };
6597
+
6598
+ /**
6599
+ * Checks whether the url has changed outside of Angular.
6600
+ * Needs to be exported to be able to check for changes that have been done in sync,
6601
+ * as hashchange/popstate events fire in async.
6602
+ */
6603
+ self.$$checkUrlChange = fireStateOrUrlChange;
6604
+
6605
+ //////////////////////////////////////////////////////////////
6606
+ // Misc API
6607
+ //////////////////////////////////////////////////////////////
6608
+
6609
+ /**
6610
+ * @name $browser#baseHref
6611
+ *
6612
+ * @description
6613
+ * Returns current <base href>
6614
+ * (always relative - without domain)
6615
+ *
6616
+ * @returns {string} The current base href
6617
+ */
6618
+ self.baseHref = function() {
6619
+ var href = baseElement.attr('href');
6620
+ return href ? href.replace(/^(https?:)?\/\/[^/]*/, '') : '';
6621
+ };
6622
+
6623
+ /**
6624
+ * @name $browser#defer
6625
+ * @param {function()} fn A function, who's execution should be deferred.
6626
+ * @param {number=} [delay=0] of milliseconds to defer the function execution.
6627
+ * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
6628
+ *
6629
+ * @description
6630
+ * Executes a fn asynchronously via `setTimeout(fn, delay)`.
6631
+ *
6632
+ * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
6633
+ * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
6634
+ * via `$browser.defer.flush()`.
6635
+ *
6636
+ */
6637
+ self.defer = function(fn, delay) {
6638
+ var timeoutId;
6639
+ outstandingRequestCount++;
6640
+ timeoutId = setTimeout(function() {
6641
+ delete pendingDeferIds[timeoutId];
6642
+ completeOutstandingRequest(fn);
6643
+ }, delay || 0);
6644
+ pendingDeferIds[timeoutId] = true;
6645
+ return timeoutId;
6646
+ };
6647
+
6648
+
6649
+ /**
6650
+ * @name $browser#defer.cancel
6651
+ *
6652
+ * @description
6653
+ * Cancels a deferred task identified with `deferId`.
6654
+ *
6655
+ * @param {*} deferId Token returned by the `$browser.defer` function.
6656
+ * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
6657
+ * canceled.
6658
+ */
6659
+ self.defer.cancel = function(deferId) {
6660
+ if (pendingDeferIds[deferId]) {
6661
+ delete pendingDeferIds[deferId];
6662
+ clearTimeout(deferId);
6663
+ completeOutstandingRequest(noop);
6664
+ return true;
6665
+ }
6666
+ return false;
6667
+ };
6668
+
6669
+ }
6670
+
6671
+ /** @this */
6672
+ function $BrowserProvider() {
6673
+ this.$get = ['$window', '$log', '$sniffer', '$document',
6674
+ function($window, $log, $sniffer, $document) {
6675
+ return new Browser($window, $document, $log, $sniffer);
6676
+ }];
6677
+ }
6678
+
6679
+ /**
6680
+ * @ngdoc service
6681
+ * @name $cacheFactory
6682
+ * @this
6683
+ *
6684
+ * @description
6685
+ * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
6686
+ * them.
6687
+ *
6688
+ * ```js
6689
+ *
6690
+ * var cache = $cacheFactory('cacheId');
6691
+ * expect($cacheFactory.get('cacheId')).toBe(cache);
6692
+ * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
6693
+ *
6694
+ * cache.put("key", "value");
6695
+ * cache.put("another key", "another value");
6696
+ *
6697
+ * // We've specified no options on creation
6698
+ * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
6699
+ *
6700
+ * ```
6701
+ *
6702
+ *
6703
+ * @param {string} cacheId Name or id of the newly created cache.
6704
+ * @param {object=} options Options object that specifies the cache behavior. Properties:
6705
+ *
6706
+ * - `{number=}` `capacity` — turns the cache into LRU cache.
6707
+ *
6708
+ * @returns {object} Newly created cache object with the following set of methods:
6709
+ *
6710
+ * - `{object}` `info()` — Returns id, size, and options of cache.
6711
+ * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
6712
+ * it.
6713
+ * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
6714
+ * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
6715
+ * - `{void}` `removeAll()` — Removes all cached values.
6716
+ * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
6717
+ *
6718
+ * @example
6719
+ <example module="cacheExampleApp" name="cache-factory">
6720
+ <file name="index.html">
6721
+ <div ng-controller="CacheController">
6722
+ <input ng-model="newCacheKey" placeholder="Key">
6723
+ <input ng-model="newCacheValue" placeholder="Value">
6724
+ <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
6725
+
6726
+ <p ng-if="keys.length">Cached Values</p>
6727
+ <div ng-repeat="key in keys">
6728
+ <span ng-bind="key"></span>
6729
+ <span>: </span>
6730
+ <b ng-bind="cache.get(key)"></b>
6731
+ </div>
6732
+
6733
+ <p>Cache Info</p>
6734
+ <div ng-repeat="(key, value) in cache.info()">
6735
+ <span ng-bind="key"></span>
6736
+ <span>: </span>
6737
+ <b ng-bind="value"></b>
6738
+ </div>
6739
+ </div>
6740
+ </file>
6741
+ <file name="script.js">
6742
+ angular.module('cacheExampleApp', []).
6743
+ controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
6744
+ $scope.keys = [];
6745
+ $scope.cache = $cacheFactory('cacheId');
6746
+ $scope.put = function(key, value) {
6747
+ if (angular.isUndefined($scope.cache.get(key))) {
6748
+ $scope.keys.push(key);
6749
+ }
6750
+ $scope.cache.put(key, angular.isUndefined(value) ? null : value);
6751
+ };
6752
+ }]);
6753
+ </file>
6754
+ <file name="style.css">
6755
+ p {
6756
+ margin: 10px 0 3px;
6757
+ }
6758
+ </file>
6759
+ </example>
6760
+ */
6761
+ function $CacheFactoryProvider() {
6762
+
6763
+ this.$get = function() {
6764
+ var caches = {};
6765
+
6766
+ function cacheFactory(cacheId, options) {
6767
+ if (cacheId in caches) {
6768
+ throw minErr('$cacheFactory')('iid', 'CacheId \'{0}\' is already taken!', cacheId);
6769
+ }
6770
+
6771
+ var size = 0,
6772
+ stats = extend({}, options, {id: cacheId}),
6773
+ data = createMap(),
6774
+ capacity = (options && options.capacity) || Number.MAX_VALUE,
6775
+ lruHash = createMap(),
6776
+ freshEnd = null,
6777
+ staleEnd = null;
6778
+
6779
+ /**
6780
+ * @ngdoc type
6781
+ * @name $cacheFactory.Cache
6782
+ *
6783
+ * @description
6784
+ * A cache object used to store and retrieve data, primarily used by
6785
+ * {@link $http $http} and the {@link ng.directive:script script} directive to cache
6786
+ * templates and other data.
6787
+ *
6788
+ * ```js
6789
+ * angular.module('superCache')
6790
+ * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
6791
+ * return $cacheFactory('super-cache');
6792
+ * }]);
6793
+ * ```
6794
+ *
6795
+ * Example test:
6796
+ *
6797
+ * ```js
6798
+ * it('should behave like a cache', inject(function(superCache) {
6799
+ * superCache.put('key', 'value');
6800
+ * superCache.put('another key', 'another value');
6801
+ *
6802
+ * expect(superCache.info()).toEqual({
6803
+ * id: 'super-cache',
6804
+ * size: 2
6805
+ * });
6806
+ *
6807
+ * superCache.remove('another key');
6808
+ * expect(superCache.get('another key')).toBeUndefined();
6809
+ *
6810
+ * superCache.removeAll();
6811
+ * expect(superCache.info()).toEqual({
6812
+ * id: 'super-cache',
6813
+ * size: 0
6814
+ * });
6815
+ * }));
6816
+ * ```
6817
+ */
6818
+ return (caches[cacheId] = {
6819
+
6820
+ /**
6821
+ * @ngdoc method
6822
+ * @name $cacheFactory.Cache#put
6823
+ * @kind function
6824
+ *
6825
+ * @description
6826
+ * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
6827
+ * retrieved later, and incrementing the size of the cache if the key was not already
6828
+ * present in the cache. If behaving like an LRU cache, it will also remove stale
6829
+ * entries from the set.
6830
+ *
6831
+ * It will not insert undefined values into the cache.
6832
+ *
6833
+ * @param {string} key the key under which the cached data is stored.
6834
+ * @param {*} value the value to store alongside the key. If it is undefined, the key
6835
+ * will not be stored.
6836
+ * @returns {*} the value stored.
6837
+ */
6838
+ put: function(key, value) {
6839
+ if (isUndefined(value)) return;
6840
+ if (capacity < Number.MAX_VALUE) {
6841
+ var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
6842
+
6843
+ refresh(lruEntry);
6844
+ }
6845
+
6846
+ if (!(key in data)) size++;
6847
+ data[key] = value;
6848
+
6849
+ if (size > capacity) {
6850
+ this.remove(staleEnd.key);
6851
+ }
6852
+
6853
+ return value;
6854
+ },
6855
+
6856
+ /**
6857
+ * @ngdoc method
6858
+ * @name $cacheFactory.Cache#get
6859
+ * @kind function
6860
+ *
6861
+ * @description
6862
+ * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
6863
+ *
6864
+ * @param {string} key the key of the data to be retrieved
6865
+ * @returns {*} the value stored.
6866
+ */
6867
+ get: function(key) {
6868
+ if (capacity < Number.MAX_VALUE) {
6869
+ var lruEntry = lruHash[key];
6870
+
6871
+ if (!lruEntry) return;
6872
+
6873
+ refresh(lruEntry);
6874
+ }
6875
+
6876
+ return data[key];
6877
+ },
6878
+
6879
+
6880
+ /**
6881
+ * @ngdoc method
6882
+ * @name $cacheFactory.Cache#remove
6883
+ * @kind function
6884
+ *
6885
+ * @description
6886
+ * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
6887
+ *
6888
+ * @param {string} key the key of the entry to be removed
6889
+ */
6890
+ remove: function(key) {
6891
+ if (capacity < Number.MAX_VALUE) {
6892
+ var lruEntry = lruHash[key];
6893
+
6894
+ if (!lruEntry) return;
6895
+
6896
+ if (lruEntry === freshEnd) freshEnd = lruEntry.p;
6897
+ if (lruEntry === staleEnd) staleEnd = lruEntry.n;
6898
+ link(lruEntry.n,lruEntry.p);
6899
+
6900
+ delete lruHash[key];
6901
+ }
6902
+
6903
+ if (!(key in data)) return;
6904
+
6905
+ delete data[key];
6906
+ size--;
6907
+ },
6908
+
6909
+
6910
+ /**
6911
+ * @ngdoc method
6912
+ * @name $cacheFactory.Cache#removeAll
6913
+ * @kind function
6914
+ *
6915
+ * @description
6916
+ * Clears the cache object of any entries.
6917
+ */
6918
+ removeAll: function() {
6919
+ data = createMap();
6920
+ size = 0;
6921
+ lruHash = createMap();
6922
+ freshEnd = staleEnd = null;
6923
+ },
6924
+
6925
+
6926
+ /**
6927
+ * @ngdoc method
6928
+ * @name $cacheFactory.Cache#destroy
6929
+ * @kind function
6930
+ *
6931
+ * @description
6932
+ * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
6933
+ * removing it from the {@link $cacheFactory $cacheFactory} set.
6934
+ */
6935
+ destroy: function() {
6936
+ data = null;
6937
+ stats = null;
6938
+ lruHash = null;
6939
+ delete caches[cacheId];
6940
+ },
6941
+
6942
+
6943
+ /**
6944
+ * @ngdoc method
6945
+ * @name $cacheFactory.Cache#info
6946
+ * @kind function
6947
+ *
6948
+ * @description
6949
+ * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
6950
+ *
6951
+ * @returns {object} an object with the following properties:
6952
+ * <ul>
6953
+ * <li>**id**: the id of the cache instance</li>
6954
+ * <li>**size**: the number of entries kept in the cache instance</li>
6955
+ * <li>**...**: any additional properties from the options object when creating the
6956
+ * cache.</li>
6957
+ * </ul>
6958
+ */
6959
+ info: function() {
6960
+ return extend({}, stats, {size: size});
6961
+ }
6962
+ });
6963
+
6964
+
6965
+ /**
6966
+ * makes the `entry` the freshEnd of the LRU linked list
6967
+ */
6968
+ function refresh(entry) {
6969
+ if (entry !== freshEnd) {
6970
+ if (!staleEnd) {
6971
+ staleEnd = entry;
6972
+ } else if (staleEnd === entry) {
6973
+ staleEnd = entry.n;
6974
+ }
6975
+
6976
+ link(entry.n, entry.p);
6977
+ link(entry, freshEnd);
6978
+ freshEnd = entry;
6979
+ freshEnd.n = null;
6980
+ }
6981
+ }
6982
+
6983
+
6984
+ /**
6985
+ * bidirectionally links two entries of the LRU linked list
6986
+ */
6987
+ function link(nextEntry, prevEntry) {
6988
+ if (nextEntry !== prevEntry) {
6989
+ if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
6990
+ if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
6991
+ }
6992
+ }
6993
+ }
6994
+
6995
+
6996
+ /**
6997
+ * @ngdoc method
6998
+ * @name $cacheFactory#info
6999
+ *
7000
+ * @description
7001
+ * Get information about all the caches that have been created
7002
+ *
7003
+ * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
7004
+ */
7005
+ cacheFactory.info = function() {
7006
+ var info = {};
7007
+ forEach(caches, function(cache, cacheId) {
7008
+ info[cacheId] = cache.info();
7009
+ });
7010
+ return info;
7011
+ };
7012
+
7013
+
7014
+ /**
7015
+ * @ngdoc method
7016
+ * @name $cacheFactory#get
7017
+ *
7018
+ * @description
7019
+ * Get access to a cache object by the `cacheId` used when it was created.
7020
+ *
7021
+ * @param {string} cacheId Name or id of a cache to access.
7022
+ * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
7023
+ */
7024
+ cacheFactory.get = function(cacheId) {
7025
+ return caches[cacheId];
7026
+ };
7027
+
7028
+
7029
+ return cacheFactory;
7030
+ };
7031
+ }
7032
+
7033
+ /**
7034
+ * @ngdoc service
7035
+ * @name $templateCache
7036
+ * @this
7037
+ *
7038
+ * @description
7039
+ * The first time a template is used, it is loaded in the template cache for quick retrieval. You
7040
+ * can load templates directly into the cache in a `script` tag, or by consuming the
7041
+ * `$templateCache` service directly.
7042
+ *
7043
+ * Adding via the `script` tag:
7044
+ *
7045
+ * ```html
7046
+ * <script type="text/ng-template" id="templateId.html">
7047
+ * <p>This is the content of the template</p>
7048
+ * </script>
7049
+ * ```
7050
+ *
7051
+ * **Note:** the `script` tag containing the template does not need to be included in the `head` of
7052
+ * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
7053
+ * element with ng-app attribute), otherwise the template will be ignored.
7054
+ *
7055
+ * Adding via the `$templateCache` service:
7056
+ *
7057
+ * ```js
7058
+ * var myApp = angular.module('myApp', []);
7059
+ * myApp.run(function($templateCache) {
7060
+ * $templateCache.put('templateId.html', 'This is the content of the template');
7061
+ * });
7062
+ * ```
7063
+ *
7064
+ * To retrieve the template later, simply use it in your component:
7065
+ * ```js
7066
+ * myApp.component('myComponent', {
7067
+ * templateUrl: 'templateId.html'
7068
+ * });
7069
+ * ```
7070
+ *
7071
+ * or get it via the `$templateCache` service:
7072
+ * ```js
7073
+ * $templateCache.get('templateId.html')
7074
+ * ```
7075
+ *
7076
+ * See {@link ng.$cacheFactory $cacheFactory}.
7077
+ *
7078
+ */
7079
+ function $TemplateCacheProvider() {
7080
+ this.$get = ['$cacheFactory', function($cacheFactory) {
7081
+ return $cacheFactory('templates');
7082
+ }];
7083
+ }
7084
+
7085
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
7086
+ * Any commits to this file should be reviewed with security in mind. *
7087
+ * Changes to this file can potentially create security vulnerabilities. *
7088
+ * An approval from 2 Core members with history of modifying *
7089
+ * this file is required. *
7090
+ * *
7091
+ * Does the change somehow allow for arbitrary javascript to be executed? *
7092
+ * Or allows for someone to change the prototype of built-in objects? *
7093
+ * Or gives undesired access to variables like document or window? *
7094
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
7095
+
7096
+ /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
7097
+ *
7098
+ * DOM-related variables:
7099
+ *
7100
+ * - "node" - DOM Node
7101
+ * - "element" - DOM Element or Node
7102
+ * - "$node" or "$element" - jqLite-wrapped node or element
7103
+ *
7104
+ *
7105
+ * Compiler related stuff:
7106
+ *
7107
+ * - "linkFn" - linking fn of a single directive
7108
+ * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
7109
+ * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
7110
+ * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
7111
+ */
7112
+
7113
+
7114
+ /**
7115
+ * @ngdoc service
7116
+ * @name $compile
7117
+ * @kind function
7118
+ *
7119
+ * @description
7120
+ * Compiles an HTML string or DOM into a template and produces a template function, which
7121
+ * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
7122
+ *
7123
+ * The compilation is a process of walking the DOM tree and matching DOM elements to
7124
+ * {@link ng.$compileProvider#directive directives}.
7125
+ *
7126
+ * <div class="alert alert-warning">
7127
+ * **Note:** This document is an in-depth reference of all directive options.
7128
+ * For a gentle introduction to directives with examples of common use cases,
7129
+ * see the {@link guide/directive directive guide}.
7130
+ * </div>
7131
+ *
7132
+ * ## Comprehensive Directive API
7133
+ *
7134
+ * There are many different options for a directive.
7135
+ *
7136
+ * The difference resides in the return value of the factory function.
7137
+ * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)}
7138
+ * that defines the directive properties, or just the `postLink` function (all other properties will have
7139
+ * the default values).
7140
+ *
7141
+ * <div class="alert alert-success">
7142
+ * **Best Practice:** It's recommended to use the "directive definition object" form.
7143
+ * </div>
7144
+ *
7145
+ * Here's an example directive declared with a Directive Definition Object:
7146
+ *
7147
+ * ```js
7148
+ * var myModule = angular.module(...);
7149
+ *
7150
+ * myModule.directive('directiveName', function factory(injectables) {
7151
+ * var directiveDefinitionObject = {
7152
+ * {@link $compile#-priority- priority}: 0,
7153
+ * {@link $compile#-template- template}: '<div></div>', // or // function(tElement, tAttrs) { ... },
7154
+ * // or
7155
+ * // {@link $compile#-templateurl- templateUrl}: 'directive.html', // or // function(tElement, tAttrs) { ... },
7156
+ * {@link $compile#-transclude- transclude}: false,
7157
+ * {@link $compile#-restrict- restrict}: 'A',
7158
+ * {@link $compile#-templatenamespace- templateNamespace}: 'html',
7159
+ * {@link $compile#-scope- scope}: false,
7160
+ * {@link $compile#-controller- controller}: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
7161
+ * {@link $compile#-controlleras- controllerAs}: 'stringIdentifier',
7162
+ * {@link $compile#-bindtocontroller- bindToController}: false,
7163
+ * {@link $compile#-require- require}: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
7164
+ * {@link $compile#-multielement- multiElement}: false,
7165
+ * {@link $compile#-compile- compile}: function compile(tElement, tAttrs, transclude) {
7166
+ * return {
7167
+ * {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
7168
+ * {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
7169
+ * }
7170
+ * // or
7171
+ * // return function postLink( ... ) { ... }
7172
+ * },
7173
+ * // or
7174
+ * // {@link $compile#-link- link}: {
7175
+ * // {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
7176
+ * // {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
7177
+ * // }
7178
+ * // or
7179
+ * // {@link $compile#-link- link}: function postLink( ... ) { ... }
7180
+ * };
7181
+ * return directiveDefinitionObject;
7182
+ * });
7183
+ * ```
7184
+ *
7185
+ * <div class="alert alert-warning">
7186
+ * **Note:** Any unspecified options will use the default value. You can see the default values below.
7187
+ * </div>
7188
+ *
7189
+ * Therefore the above can be simplified as:
7190
+ *
7191
+ * ```js
7192
+ * var myModule = angular.module(...);
7193
+ *
7194
+ * myModule.directive('directiveName', function factory(injectables) {
7195
+ * var directiveDefinitionObject = {
7196
+ * link: function postLink(scope, iElement, iAttrs) { ... }
7197
+ * };
7198
+ * return directiveDefinitionObject;
7199
+ * // or
7200
+ * // return function postLink(scope, iElement, iAttrs) { ... }
7201
+ * });
7202
+ * ```
7203
+ *
7204
+ * ### Life-cycle hooks
7205
+ * Directive controllers can provide the following methods that are called by Angular at points in the life-cycle of the
7206
+ * directive:
7207
+ * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
7208
+ * had their bindings initialized (and before the pre &amp; post linking functions for the directives on
7209
+ * this element). This is a good place to put initialization code for your controller.
7210
+ * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
7211
+ * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
7212
+ * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
7213
+ * component such as cloning the bound value to prevent accidental mutation of the outer value. Note that this will
7214
+ * also be called when your bindings are initialized.
7215
+ * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on
7216
+ * changes. Any actions that you wish to take in response to the changes that you detect must be
7217
+ * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook
7218
+ * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not
7219
+ * be detected by Angular's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments;
7220
+ * if detecting changes, you must store the previous value(s) for comparison to the current values.
7221
+ * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
7222
+ * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
7223
+ * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
7224
+ * components will have their `$onDestroy()` hook called before child components.
7225
+ * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
7226
+ * function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
7227
+ * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
7228
+ * they are waiting for their template to load asynchronously and their own compilation and linking has been
7229
+ * suspended until that occurs.
7230
+ *
7231
+ * #### Comparison with Angular 2 life-cycle hooks
7232
+ * Angular 2 also uses life-cycle hooks for its components. While the Angular 1 life-cycle hooks are similar there are
7233
+ * some differences that you should be aware of, especially when it comes to moving your code from Angular 1 to Angular 2:
7234
+ *
7235
+ * * Angular 1 hooks are prefixed with `$`, such as `$onInit`. Angular 2 hooks are prefixed with `ng`, such as `ngOnInit`.
7236
+ * * Angular 1 hooks can be defined on the controller prototype or added to the controller inside its constructor.
7237
+ * In Angular 2 you can only define hooks on the prototype of the Component class.
7238
+ * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in Angular 1 than you would to
7239
+ * `ngDoCheck` in Angular 2
7240
+ * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be
7241
+ * propagated throughout the application.
7242
+ * Angular 2 does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an
7243
+ * error or do nothing depending upon the state of `enableProdMode()`.
7244
+ *
7245
+ * #### Life-cycle hook examples
7246
+ *
7247
+ * This example shows how you can check for mutations to a Date object even though the identity of the object
7248
+ * has not changed.
7249
+ *
7250
+ * <example name="doCheckDateExample" module="do-check-module">
7251
+ * <file name="app.js">
7252
+ * angular.module('do-check-module', [])
7253
+ * .component('app', {
7254
+ * template:
7255
+ * 'Month: <input ng-model="$ctrl.month" ng-change="$ctrl.updateDate()">' +
7256
+ * 'Date: {{ $ctrl.date }}' +
7257
+ * '<test date="$ctrl.date"></test>',
7258
+ * controller: function() {
7259
+ * this.date = new Date();
7260
+ * this.month = this.date.getMonth();
7261
+ * this.updateDate = function() {
7262
+ * this.date.setMonth(this.month);
7263
+ * };
7264
+ * }
7265
+ * })
7266
+ * .component('test', {
7267
+ * bindings: { date: '<' },
7268
+ * template:
7269
+ * '<pre>{{ $ctrl.log | json }}</pre>',
7270
+ * controller: function() {
7271
+ * var previousValue;
7272
+ * this.log = [];
7273
+ * this.$doCheck = function() {
7274
+ * var currentValue = this.date && this.date.valueOf();
7275
+ * if (previousValue !== currentValue) {
7276
+ * this.log.push('doCheck: date mutated: ' + this.date);
7277
+ * previousValue = currentValue;
7278
+ * }
7279
+ * };
7280
+ * }
7281
+ * });
7282
+ * </file>
7283
+ * <file name="index.html">
7284
+ * <app></app>
7285
+ * </file>
7286
+ * </example>
7287
+ *
7288
+ * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the
7289
+ * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large
7290
+ * arrays or objects can have a negative impact on your application performance)
7291
+ *
7292
+ * <example name="doCheckArrayExample" module="do-check-module">
7293
+ * <file name="index.html">
7294
+ * <div ng-init="items = []">
7295
+ * <button ng-click="items.push(items.length)">Add Item</button>
7296
+ * <button ng-click="items = []">Reset Items</button>
7297
+ * <pre>{{ items }}</pre>
7298
+ * <test items="items"></test>
7299
+ * </div>
7300
+ * </file>
7301
+ * <file name="app.js">
7302
+ * angular.module('do-check-module', [])
7303
+ * .component('test', {
7304
+ * bindings: { items: '<' },
7305
+ * template:
7306
+ * '<pre>{{ $ctrl.log | json }}</pre>',
7307
+ * controller: function() {
7308
+ * this.log = [];
7309
+ *
7310
+ * this.$doCheck = function() {
7311
+ * if (this.items_ref !== this.items) {
7312
+ * this.log.push('doCheck: items changed');
7313
+ * this.items_ref = this.items;
7314
+ * }
7315
+ * if (!angular.equals(this.items_clone, this.items)) {
7316
+ * this.log.push('doCheck: items mutated');
7317
+ * this.items_clone = angular.copy(this.items);
7318
+ * }
7319
+ * };
7320
+ * }
7321
+ * });
7322
+ * </file>
7323
+ * </example>
7324
+ *
7325
+ *
7326
+ * ### Directive Definition Object
7327
+ *
7328
+ * The directive definition object provides instructions to the {@link ng.$compile
7329
+ * compiler}. The attributes are:
7330
+ *
7331
+ * #### `multiElement`
7332
+ * When this property is set to true (default is `false`), the HTML compiler will collect DOM nodes between
7333
+ * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
7334
+ * together as the directive elements. It is recommended that this feature be used on directives
7335
+ * which are not strictly behavioral (such as {@link ngClick}), and which
7336
+ * do not manipulate or replace child nodes (such as {@link ngInclude}).
7337
+ *
7338
+ * #### `priority`
7339
+ * When there are multiple directives defined on a single DOM element, sometimes it
7340
+ * is necessary to specify the order in which the directives are applied. The `priority` is used
7341
+ * to sort the directives before their `compile` functions get called. Priority is defined as a
7342
+ * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
7343
+ * are also run in priority order, but post-link functions are run in reverse order. The order
7344
+ * of directives with the same priority is undefined. The default priority is `0`.
7345
+ *
7346
+ * #### `terminal`
7347
+ * If set to true then the current `priority` will be the last set of directives
7348
+ * which will execute (any directives at the current priority will still execute
7349
+ * as the order of execution on same `priority` is undefined). Note that expressions
7350
+ * and other directives used in the directive's template will also be excluded from execution.
7351
+ *
7352
+ * #### `scope`
7353
+ * The scope property can be `false`, `true`, or an object:
7354
+ *
7355
+ * * **`false` (default):** No scope will be created for the directive. The directive will use its
7356
+ * parent's scope.
7357
+ *
7358
+ * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
7359
+ * the directive's element. If multiple directives on the same element request a new scope,
7360
+ * only one new scope is created.
7361
+ *
7362
+ * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's template.
7363
+ * The 'isolate' scope differs from normal scope in that it does not prototypically
7364
+ * inherit from its parent scope. This is useful when creating reusable components, which should not
7365
+ * accidentally read or modify data in the parent scope. Note that an isolate scope
7366
+ * directive without a `template` or `templateUrl` will not apply the isolate scope
7367
+ * to its children elements.
7368
+ *
7369
+ * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
7370
+ * directive's element. These local properties are useful for aliasing values for templates. The keys in
7371
+ * the object hash map to the name of the property on the isolate scope; the values define how the property
7372
+ * is bound to the parent scope, via matching attributes on the directive's element:
7373
+ *
7374
+ * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
7375
+ * always a string since DOM attributes are strings. If no `attr` name is specified then the
7376
+ * attribute name is assumed to be the same as the local name. Given `<my-component
7377
+ * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`,
7378
+ * the directive's scope property `localName` will reflect the interpolated value of `hello
7379
+ * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's
7380
+ * scope. The `name` is read from the parent scope (not the directive's scope).
7381
+ *
7382
+ * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression
7383
+ * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope.
7384
+ * If no `attr` name is specified then the attribute name is assumed to be the same as the local
7385
+ * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: {
7386
+ * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the
7387
+ * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in
7388
+ * `localModel` and vice versa. Optional attributes should be marked as such with a question mark:
7389
+ * `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't
7390
+ * optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`})
7391
+ * will be thrown upon discovering changes to the local value, since it will be impossible to sync
7392
+ * them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
7393
+ * method is used for tracking changes, and the equality check is based on object identity.
7394
+ * However, if an object literal or an array literal is passed as the binding expression, the
7395
+ * equality check is done by value (using the {@link angular.equals} function). It's also possible
7396
+ * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection
7397
+ * `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional).
7398
+ *
7399
+ * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an
7400
+ * expression passed via the attribute `attr`. The expression is evaluated in the context of the
7401
+ * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the
7402
+ * local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`.
7403
+ *
7404
+ * For example, given `<my-component my-attr="parentModel">` and directive definition of
7405
+ * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the
7406
+ * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
7407
+ * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however
7408
+ * two caveats:
7409
+ * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply
7410
+ * sets the same value. That means if your bound value is an object, changes to its properties
7411
+ * in the isolated scope will be reflected in the parent scope (because both reference the same object).
7412
+ * 2. one-way binding watches changes to the **identity** of the parent value. That means the
7413
+ * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference
7414
+ * to the value has changed. In most cases, this should not be of concern, but can be important
7415
+ * to know if you one-way bind to an object, and then replace that object in the isolated scope.
7416
+ * If you now change a property of the object in your parent scope, the change will not be
7417
+ * propagated to the isolated scope, because the identity of the object on the parent scope
7418
+ * has not changed. Instead you must assign a new object.
7419
+ *
7420
+ * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
7421
+ * back to the parent. However, it does not make this completely impossible.
7422
+ *
7423
+ * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If
7424
+ * no `attr` name is specified then the attribute name is assumed to be the same as the local name.
7425
+ * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: {
7426
+ * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for
7427
+ * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope
7428
+ * via an expression to the parent scope. This can be done by passing a map of local variable names
7429
+ * and values into the expression wrapper fn. For example, if the expression is `increment(amount)`
7430
+ * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`.
7431
+ *
7432
+ * In general it's possible to apply more than one directive to one element, but there might be limitations
7433
+ * depending on the type of scope required by the directives. The following points will help explain these limitations.
7434
+ * For simplicity only two directives are taken into account, but it is also applicable for several directives:
7435
+ *
7436
+ * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
7437
+ * * **child scope** + **no scope** => Both directives will share one single child scope
7438
+ * * **child scope** + **child scope** => Both directives will share one single child scope
7439
+ * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
7440
+ * its parent's scope
7441
+ * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
7442
+ * be applied to the same element.
7443
+ * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
7444
+ * cannot be applied to the same element.
7445
+ *
7446
+ *
7447
+ * #### `bindToController`
7448
+ * This property is used to bind scope properties directly to the controller. It can be either
7449
+ * `true` or an object hash with the same format as the `scope` property.
7450
+ *
7451
+ * When an isolate scope is used for a directive (see above), `bindToController: true` will
7452
+ * allow a component to have its properties bound to the controller, rather than to scope.
7453
+ *
7454
+ * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller
7455
+ * properties. You can access these bindings once they have been initialized by providing a controller method called
7456
+ * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings
7457
+ * initialized.
7458
+ *
7459
+ * <div class="alert alert-warning">
7460
+ * **Deprecation warning:** if `$compileProcvider.preAssignBindingsEnabled(true)` was called, bindings for non-ES6 class
7461
+ * controllers are bound to `this` before the controller constructor is called but this use is now deprecated. Please
7462
+ * place initialization code that relies upon bindings inside a `$onInit` method on the controller, instead.
7463
+ * </div>
7464
+ *
7465
+ * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property.
7466
+ * This will set up the scope bindings to the controller directly. Note that `scope` can still be used
7467
+ * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate
7468
+ * scope (useful for component directives).
7469
+ *
7470
+ * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`.
7471
+ *
7472
+ *
7473
+ * #### `controller`
7474
+ * Controller constructor function. The controller is instantiated before the
7475
+ * pre-linking phase and can be accessed by other directives (see
7476
+ * `require` attribute). This allows the directives to communicate with each other and augment
7477
+ * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
7478
+ *
7479
+ * * `$scope` - Current scope associated with the element
7480
+ * * `$element` - Current element
7481
+ * * `$attrs` - Current attributes object for the element
7482
+ * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
7483
+ * `function([scope], cloneLinkingFn, futureParentElement, slotName)`:
7484
+ * * `scope`: (optional) override the scope.
7485
+ * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content.
7486
+ * * `futureParentElement` (optional):
7487
+ * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
7488
+ * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
7489
+ * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
7490
+ * and when the `cloneLinkingFn` is passed,
7491
+ * as those elements need to created and cloned in a special way when they are defined outside their
7492
+ * usual containers (e.g. like `<svg>`).
7493
+ * * See also the `directive.templateNamespace` property.
7494
+ * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`)
7495
+ * then the default transclusion is provided.
7496
+ * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns
7497
+ * `true` if the specified slot contains content (i.e. one or more DOM nodes).
7498
+ *
7499
+ * #### `require`
7500
+ * Require another directive and inject its controller as the fourth argument to the linking function. The
7501
+ * `require` property can be a string, an array or an object:
7502
+ * * a **string** containing the name of the directive to pass to the linking function
7503
+ * * an **array** containing the names of directives to pass to the linking function. The argument passed to the
7504
+ * linking function will be an array of controllers in the same order as the names in the `require` property
7505
+ * * an **object** whose property values are the names of the directives to pass to the linking function. The argument
7506
+ * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding
7507
+ * controllers.
7508
+ *
7509
+ * If the `require` property is an object and `bindToController` is truthy, then the required controllers are
7510
+ * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers
7511
+ * have been constructed but before `$onInit` is called.
7512
+ * If the name of the required controller is the same as the local name (the key), the name can be
7513
+ * omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`.
7514
+ * See the {@link $compileProvider#component} helper for an example of how this can be used.
7515
+ * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is
7516
+ * raised (unless no link function is specified and the required controllers are not being bound to the directive
7517
+ * controller, in which case error checking is skipped). The name can be prefixed with:
7518
+ *
7519
+ * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
7520
+ * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
7521
+ * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
7522
+ * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
7523
+ * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
7524
+ * `null` to the `link` fn if not found.
7525
+ * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
7526
+ * `null` to the `link` fn if not found.
7527
+ *
7528
+ *
7529
+ * #### `controllerAs`
7530
+ * Identifier name for a reference to the controller in the directive's scope.
7531
+ * This allows the controller to be referenced from the directive template. This is especially
7532
+ * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
7533
+ * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
7534
+ * `controllerAs` reference might overwrite a property that already exists on the parent scope.
7535
+ *
7536
+ *
7537
+ * #### `restrict`
7538
+ * String of subset of `EACM` which restricts the directive to a specific directive
7539
+ * declaration style. If omitted, the defaults (elements and attributes) are used.
7540
+ *
7541
+ * * `E` - Element name (default): `<my-directive></my-directive>`
7542
+ * * `A` - Attribute (default): `<div my-directive="exp"></div>`
7543
+ * * `C` - Class: `<div class="my-directive: exp;"></div>`
7544
+ * * `M` - Comment: `<!-- directive: my-directive exp -->`
7545
+ *
7546
+ *
7547
+ * #### `templateNamespace`
7548
+ * String representing the document type used by the markup in the template.
7549
+ * AngularJS needs this information as those elements need to be created and cloned
7550
+ * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
7551
+ *
7552
+ * * `html` - All root nodes in the template are HTML. Root nodes may also be
7553
+ * top-level elements such as `<svg>` or `<math>`.
7554
+ * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
7555
+ * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
7556
+ *
7557
+ * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
7558
+ *
7559
+ * #### `template`
7560
+ * HTML markup that may:
7561
+ * * Replace the contents of the directive's element (default).
7562
+ * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
7563
+ * * Wrap the contents of the directive's element (if `transclude` is true).
7564
+ *
7565
+ * Value may be:
7566
+ *
7567
+ * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
7568
+ * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
7569
+ * function api below) and returns a string value.
7570
+ *
7571
+ *
7572
+ * #### `templateUrl`
7573
+ * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
7574
+ *
7575
+ * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
7576
+ * for later when the template has been resolved. In the meantime it will continue to compile and link
7577
+ * sibling and parent elements as though this element had not contained any directives.
7578
+ *
7579
+ * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
7580
+ * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
7581
+ * case when only one deeply nested directive has `templateUrl`.
7582
+ *
7583
+ * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}
7584
+ *
7585
+ * You can specify `templateUrl` as a string representing the URL or as a function which takes two
7586
+ * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
7587
+ * a string value representing the url. In either case, the template URL is passed through {@link
7588
+ * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
7589
+ *
7590
+ *
7591
+ * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
7592
+ * specify what the template should replace. Defaults to `false`.
7593
+ *
7594
+ * * `true` - the template will replace the directive's element.
7595
+ * * `false` - the template will replace the contents of the directive's element.
7596
+ *
7597
+ * The replacement process migrates all of the attributes / classes from the old element to the new
7598
+ * one. See the {@link guide/directive#template-expanding-directive
7599
+ * Directives Guide} for an example.
7600
+ *
7601
+ * There are very few scenarios where element replacement is required for the application function,
7602
+ * the main one being reusable custom components that are used within SVG contexts
7603
+ * (because SVG doesn't work with custom elements in the DOM tree).
7604
+ *
7605
+ * #### `transclude`
7606
+ * Extract the contents of the element where the directive appears and make it available to the directive.
7607
+ * The contents are compiled and provided to the directive as a **transclusion function**. See the
7608
+ * {@link $compile#transclusion Transclusion} section below.
7609
+ *
7610
+ *
7611
+ * #### `compile`
7612
+ *
7613
+ * ```js
7614
+ * function compile(tElement, tAttrs, transclude) { ... }
7615
+ * ```
7616
+ *
7617
+ * The compile function deals with transforming the template DOM. Since most directives do not do
7618
+ * template transformation, it is not used often. The compile function takes the following arguments:
7619
+ *
7620
+ * * `tElement` - template element - The element where the directive has been declared. It is
7621
+ * safe to do template transformation on the element and child elements only.
7622
+ *
7623
+ * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
7624
+ * between all directive compile functions.
7625
+ *
7626
+ * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
7627
+ *
7628
+ * <div class="alert alert-warning">
7629
+ * **Note:** The template instance and the link instance may be different objects if the template has
7630
+ * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
7631
+ * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
7632
+ * should be done in a linking function rather than in a compile function.
7633
+ * </div>
7634
+
7635
+ * <div class="alert alert-warning">
7636
+ * **Note:** The compile function cannot handle directives that recursively use themselves in their
7637
+ * own templates or compile functions. Compiling these directives results in an infinite loop and
7638
+ * stack overflow errors.
7639
+ *
7640
+ * This can be avoided by manually using $compile in the postLink function to imperatively compile
7641
+ * a directive's template instead of relying on automatic template compilation via `template` or
7642
+ * `templateUrl` declaration or manual compilation inside the compile function.
7643
+ * </div>
7644
+ *
7645
+ * <div class="alert alert-danger">
7646
+ * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
7647
+ * e.g. does not know about the right outer scope. Please use the transclude function that is passed
7648
+ * to the link function instead.
7649
+ * </div>
7650
+
7651
+ * A compile function can have a return value which can be either a function or an object.
7652
+ *
7653
+ * * returning a (post-link) function - is equivalent to registering the linking function via the
7654
+ * `link` property of the config object when the compile function is empty.
7655
+ *
7656
+ * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
7657
+ * control when a linking function should be called during the linking phase. See info about
7658
+ * pre-linking and post-linking functions below.
7659
+ *
7660
+ *
7661
+ * #### `link`
7662
+ * This property is used only if the `compile` property is not defined.
7663
+ *
7664
+ * ```js
7665
+ * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
7666
+ * ```
7667
+ *
7668
+ * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
7669
+ * executed after the template has been cloned. This is where most of the directive logic will be
7670
+ * put.
7671
+ *
7672
+ * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
7673
+ * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
7674
+ *
7675
+ * * `iElement` - instance element - The element where the directive is to be used. It is safe to
7676
+ * manipulate the children of the element only in `postLink` function since the children have
7677
+ * already been linked.
7678
+ *
7679
+ * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
7680
+ * between all directive linking functions.
7681
+ *
7682
+ * * `controller` - the directive's required controller instance(s) - Instances are shared
7683
+ * among all directives, which allows the directives to use the controllers as a communication
7684
+ * channel. The exact value depends on the directive's `require` property:
7685
+ * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
7686
+ * * `string`: the controller instance
7687
+ * * `array`: array of controller instances
7688
+ *
7689
+ * If a required controller cannot be found, and it is optional, the instance is `null`,
7690
+ * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
7691
+ *
7692
+ * Note that you can also require the directive's own controller - it will be made available like
7693
+ * any other controller.
7694
+ *
7695
+ * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
7696
+ * This is the same as the `$transclude` parameter of directive controllers,
7697
+ * see {@link ng.$compile#-controller- the controller section for details}.
7698
+ * `function([scope], cloneLinkingFn, futureParentElement)`.
7699
+ *
7700
+ * #### Pre-linking function
7701
+ *
7702
+ * Executed before the child elements are linked. Not safe to do DOM transformation since the
7703
+ * compiler linking function will fail to locate the correct elements for linking.
7704
+ *
7705
+ * #### Post-linking function
7706
+ *
7707
+ * Executed after the child elements are linked.
7708
+ *
7709
+ * Note that child elements that contain `templateUrl` directives will not have been compiled
7710
+ * and linked since they are waiting for their template to load asynchronously and their own
7711
+ * compilation and linking has been suspended until that occurs.
7712
+ *
7713
+ * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
7714
+ * for their async templates to be resolved.
7715
+ *
7716
+ *
7717
+ * ### Transclusion
7718
+ *
7719
+ * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
7720
+ * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
7721
+ * scope from where they were taken.
7722
+ *
7723
+ * Transclusion is used (often with {@link ngTransclude}) to insert the
7724
+ * original contents of a directive's element into a specified place in the template of the directive.
7725
+ * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
7726
+ * content has access to the properties on the scope from which it was taken, even if the directive
7727
+ * has isolated scope.
7728
+ * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
7729
+ *
7730
+ * This makes it possible for the widget to have private state for its template, while the transcluded
7731
+ * content has access to its originating scope.
7732
+ *
7733
+ * <div class="alert alert-warning">
7734
+ * **Note:** When testing an element transclude directive you must not place the directive at the root of the
7735
+ * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
7736
+ * Testing Transclusion Directives}.
7737
+ * </div>
7738
+ *
7739
+ * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
7740
+ * directive's element, the entire element or multiple parts of the element contents:
7741
+ *
7742
+ * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
7743
+ * * `'element'` - transclude the whole of the directive's element including any directives on this
7744
+ * element that defined at a lower priority than this directive. When used, the `template`
7745
+ * property is ignored.
7746
+ * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
7747
+ *
7748
+ * **Mult-slot transclusion** is declared by providing an object for the `transclude` property.
7749
+ *
7750
+ * This object is a map where the keys are the name of the slot to fill and the value is an element selector
7751
+ * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`)
7752
+ * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc).
7753
+ *
7754
+ * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
7755
+ *
7756
+ * If the element selector is prefixed with a `?` then that slot is optional.
7757
+ *
7758
+ * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to
7759
+ * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive.
7760
+ *
7761
+ * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements
7762
+ * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call
7763
+ * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and
7764
+ * injectable into the directive's controller.
7765
+ *
7766
+ *
7767
+ * #### Transclusion Functions
7768
+ *
7769
+ * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
7770
+ * function** to the directive's `link` function and `controller`. This transclusion function is a special
7771
+ * **linking function** that will return the compiled contents linked to a new transclusion scope.
7772
+ *
7773
+ * <div class="alert alert-info">
7774
+ * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
7775
+ * ngTransclude will deal with it for us.
7776
+ * </div>
7777
+ *
7778
+ * If you want to manually control the insertion and removal of the transcluded content in your directive
7779
+ * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
7780
+ * object that contains the compiled DOM, which is linked to the correct transclusion scope.
7781
+ *
7782
+ * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
7783
+ * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
7784
+ * content and the `scope` is the newly created transclusion scope, which the clone will be linked to.
7785
+ *
7786
+ * <div class="alert alert-info">
7787
+ * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function
7788
+ * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
7789
+ * </div>
7790
+ *
7791
+ * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
7792
+ * attach function**:
7793
+ *
7794
+ * ```js
7795
+ * var transcludedContent, transclusionScope;
7796
+ *
7797
+ * $transclude(function(clone, scope) {
7798
+ * element.append(clone);
7799
+ * transcludedContent = clone;
7800
+ * transclusionScope = scope;
7801
+ * });
7802
+ * ```
7803
+ *
7804
+ * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
7805
+ * associated transclusion scope:
7806
+ *
7807
+ * ```js
7808
+ * transcludedContent.remove();
7809
+ * transclusionScope.$destroy();
7810
+ * ```
7811
+ *
7812
+ * <div class="alert alert-info">
7813
+ * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
7814
+ * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
7815
+ * then you are also responsible for calling `$destroy` on the transclusion scope.
7816
+ * </div>
7817
+ *
7818
+ * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
7819
+ * automatically destroy their transcluded clones as necessary so you do not need to worry about this if
7820
+ * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
7821
+ *
7822
+ *
7823
+ * #### Transclusion Scopes
7824
+ *
7825
+ * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
7826
+ * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
7827
+ * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
7828
+ * was taken.
7829
+ *
7830
+ * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
7831
+ * like this:
7832
+ *
7833
+ * ```html
7834
+ * <div ng-app>
7835
+ * <div isolate>
7836
+ * <div transclusion>
7837
+ * </div>
7838
+ * </div>
7839
+ * </div>
7840
+ * ```
7841
+ *
7842
+ * The `$parent` scope hierarchy will look like this:
7843
+ *
7844
+ ```
7845
+ - $rootScope
7846
+ - isolate
7847
+ - transclusion
7848
+ ```
7849
+ *
7850
+ * but the scopes will inherit prototypically from different scopes to their `$parent`.
7851
+ *
7852
+ ```
7853
+ - $rootScope
7854
+ - transclusion
7855
+ - isolate
7856
+ ```
7857
+ *
7858
+ *
7859
+ * ### Attributes
7860
+ *
7861
+ * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
7862
+ * `link()` or `compile()` functions. It has a variety of uses.
7863
+ *
7864
+ * * *Accessing normalized attribute names:* Directives like 'ngBind' can be expressed in many ways:
7865
+ * 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. The attributes object allows for normalized access
7866
+ * to the attributes.
7867
+ *
7868
+ * * *Directive inter-communication:* All directives share the same instance of the attributes
7869
+ * object which allows the directives to use the attributes object as inter directive
7870
+ * communication.
7871
+ *
7872
+ * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
7873
+ * allowing other directives to read the interpolated value.
7874
+ *
7875
+ * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
7876
+ * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
7877
+ * the only way to easily get the actual value because during the linking phase the interpolation
7878
+ * hasn't been evaluated yet and so the value is at this time set to `undefined`.
7879
+ *
7880
+ * ```js
7881
+ * function linkingFn(scope, elm, attrs, ctrl) {
7882
+ * // get the attribute value
7883
+ * console.log(attrs.ngModel);
7884
+ *
7885
+ * // change the attribute
7886
+ * attrs.$set('ngModel', 'new value');
7887
+ *
7888
+ * // observe changes to interpolated attribute
7889
+ * attrs.$observe('ngModel', function(value) {
7890
+ * console.log('ngModel has changed value to ' + value);
7891
+ * });
7892
+ * }
7893
+ * ```
7894
+ *
7895
+ * ## Example
7896
+ *
7897
+ * <div class="alert alert-warning">
7898
+ * **Note**: Typically directives are registered with `module.directive`. The example below is
7899
+ * to illustrate how `$compile` works.
7900
+ * </div>
7901
+ *
7902
+ <example module="compileExample" name="compile">
7903
+ <file name="index.html">
7904
+ <script>
7905
+ angular.module('compileExample', [], function($compileProvider) {
7906
+ // configure new 'compile' directive by passing a directive
7907
+ // factory function. The factory function injects the '$compile'
7908
+ $compileProvider.directive('compile', function($compile) {
7909
+ // directive factory creates a link function
7910
+ return function(scope, element, attrs) {
7911
+ scope.$watch(
7912
+ function(scope) {
7913
+ // watch the 'compile' expression for changes
7914
+ return scope.$eval(attrs.compile);
7915
+ },
7916
+ function(value) {
7917
+ // when the 'compile' expression changes
7918
+ // assign it into the current DOM
7919
+ element.html(value);
7920
+
7921
+ // compile the new DOM and link it to the current
7922
+ // scope.
7923
+ // NOTE: we only compile .childNodes so that
7924
+ // we don't get into infinite loop compiling ourselves
7925
+ $compile(element.contents())(scope);
7926
+ }
7927
+ );
7928
+ };
7929
+ });
7930
+ })
7931
+ .controller('GreeterController', ['$scope', function($scope) {
7932
+ $scope.name = 'Angular';
7933
+ $scope.html = 'Hello {{name}}';
7934
+ }]);
7935
+ </script>
7936
+ <div ng-controller="GreeterController">
7937
+ <input ng-model="name"> <br/>
7938
+ <textarea ng-model="html"></textarea> <br/>
7939
+ <div compile="html"></div>
7940
+ </div>
7941
+ </file>
7942
+ <file name="protractor.js" type="protractor">
7943
+ it('should auto compile', function() {
7944
+ var textarea = $('textarea');
7945
+ var output = $('div[compile]');
7946
+ // The initial state reads 'Hello Angular'.
7947
+ expect(output.getText()).toBe('Hello Angular');
7948
+ textarea.clear();
7949
+ textarea.sendKeys('{{name}}!');
7950
+ expect(output.getText()).toBe('Angular!');
7951
+ });
7952
+ </file>
7953
+ </example>
7954
+
7955
+ *
7956
+ *
7957
+ * @param {string|DOMElement} element Element or HTML string to compile into a template function.
7958
+ * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
7959
+ *
7960
+ * <div class="alert alert-danger">
7961
+ * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
7962
+ * e.g. will not use the right outer scope. Please pass the transclude function as a
7963
+ * `parentBoundTranscludeFn` to the link function instead.
7964
+ * </div>
7965
+ *
7966
+ * @param {number} maxPriority only apply directives lower than given priority (Only effects the
7967
+ * root element(s), not their children)
7968
+ * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
7969
+ * (a DOM element/tree) to a scope. Where:
7970
+ *
7971
+ * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
7972
+ * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
7973
+ * `template` and call the `cloneAttachFn` function allowing the caller to attach the
7974
+ * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
7975
+ * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
7976
+ *
7977
+ * * `clonedElement` - is a clone of the original `element` passed into the compiler.
7978
+ * * `scope` - is the current scope with which the linking function is working with.
7979
+ *
7980
+ * * `options` - An optional object hash with linking options. If `options` is provided, then the following
7981
+ * keys may be used to control linking behavior:
7982
+ *
7983
+ * * `parentBoundTranscludeFn` - the transclude function made available to
7984
+ * directives; if given, it will be passed through to the link functions of
7985
+ * directives found in `element` during compilation.
7986
+ * * `transcludeControllers` - an object hash with keys that map controller names
7987
+ * to a hash with the key `instance`, which maps to the controller instance;
7988
+ * if given, it will make the controllers available to directives on the compileNode:
7989
+ * ```
7990
+ * {
7991
+ * parent: {
7992
+ * instance: parentControllerInstance
7993
+ * }
7994
+ * }
7995
+ * ```
7996
+ * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
7997
+ * the cloned elements; only needed for transcludes that are allowed to contain non html
7998
+ * elements (e.g. SVG elements). See also the directive.controller property.
7999
+ *
8000
+ * Calling the linking function returns the element of the template. It is either the original
8001
+ * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
8002
+ *
8003
+ * After linking the view is not updated until after a call to $digest which typically is done by
8004
+ * Angular automatically.
8005
+ *
8006
+ * If you need access to the bound view, there are two ways to do it:
8007
+ *
8008
+ * - If you are not asking the linking function to clone the template, create the DOM element(s)
8009
+ * before you send them to the compiler and keep this reference around.
8010
+ * ```js
8011
+ * var element = $compile('<p>{{total}}</p>')(scope);
8012
+ * ```
8013
+ *
8014
+ * - if on the other hand, you need the element to be cloned, the view reference from the original
8015
+ * example would not point to the clone, but rather to the original template that was cloned. In
8016
+ * this case, you can access the clone via the cloneAttachFn:
8017
+ * ```js
8018
+ * var templateElement = angular.element('<p>{{total}}</p>'),
8019
+ * scope = ....;
8020
+ *
8021
+ * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
8022
+ * //attach the clone to DOM document at the right place
8023
+ * });
8024
+ *
8025
+ * //now we have reference to the cloned DOM via `clonedElement`
8026
+ * ```
8027
+ *
8028
+ *
8029
+ * For information on how the compiler works, see the
8030
+ * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
8031
+ *
8032
+ * @knownIssue
8033
+ *
8034
+ * ### Double Compilation
8035
+ *
8036
+ Double compilation occurs when an already compiled part of the DOM gets
8037
+ compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues,
8038
+ and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it
8039
+ section on double compilation} for an in-depth explanation and ways to avoid it.
8040
+ *
8041
+ */
8042
+
8043
+ var $compileMinErr = minErr('$compile');
8044
+
8045
+ function UNINITIALIZED_VALUE() {}
8046
+ var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE();
8047
+
8048
+ /**
8049
+ * @ngdoc provider
8050
+ * @name $compileProvider
8051
+ *
8052
+ * @description
8053
+ */
8054
+ $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
8055
+ /** @this */
8056
+ function $CompileProvider($provide, $$sanitizeUriProvider) {
8057
+ var hasDirectives = {},
8058
+ Suffix = 'Directive',
8059
+ COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/,
8060
+ CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/,
8061
+ ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
8062
+ REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
8063
+
8064
+ // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
8065
+ // The assumption is that future DOM event attribute names will begin with
8066
+ // 'on' and be composed of only English letters.
8067
+ var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
8068
+ var bindingCache = createMap();
8069
+
8070
+ function parseIsolateBindings(scope, directiveName, isController) {
8071
+ var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*([\w$]*)\s*$/;
8072
+
8073
+ var bindings = createMap();
8074
+
8075
+ forEach(scope, function(definition, scopeName) {
8076
+ if (definition in bindingCache) {
8077
+ bindings[scopeName] = bindingCache[definition];
8078
+ return;
8079
+ }
8080
+ var match = definition.match(LOCAL_REGEXP);
8081
+
8082
+ if (!match) {
8083
+ throw $compileMinErr('iscp',
8084
+ 'Invalid {3} for directive \'{0}\'.' +
8085
+ ' Definition: {... {1}: \'{2}\' ...}',
8086
+ directiveName, scopeName, definition,
8087
+ (isController ? 'controller bindings definition' :
8088
+ 'isolate scope definition'));
8089
+ }
8090
+
8091
+ bindings[scopeName] = {
8092
+ mode: match[1][0],
8093
+ collection: match[2] === '*',
8094
+ optional: match[3] === '?',
8095
+ attrName: match[4] || scopeName
8096
+ };
8097
+ if (match[4]) {
8098
+ bindingCache[definition] = bindings[scopeName];
8099
+ }
8100
+ });
8101
+
8102
+ return bindings;
8103
+ }
8104
+
8105
+ function parseDirectiveBindings(directive, directiveName) {
8106
+ var bindings = {
8107
+ isolateScope: null,
8108
+ bindToController: null
8109
+ };
8110
+ if (isObject(directive.scope)) {
8111
+ if (directive.bindToController === true) {
8112
+ bindings.bindToController = parseIsolateBindings(directive.scope,
8113
+ directiveName, true);
8114
+ bindings.isolateScope = {};
8115
+ } else {
8116
+ bindings.isolateScope = parseIsolateBindings(directive.scope,
8117
+ directiveName, false);
8118
+ }
8119
+ }
8120
+ if (isObject(directive.bindToController)) {
8121
+ bindings.bindToController =
8122
+ parseIsolateBindings(directive.bindToController, directiveName, true);
8123
+ }
8124
+ if (bindings.bindToController && !directive.controller) {
8125
+ // There is no controller
8126
+ throw $compileMinErr('noctrl',
8127
+ 'Cannot bind to controller without directive \'{0}\'s controller.',
8128
+ directiveName);
8129
+ }
8130
+ return bindings;
8131
+ }
8132
+
8133
+ function assertValidDirectiveName(name) {
8134
+ var letter = name.charAt(0);
8135
+ if (!letter || letter !== lowercase(letter)) {
8136
+ throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The first character must be a lowercase letter', name);
8137
+ }
8138
+ if (name !== name.trim()) {
8139
+ throw $compileMinErr('baddir',
8140
+ 'Directive/Component name \'{0}\' is invalid. The name should not contain leading or trailing whitespaces',
8141
+ name);
8142
+ }
8143
+ }
8144
+
8145
+ function getDirectiveRequire(directive) {
8146
+ var require = directive.require || (directive.controller && directive.name);
8147
+
8148
+ if (!isArray(require) && isObject(require)) {
8149
+ forEach(require, function(value, key) {
8150
+ var match = value.match(REQUIRE_PREFIX_REGEXP);
8151
+ var name = value.substring(match[0].length);
8152
+ if (!name) require[key] = match[0] + key;
8153
+ });
8154
+ }
8155
+
8156
+ return require;
8157
+ }
8158
+
8159
+ function getDirectiveRestrict(restrict, name) {
8160
+ if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) {
8161
+ throw $compileMinErr('badrestrict',
8162
+ 'Restrict property \'{0}\' of directive \'{1}\' is invalid',
8163
+ restrict,
8164
+ name);
8165
+ }
8166
+
8167
+ return restrict || 'EA';
8168
+ }
8169
+
8170
+ /**
8171
+ * @ngdoc method
8172
+ * @name $compileProvider#directive
8173
+ * @kind function
8174
+ *
8175
+ * @description
8176
+ * Register a new directive with the compiler.
8177
+ *
8178
+ * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
8179
+ * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
8180
+ * names and the values are the factories.
8181
+ * @param {Function|Array} directiveFactory An injectable directive factory function. See the
8182
+ * {@link guide/directive directive guide} and the {@link $compile compile API} for more info.
8183
+ * @returns {ng.$compileProvider} Self for chaining.
8184
+ */
8185
+ this.directive = function registerDirective(name, directiveFactory) {
8186
+ assertArg(name, 'name');
8187
+ assertNotHasOwnProperty(name, 'directive');
8188
+ if (isString(name)) {
8189
+ assertValidDirectiveName(name);
8190
+ assertArg(directiveFactory, 'directiveFactory');
8191
+ if (!hasDirectives.hasOwnProperty(name)) {
8192
+ hasDirectives[name] = [];
8193
+ $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
8194
+ function($injector, $exceptionHandler) {
8195
+ var directives = [];
8196
+ forEach(hasDirectives[name], function(directiveFactory, index) {
8197
+ try {
8198
+ var directive = $injector.invoke(directiveFactory);
8199
+ if (isFunction(directive)) {
8200
+ directive = { compile: valueFn(directive) };
8201
+ } else if (!directive.compile && directive.link) {
8202
+ directive.compile = valueFn(directive.link);
8203
+ }
8204
+ directive.priority = directive.priority || 0;
8205
+ directive.index = index;
8206
+ directive.name = directive.name || name;
8207
+ directive.require = getDirectiveRequire(directive);
8208
+ directive.restrict = getDirectiveRestrict(directive.restrict, name);
8209
+ directive.$$moduleName = directiveFactory.$$moduleName;
8210
+ directives.push(directive);
8211
+ } catch (e) {
8212
+ $exceptionHandler(e);
8213
+ }
8214
+ });
8215
+ return directives;
8216
+ }]);
8217
+ }
8218
+ hasDirectives[name].push(directiveFactory);
8219
+ } else {
8220
+ forEach(name, reverseParams(registerDirective));
8221
+ }
8222
+ return this;
8223
+ };
8224
+
8225
+ /**
8226
+ * @ngdoc method
8227
+ * @name $compileProvider#component
8228
+ * @module ng
8229
+ * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`),
8230
+ * or an object map of components where the keys are the names and the values are the component definition objects.
8231
+ * @param {Object} options Component definition object (a simplified
8232
+ * {@link ng.$compile#directive-definition-object directive definition object}),
8233
+ * with the following properties (all optional):
8234
+ *
8235
+ * - `controller` – `{(string|function()=}` – controller constructor function that should be
8236
+ * associated with newly created scope or the name of a {@link ng.$compile#-controller-
8237
+ * registered controller} if passed as a string. An empty `noop` function by default.
8238
+ * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope.
8239
+ * If present, the controller will be published to scope under the `controllerAs` name.
8240
+ * If not present, this will default to be `$ctrl`.
8241
+ * - `template` – `{string=|function()=}` – html template as a string or a function that
8242
+ * returns an html template as a string which should be used as the contents of this component.
8243
+ * Empty string by default.
8244
+ *
8245
+ * If `template` is a function, then it is {@link auto.$injector#invoke injected} with
8246
+ * the following locals:
8247
+ *
8248
+ * - `$element` - Current element
8249
+ * - `$attrs` - Current attributes object for the element
8250
+ *
8251
+ * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
8252
+ * template that should be used as the contents of this component.
8253
+ *
8254
+ * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with
8255
+ * the following locals:
8256
+ *
8257
+ * - `$element` - Current element
8258
+ * - `$attrs` - Current attributes object for the element
8259
+ *
8260
+ * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties.
8261
+ * Component properties are always bound to the component controller and not to the scope.
8262
+ * See {@link ng.$compile#-bindtocontroller- `bindToController`}.
8263
+ * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled.
8264
+ * Disabled by default.
8265
+ * - `require` - `{Object<string, string>=}` - requires the controllers of other directives and binds them to
8266
+ * this component's controller. The object keys specify the property names under which the required
8267
+ * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}.
8268
+ * - `$...` – additional properties to attach to the directive factory function and the controller
8269
+ * constructor function. (This is used by the component router to annotate)
8270
+ *
8271
+ * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls.
8272
+ * @description
8273
+ * Register a **component definition** with the compiler. This is a shorthand for registering a special
8274
+ * type of directive, which represents a self-contained UI component in your application. Such components
8275
+ * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`).
8276
+ *
8277
+ * Component definitions are very simple and do not require as much configuration as defining general
8278
+ * directives. Component definitions usually consist only of a template and a controller backing it.
8279
+ *
8280
+ * In order to make the definition easier, components enforce best practices like use of `controllerAs`,
8281
+ * `bindToController`. They always have **isolate scope** and are restricted to elements.
8282
+ *
8283
+ * Here are a few examples of how you would usually define components:
8284
+ *
8285
+ * ```js
8286
+ * var myMod = angular.module(...);
8287
+ * myMod.component('myComp', {
8288
+ * template: '<div>My name is {{$ctrl.name}}</div>',
8289
+ * controller: function() {
8290
+ * this.name = 'shahar';
8291
+ * }
8292
+ * });
8293
+ *
8294
+ * myMod.component('myComp', {
8295
+ * template: '<div>My name is {{$ctrl.name}}</div>',
8296
+ * bindings: {name: '@'}
8297
+ * });
8298
+ *
8299
+ * myMod.component('myComp', {
8300
+ * templateUrl: 'views/my-comp.html',
8301
+ * controller: 'MyCtrl',
8302
+ * controllerAs: 'ctrl',
8303
+ * bindings: {name: '@'}
8304
+ * });
8305
+ *
8306
+ * ```
8307
+ * For more examples, and an in-depth guide, see the {@link guide/component component guide}.
8308
+ *
8309
+ * <br />
8310
+ * See also {@link ng.$compileProvider#directive $compileProvider.directive()}.
8311
+ */
8312
+ this.component = function registerComponent(name, options) {
8313
+ if (!isString(name)) {
8314
+ forEach(name, reverseParams(bind(this, registerComponent)));
8315
+ return this;
8316
+ }
8317
+
8318
+ var controller = options.controller || function() {};
8319
+
8320
+ function factory($injector) {
8321
+ function makeInjectable(fn) {
8322
+ if (isFunction(fn) || isArray(fn)) {
8323
+ return /** @this */ function(tElement, tAttrs) {
8324
+ return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs});
8325
+ };
8326
+ } else {
8327
+ return fn;
8328
+ }
8329
+ }
8330
+
8331
+ var template = (!options.template && !options.templateUrl ? '' : options.template);
8332
+ var ddo = {
8333
+ controller: controller,
8334
+ controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
8335
+ template: makeInjectable(template),
8336
+ templateUrl: makeInjectable(options.templateUrl),
8337
+ transclude: options.transclude,
8338
+ scope: {},
8339
+ bindToController: options.bindings || {},
8340
+ restrict: 'E',
8341
+ require: options.require
8342
+ };
8343
+
8344
+ // Copy annotations (starting with $) over to the DDO
8345
+ forEach(options, function(val, key) {
8346
+ if (key.charAt(0) === '$') ddo[key] = val;
8347
+ });
8348
+
8349
+ return ddo;
8350
+ }
8351
+
8352
+ // TODO(pete) remove the following `forEach` before we release 1.6.0
8353
+ // The component-router@0.2.0 looks for the annotations on the controller constructor
8354
+ // Nothing in Angular looks for annotations on the factory function but we can't remove
8355
+ // it from 1.5.x yet.
8356
+
8357
+ // Copy any annotation properties (starting with $) over to the factory and controller constructor functions
8358
+ // These could be used by libraries such as the new component router
8359
+ forEach(options, function(val, key) {
8360
+ if (key.charAt(0) === '$') {
8361
+ factory[key] = val;
8362
+ // Don't try to copy over annotations to named controller
8363
+ if (isFunction(controller)) controller[key] = val;
8364
+ }
8365
+ });
8366
+
8367
+ factory.$inject = ['$injector'];
8368
+
8369
+ return this.directive(name, factory);
8370
+ };
8371
+
8372
+
8373
+ /**
8374
+ * @ngdoc method
8375
+ * @name $compileProvider#aHrefSanitizationWhitelist
8376
+ * @kind function
8377
+ *
8378
+ * @description
8379
+ * Retrieves or overrides the default regular expression that is used for whitelisting of safe
8380
+ * urls during a[href] sanitization.
8381
+ *
8382
+ * The sanitization is a security measure aimed at preventing XSS attacks via html links.
8383
+ *
8384
+ * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
8385
+ * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
8386
+ * regular expression. If a match is found, the original url is written into the dom. Otherwise,
8387
+ * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
8388
+ *
8389
+ * @param {RegExp=} regexp New regexp to whitelist urls with.
8390
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
8391
+ * chaining otherwise.
8392
+ */
8393
+ this.aHrefSanitizationWhitelist = function(regexp) {
8394
+ if (isDefined(regexp)) {
8395
+ $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
8396
+ return this;
8397
+ } else {
8398
+ return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
8399
+ }
8400
+ };
8401
+
8402
+
8403
+ /**
8404
+ * @ngdoc method
8405
+ * @name $compileProvider#imgSrcSanitizationWhitelist
8406
+ * @kind function
8407
+ *
8408
+ * @description
8409
+ * Retrieves or overrides the default regular expression that is used for whitelisting of safe
8410
+ * urls during img[src] sanitization.
8411
+ *
8412
+ * The sanitization is a security measure aimed at prevent XSS attacks via html links.
8413
+ *
8414
+ * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
8415
+ * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
8416
+ * regular expression. If a match is found, the original url is written into the dom. Otherwise,
8417
+ * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
8418
+ *
8419
+ * @param {RegExp=} regexp New regexp to whitelist urls with.
8420
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
8421
+ * chaining otherwise.
8422
+ */
8423
+ this.imgSrcSanitizationWhitelist = function(regexp) {
8424
+ if (isDefined(regexp)) {
8425
+ $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
8426
+ return this;
8427
+ } else {
8428
+ return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
8429
+ }
8430
+ };
8431
+
8432
+ /**
8433
+ * @ngdoc method
8434
+ * @name $compileProvider#debugInfoEnabled
8435
+ *
8436
+ * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
8437
+ * current debugInfoEnabled state
8438
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
8439
+ *
8440
+ * @kind function
8441
+ *
8442
+ * @description
8443
+ * Call this method to enable/disable various debug runtime information in the compiler such as adding
8444
+ * binding information and a reference to the current scope on to DOM elements.
8445
+ * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
8446
+ * * `ng-binding` CSS class
8447
+ * * `$binding` data property containing an array of the binding expressions
8448
+ *
8449
+ * You may want to disable this in production for a significant performance boost. See
8450
+ * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
8451
+ *
8452
+ * The default value is true.
8453
+ */
8454
+ var debugInfoEnabled = true;
8455
+ this.debugInfoEnabled = function(enabled) {
8456
+ if (isDefined(enabled)) {
8457
+ debugInfoEnabled = enabled;
8458
+ return this;
8459
+ }
8460
+ return debugInfoEnabled;
8461
+ };
8462
+
8463
+ /**
8464
+ * @ngdoc method
8465
+ * @name $compileProvider#preAssignBindingsEnabled
8466
+ *
8467
+ * @param {boolean=} enabled update the preAssignBindingsEnabled state if provided, otherwise just return the
8468
+ * current preAssignBindingsEnabled state
8469
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
8470
+ *
8471
+ * @kind function
8472
+ *
8473
+ * @description
8474
+ * Call this method to enable/disable whether directive controllers are assigned bindings before
8475
+ * calling the controller's constructor.
8476
+ * If enabled (true), the compiler assigns the value of each of the bindings to the
8477
+ * properties of the controller object before the constructor of this object is called.
8478
+ *
8479
+ * If disabled (false), the compiler calls the constructor first before assigning bindings.
8480
+ *
8481
+ * The default value is false.
8482
+ *
8483
+ * @deprecated
8484
+ * sinceVersion="1.6.0"
8485
+ * removeVersion="1.7.0"
8486
+ *
8487
+ * This method and the option to assign the bindings before calling the controller's constructor
8488
+ * will be removed in v1.7.0.
8489
+ */
8490
+ var preAssignBindingsEnabled = false;
8491
+ this.preAssignBindingsEnabled = function(enabled) {
8492
+ if (isDefined(enabled)) {
8493
+ preAssignBindingsEnabled = enabled;
8494
+ return this;
8495
+ }
8496
+ return preAssignBindingsEnabled;
8497
+ };
8498
+
8499
+ /**
8500
+ * @ngdoc method
8501
+ * @name $compileProvider#strictComponentBindingsEnabled
8502
+ *
8503
+ * @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided, otherwise just return the
8504
+ * current strictComponentBindingsEnabled state
8505
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
8506
+ *
8507
+ * @kind function
8508
+ *
8509
+ * @description
8510
+ * Call this method to enable/disable strict component bindings check. If enabled, the compiler will enforce that
8511
+ * for all bindings of a component that are not set as optional with `?`, an attribute needs to be provided
8512
+ * on the component's HTML tag.
8513
+ *
8514
+ * The default value is false.
8515
+ */
8516
+ var strictComponentBindingsEnabled = false;
8517
+ this.strictComponentBindingsEnabled = function(enabled) {
8518
+ if (isDefined(enabled)) {
8519
+ strictComponentBindingsEnabled = enabled;
8520
+ return this;
8521
+ }
8522
+ return strictComponentBindingsEnabled;
8523
+ };
8524
+
8525
+ var TTL = 10;
8526
+ /**
8527
+ * @ngdoc method
8528
+ * @name $compileProvider#onChangesTtl
8529
+ * @description
8530
+ *
8531
+ * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and
8532
+ * assuming that the model is unstable.
8533
+ *
8534
+ * The current default is 10 iterations.
8535
+ *
8536
+ * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result
8537
+ * in several iterations of calls to these hooks. However if an application needs more than the default 10
8538
+ * iterations to stabilize then you should investigate what is causing the model to continuously change during
8539
+ * the `$onChanges` hook execution.
8540
+ *
8541
+ * Increasing the TTL could have performance implications, so you should not change it without proper justification.
8542
+ *
8543
+ * @param {number} limit The number of `$onChanges` hook iterations.
8544
+ * @returns {number|object} the current limit (or `this` if called as a setter for chaining)
8545
+ */
8546
+ this.onChangesTtl = function(value) {
8547
+ if (arguments.length) {
8548
+ TTL = value;
8549
+ return this;
8550
+ }
8551
+ return TTL;
8552
+ };
8553
+
8554
+ var commentDirectivesEnabledConfig = true;
8555
+ /**
8556
+ * @ngdoc method
8557
+ * @name $compileProvider#commentDirectivesEnabled
8558
+ * @description
8559
+ *
8560
+ * It indicates to the compiler
8561
+ * whether or not directives on comments should be compiled.
8562
+ * Defaults to `true`.
8563
+ *
8564
+ * Calling this function with false disables the compilation of directives
8565
+ * on comments for the whole application.
8566
+ * This results in a compilation performance gain,
8567
+ * as the compiler doesn't have to check comments when looking for directives.
8568
+ * This should however only be used if you are sure that no comment directives are used in
8569
+ * the application (including any 3rd party directives).
8570
+ *
8571
+ * @param {boolean} enabled `false` if the compiler may ignore directives on comments
8572
+ * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
8573
+ */
8574
+ this.commentDirectivesEnabled = function(value) {
8575
+ if (arguments.length) {
8576
+ commentDirectivesEnabledConfig = value;
8577
+ return this;
8578
+ }
8579
+ return commentDirectivesEnabledConfig;
8580
+ };
8581
+
8582
+
8583
+ var cssClassDirectivesEnabledConfig = true;
8584
+ /**
8585
+ * @ngdoc method
8586
+ * @name $compileProvider#cssClassDirectivesEnabled
8587
+ * @description
8588
+ *
8589
+ * It indicates to the compiler
8590
+ * whether or not directives on element classes should be compiled.
8591
+ * Defaults to `true`.
8592
+ *
8593
+ * Calling this function with false disables the compilation of directives
8594
+ * on element classes for the whole application.
8595
+ * This results in a compilation performance gain,
8596
+ * as the compiler doesn't have to check element classes when looking for directives.
8597
+ * This should however only be used if you are sure that no class directives are used in
8598
+ * the application (including any 3rd party directives).
8599
+ *
8600
+ * @param {boolean} enabled `false` if the compiler may ignore directives on element classes
8601
+ * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
8602
+ */
8603
+ this.cssClassDirectivesEnabled = function(value) {
8604
+ if (arguments.length) {
8605
+ cssClassDirectivesEnabledConfig = value;
8606
+ return this;
8607
+ }
8608
+ return cssClassDirectivesEnabledConfig;
8609
+ };
8610
+
8611
+ this.$get = [
8612
+ '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
8613
+ '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri',
8614
+ function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
8615
+ $controller, $rootScope, $sce, $animate, $$sanitizeUri) {
8616
+
8617
+ var SIMPLE_ATTR_NAME = /^\w/;
8618
+ var specialAttrHolder = window.document.createElement('div');
8619
+
8620
+
8621
+ var commentDirectivesEnabled = commentDirectivesEnabledConfig;
8622
+ var cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig;
8623
+
8624
+
8625
+ var onChangesTtl = TTL;
8626
+ // The onChanges hooks should all be run together in a single digest
8627
+ // When changes occur, the call to trigger their hooks will be added to this queue
8628
+ var onChangesQueue;
8629
+
8630
+ // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest
8631
+ function flushOnChangesQueue() {
8632
+ try {
8633
+ if (!(--onChangesTtl)) {
8634
+ // We have hit the TTL limit so reset everything
8635
+ onChangesQueue = undefined;
8636
+ throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL);
8637
+ }
8638
+ // We must run this hook in an apply since the $$postDigest runs outside apply
8639
+ $rootScope.$apply(function() {
8640
+ var errors = [];
8641
+ for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) {
8642
+ try {
8643
+ onChangesQueue[i]();
8644
+ } catch (e) {
8645
+ errors.push(e);
8646
+ }
8647
+ }
8648
+ // Reset the queue to trigger a new schedule next time there is a change
8649
+ onChangesQueue = undefined;
8650
+ if (errors.length) {
8651
+ throw errors;
8652
+ }
8653
+ });
8654
+ } finally {
8655
+ onChangesTtl++;
8656
+ }
8657
+ }
8658
+
8659
+
8660
+ function Attributes(element, attributesToCopy) {
8661
+ if (attributesToCopy) {
8662
+ var keys = Object.keys(attributesToCopy);
8663
+ var i, l, key;
8664
+
8665
+ for (i = 0, l = keys.length; i < l; i++) {
8666
+ key = keys[i];
8667
+ this[key] = attributesToCopy[key];
8668
+ }
8669
+ } else {
8670
+ this.$attr = {};
8671
+ }
8672
+
8673
+ this.$$element = element;
8674
+ }
8675
+
8676
+ Attributes.prototype = {
8677
+ /**
8678
+ * @ngdoc method
8679
+ * @name $compile.directive.Attributes#$normalize
8680
+ * @kind function
8681
+ *
8682
+ * @description
8683
+ * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
8684
+ * `data-`) to its normalized, camelCase form.
8685
+ *
8686
+ * Also there is special case for Moz prefix starting with upper case letter.
8687
+ *
8688
+ * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
8689
+ *
8690
+ * @param {string} name Name to normalize
8691
+ */
8692
+ $normalize: directiveNormalize,
8693
+
8694
+
8695
+ /**
8696
+ * @ngdoc method
8697
+ * @name $compile.directive.Attributes#$addClass
8698
+ * @kind function
8699
+ *
8700
+ * @description
8701
+ * Adds the CSS class value specified by the classVal parameter to the element. If animations
8702
+ * are enabled then an animation will be triggered for the class addition.
8703
+ *
8704
+ * @param {string} classVal The className value that will be added to the element
8705
+ */
8706
+ $addClass: function(classVal) {
8707
+ if (classVal && classVal.length > 0) {
8708
+ $animate.addClass(this.$$element, classVal);
8709
+ }
8710
+ },
8711
+
8712
+ /**
8713
+ * @ngdoc method
8714
+ * @name $compile.directive.Attributes#$removeClass
8715
+ * @kind function
8716
+ *
8717
+ * @description
8718
+ * Removes the CSS class value specified by the classVal parameter from the element. If
8719
+ * animations are enabled then an animation will be triggered for the class removal.
8720
+ *
8721
+ * @param {string} classVal The className value that will be removed from the element
8722
+ */
8723
+ $removeClass: function(classVal) {
8724
+ if (classVal && classVal.length > 0) {
8725
+ $animate.removeClass(this.$$element, classVal);
8726
+ }
8727
+ },
8728
+
8729
+ /**
8730
+ * @ngdoc method
8731
+ * @name $compile.directive.Attributes#$updateClass
8732
+ * @kind function
8733
+ *
8734
+ * @description
8735
+ * Adds and removes the appropriate CSS class values to the element based on the difference
8736
+ * between the new and old CSS class values (specified as newClasses and oldClasses).
8737
+ *
8738
+ * @param {string} newClasses The current CSS className value
8739
+ * @param {string} oldClasses The former CSS className value
8740
+ */
8741
+ $updateClass: function(newClasses, oldClasses) {
8742
+ var toAdd = tokenDifference(newClasses, oldClasses);
8743
+ if (toAdd && toAdd.length) {
8744
+ $animate.addClass(this.$$element, toAdd);
8745
+ }
8746
+
8747
+ var toRemove = tokenDifference(oldClasses, newClasses);
8748
+ if (toRemove && toRemove.length) {
8749
+ $animate.removeClass(this.$$element, toRemove);
8750
+ }
8751
+ },
8752
+
8753
+ /**
8754
+ * Set a normalized attribute on the element in a way such that all directives
8755
+ * can share the attribute. This function properly handles boolean attributes.
8756
+ * @param {string} key Normalized key. (ie ngAttribute)
8757
+ * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
8758
+ * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
8759
+ * Defaults to true.
8760
+ * @param {string=} attrName Optional none normalized name. Defaults to key.
8761
+ */
8762
+ $set: function(key, value, writeAttr, attrName) {
8763
+ // TODO: decide whether or not to throw an error if "class"
8764
+ //is set through this function since it may cause $updateClass to
8765
+ //become unstable.
8766
+
8767
+ var node = this.$$element[0],
8768
+ booleanKey = getBooleanAttrName(node, key),
8769
+ aliasedKey = getAliasedAttrName(key),
8770
+ observer = key,
8771
+ nodeName;
8772
+
8773
+ if (booleanKey) {
8774
+ this.$$element.prop(key, value);
8775
+ attrName = booleanKey;
8776
+ } else if (aliasedKey) {
8777
+ this[aliasedKey] = value;
8778
+ observer = aliasedKey;
8779
+ }
8780
+
8781
+ this[key] = value;
8782
+
8783
+ // translate normalized key to actual key
8784
+ if (attrName) {
8785
+ this.$attr[key] = attrName;
8786
+ } else {
8787
+ attrName = this.$attr[key];
8788
+ if (!attrName) {
8789
+ this.$attr[key] = attrName = snake_case(key, '-');
8790
+ }
8791
+ }
8792
+
8793
+ nodeName = nodeName_(this.$$element);
8794
+
8795
+ if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) ||
8796
+ (nodeName === 'img' && key === 'src')) {
8797
+ // sanitize a[href] and img[src] values
8798
+ this[key] = value = $$sanitizeUri(value, key === 'src');
8799
+ } else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) {
8800
+ // sanitize img[srcset] values
8801
+ var result = '';
8802
+
8803
+ // first check if there are spaces because it's not the same pattern
8804
+ var trimmedSrcset = trim(value);
8805
+ // ( 999x ,| 999w ,| ,|, )
8806
+ var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
8807
+ var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
8808
+
8809
+ // split srcset into tuple of uri and descriptor except for the last item
8810
+ var rawUris = trimmedSrcset.split(pattern);
8811
+
8812
+ // for each tuples
8813
+ var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
8814
+ for (var i = 0; i < nbrUrisWith2parts; i++) {
8815
+ var innerIdx = i * 2;
8816
+ // sanitize the uri
8817
+ result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
8818
+ // add the descriptor
8819
+ result += (' ' + trim(rawUris[innerIdx + 1]));
8820
+ }
8821
+
8822
+ // split the last item into uri and descriptor
8823
+ var lastTuple = trim(rawUris[i * 2]).split(/\s/);
8824
+
8825
+ // sanitize the last uri
8826
+ result += $$sanitizeUri(trim(lastTuple[0]), true);
8827
+
8828
+ // and add the last descriptor if any
8829
+ if (lastTuple.length === 2) {
8830
+ result += (' ' + trim(lastTuple[1]));
8831
+ }
8832
+ this[key] = value = result;
8833
+ }
8834
+
8835
+ if (writeAttr !== false) {
8836
+ if (value === null || isUndefined(value)) {
8837
+ this.$$element.removeAttr(attrName);
8838
+ } else {
8839
+ if (SIMPLE_ATTR_NAME.test(attrName)) {
8840
+ this.$$element.attr(attrName, value);
8841
+ } else {
8842
+ setSpecialAttr(this.$$element[0], attrName, value);
8843
+ }
8844
+ }
8845
+ }
8846
+
8847
+ // fire observers
8848
+ var $$observers = this.$$observers;
8849
+ if ($$observers) {
8850
+ forEach($$observers[observer], function(fn) {
8851
+ try {
8852
+ fn(value);
8853
+ } catch (e) {
8854
+ $exceptionHandler(e);
8855
+ }
8856
+ });
8857
+ }
8858
+ },
8859
+
8860
+
8861
+ /**
8862
+ * @ngdoc method
8863
+ * @name $compile.directive.Attributes#$observe
8864
+ * @kind function
8865
+ *
8866
+ * @description
8867
+ * Observes an interpolated attribute.
8868
+ *
8869
+ * The observer function will be invoked once during the next `$digest` following
8870
+ * compilation. The observer is then invoked whenever the interpolated value
8871
+ * changes.
8872
+ *
8873
+ * @param {string} key Normalized key. (ie ngAttribute) .
8874
+ * @param {function(interpolatedValue)} fn Function that will be called whenever
8875
+ the interpolated value of the attribute changes.
8876
+ * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation
8877
+ * guide} for more info.
8878
+ * @returns {function()} Returns a deregistration function for this observer.
8879
+ */
8880
+ $observe: function(key, fn) {
8881
+ var attrs = this,
8882
+ $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
8883
+ listeners = ($$observers[key] || ($$observers[key] = []));
8884
+
8885
+ listeners.push(fn);
8886
+ $rootScope.$evalAsync(function() {
8887
+ if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
8888
+ // no one registered attribute interpolation function, so lets call it manually
8889
+ fn(attrs[key]);
8890
+ }
8891
+ });
8892
+
8893
+ return function() {
8894
+ arrayRemove(listeners, fn);
8895
+ };
8896
+ }
8897
+ };
8898
+
8899
+ function setSpecialAttr(element, attrName, value) {
8900
+ // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute`
8901
+ // so we have to jump through some hoops to get such an attribute
8902
+ // https://github.com/angular/angular.js/pull/13318
8903
+ specialAttrHolder.innerHTML = '<span ' + attrName + '>';
8904
+ var attributes = specialAttrHolder.firstChild.attributes;
8905
+ var attribute = attributes[0];
8906
+ // We have to remove the attribute from its container element before we can add it to the destination element
8907
+ attributes.removeNamedItem(attribute.name);
8908
+ attribute.value = value;
8909
+ element.attributes.setNamedItem(attribute);
8910
+ }
8911
+
8912
+ function safeAddClass($element, className) {
8913
+ try {
8914
+ $element.addClass(className);
8915
+ } catch (e) {
8916
+ // ignore, since it means that we are trying to set class on
8917
+ // SVG element, where class name is read-only.
8918
+ }
8919
+ }
8920
+
8921
+
8922
+ var startSymbol = $interpolate.startSymbol(),
8923
+ endSymbol = $interpolate.endSymbol(),
8924
+ denormalizeTemplate = (startSymbol === '{{' && endSymbol === '}}')
8925
+ ? identity
8926
+ : function denormalizeTemplate(template) {
8927
+ return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
8928
+ },
8929
+ NG_ATTR_BINDING = /^ngAttr[A-Z]/;
8930
+ var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
8931
+
8932
+ compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
8933
+ var bindings = $element.data('$binding') || [];
8934
+
8935
+ if (isArray(binding)) {
8936
+ bindings = bindings.concat(binding);
8937
+ } else {
8938
+ bindings.push(binding);
8939
+ }
8940
+
8941
+ $element.data('$binding', bindings);
8942
+ } : noop;
8943
+
8944
+ compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
8945
+ safeAddClass($element, 'ng-binding');
8946
+ } : noop;
8947
+
8948
+ compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
8949
+ var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
8950
+ $element.data(dataName, scope);
8951
+ } : noop;
8952
+
8953
+ compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
8954
+ safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
8955
+ } : noop;
8956
+
8957
+ compile.$$createComment = function(directiveName, comment) {
8958
+ var content = '';
8959
+ if (debugInfoEnabled) {
8960
+ content = ' ' + (directiveName || '') + ': ';
8961
+ if (comment) content += comment + ' ';
8962
+ }
8963
+ return window.document.createComment(content);
8964
+ };
8965
+
8966
+ return compile;
8967
+
8968
+ //================================
8969
+
8970
+ function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
8971
+ previousCompileContext) {
8972
+ if (!($compileNodes instanceof jqLite)) {
8973
+ // jquery always rewraps, whereas we need to preserve the original selector so that we can
8974
+ // modify it.
8975
+ $compileNodes = jqLite($compileNodes);
8976
+ }
8977
+ var compositeLinkFn =
8978
+ compileNodes($compileNodes, transcludeFn, $compileNodes,
8979
+ maxPriority, ignoreDirective, previousCompileContext);
8980
+ compile.$$addScopeClass($compileNodes);
8981
+ var namespace = null;
8982
+ return function publicLinkFn(scope, cloneConnectFn, options) {
8983
+ if (!$compileNodes) {
8984
+ throw $compileMinErr('multilink', 'This element has already been linked.');
8985
+ }
8986
+ assertArg(scope, 'scope');
8987
+
8988
+ if (previousCompileContext && previousCompileContext.needsNewScope) {
8989
+ // A parent directive did a replace and a directive on this element asked
8990
+ // for transclusion, which caused us to lose a layer of element on which
8991
+ // we could hold the new transclusion scope, so we will create it manually
8992
+ // here.
8993
+ scope = scope.$parent.$new();
8994
+ }
8995
+
8996
+ options = options || {};
8997
+ var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
8998
+ transcludeControllers = options.transcludeControllers,
8999
+ futureParentElement = options.futureParentElement;
9000
+
9001
+ // When `parentBoundTranscludeFn` is passed, it is a
9002
+ // `controllersBoundTransclude` function (it was previously passed
9003
+ // as `transclude` to directive.link) so we must unwrap it to get
9004
+ // its `boundTranscludeFn`
9005
+ if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
9006
+ parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
9007
+ }
9008
+
9009
+ if (!namespace) {
9010
+ namespace = detectNamespaceForChildElements(futureParentElement);
9011
+ }
9012
+ var $linkNode;
9013
+ if (namespace !== 'html') {
9014
+ // When using a directive with replace:true and templateUrl the $compileNodes
9015
+ // (or a child element inside of them)
9016
+ // might change, so we need to recreate the namespace adapted compileNodes
9017
+ // for call to the link function.
9018
+ // Note: This will already clone the nodes...
9019
+ $linkNode = jqLite(
9020
+ wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
9021
+ );
9022
+ } else if (cloneConnectFn) {
9023
+ // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
9024
+ // and sometimes changes the structure of the DOM.
9025
+ $linkNode = JQLitePrototype.clone.call($compileNodes);
9026
+ } else {
9027
+ $linkNode = $compileNodes;
9028
+ }
9029
+
9030
+ if (transcludeControllers) {
9031
+ for (var controllerName in transcludeControllers) {
9032
+ $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
9033
+ }
9034
+ }
9035
+
9036
+ compile.$$addScopeInfo($linkNode, scope);
9037
+
9038
+ if (cloneConnectFn) cloneConnectFn($linkNode, scope);
9039
+ if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
9040
+
9041
+ if (!cloneConnectFn) {
9042
+ $compileNodes = compositeLinkFn = null;
9043
+ }
9044
+ return $linkNode;
9045
+ };
9046
+ }
9047
+
9048
+ function detectNamespaceForChildElements(parentElement) {
9049
+ // TODO: Make this detect MathML as well...
9050
+ var node = parentElement && parentElement[0];
9051
+ if (!node) {
9052
+ return 'html';
9053
+ } else {
9054
+ return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html';
9055
+ }
9056
+ }
9057
+
9058
+ /**
9059
+ * Compile function matches each node in nodeList against the directives. Once all directives
9060
+ * for a particular node are collected their compile functions are executed. The compile
9061
+ * functions return values - the linking functions - are combined into a composite linking
9062
+ * function, which is the a linking function for the node.
9063
+ *
9064
+ * @param {NodeList} nodeList an array of nodes or NodeList to compile
9065
+ * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
9066
+ * scope argument is auto-generated to the new child of the transcluded parent scope.
9067
+ * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
9068
+ * the rootElement must be set the jqLite collection of the compile root. This is
9069
+ * needed so that the jqLite collection items can be replaced with widgets.
9070
+ * @param {number=} maxPriority Max directive priority.
9071
+ * @returns {Function} A composite linking function of all of the matched directives or null.
9072
+ */
9073
+ function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
9074
+ previousCompileContext) {
9075
+ var linkFns = [],
9076
+ // `nodeList` can be either an element's `.childNodes` (live NodeList)
9077
+ // or a jqLite/jQuery collection or an array
9078
+ notLiveList = isArray(nodeList) || (nodeList instanceof jqLite),
9079
+ attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
9080
+
9081
+
9082
+ for (var i = 0; i < nodeList.length; i++) {
9083
+ attrs = new Attributes();
9084
+
9085
+ // Support: IE 11 only
9086
+ // Workaround for #11781 and #14924
9087
+ if (msie === 11) {
9088
+ mergeConsecutiveTextNodes(nodeList, i, notLiveList);
9089
+ }
9090
+
9091
+ // We must always refer to `nodeList[i]` hereafter,
9092
+ // since the nodes can be replaced underneath us.
9093
+ directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
9094
+ ignoreDirective);
9095
+
9096
+ nodeLinkFn = (directives.length)
9097
+ ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
9098
+ null, [], [], previousCompileContext)
9099
+ : null;
9100
+
9101
+ if (nodeLinkFn && nodeLinkFn.scope) {
9102
+ compile.$$addScopeClass(attrs.$$element);
9103
+ }
9104
+
9105
+ childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
9106
+ !(childNodes = nodeList[i].childNodes) ||
9107
+ !childNodes.length)
9108
+ ? null
9109
+ : compileNodes(childNodes,
9110
+ nodeLinkFn ? (
9111
+ (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
9112
+ && nodeLinkFn.transclude) : transcludeFn);
9113
+
9114
+ if (nodeLinkFn || childLinkFn) {
9115
+ linkFns.push(i, nodeLinkFn, childLinkFn);
9116
+ linkFnFound = true;
9117
+ nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
9118
+ }
9119
+
9120
+ //use the previous context only for the first element in the virtual group
9121
+ previousCompileContext = null;
9122
+ }
9123
+
9124
+ // return a linking function if we have found anything, null otherwise
9125
+ return linkFnFound ? compositeLinkFn : null;
9126
+
9127
+ function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
9128
+ var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
9129
+ var stableNodeList;
9130
+
9131
+
9132
+ if (nodeLinkFnFound) {
9133
+ // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
9134
+ // offsets don't get screwed up
9135
+ var nodeListLength = nodeList.length;
9136
+ stableNodeList = new Array(nodeListLength);
9137
+
9138
+ // create a sparse array by only copying the elements which have a linkFn
9139
+ for (i = 0; i < linkFns.length; i += 3) {
9140
+ idx = linkFns[i];
9141
+ stableNodeList[idx] = nodeList[idx];
9142
+ }
9143
+ } else {
9144
+ stableNodeList = nodeList;
9145
+ }
9146
+
9147
+ for (i = 0, ii = linkFns.length; i < ii;) {
9148
+ node = stableNodeList[linkFns[i++]];
9149
+ nodeLinkFn = linkFns[i++];
9150
+ childLinkFn = linkFns[i++];
9151
+
9152
+ if (nodeLinkFn) {
9153
+ if (nodeLinkFn.scope) {
9154
+ childScope = scope.$new();
9155
+ compile.$$addScopeInfo(jqLite(node), childScope);
9156
+ } else {
9157
+ childScope = scope;
9158
+ }
9159
+
9160
+ if (nodeLinkFn.transcludeOnThisElement) {
9161
+ childBoundTranscludeFn = createBoundTranscludeFn(
9162
+ scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
9163
+
9164
+ } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
9165
+ childBoundTranscludeFn = parentBoundTranscludeFn;
9166
+
9167
+ } else if (!parentBoundTranscludeFn && transcludeFn) {
9168
+ childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
9169
+
9170
+ } else {
9171
+ childBoundTranscludeFn = null;
9172
+ }
9173
+
9174
+ nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
9175
+
9176
+ } else if (childLinkFn) {
9177
+ childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
9178
+ }
9179
+ }
9180
+ }
9181
+ }
9182
+
9183
+ function mergeConsecutiveTextNodes(nodeList, idx, notLiveList) {
9184
+ var node = nodeList[idx];
9185
+ var parent = node.parentNode;
9186
+ var sibling;
9187
+
9188
+ if (node.nodeType !== NODE_TYPE_TEXT) {
9189
+ return;
9190
+ }
9191
+
9192
+ while (true) {
9193
+ sibling = parent ? node.nextSibling : nodeList[idx + 1];
9194
+ if (!sibling || sibling.nodeType !== NODE_TYPE_TEXT) {
9195
+ break;
9196
+ }
9197
+
9198
+ node.nodeValue = node.nodeValue + sibling.nodeValue;
9199
+
9200
+ if (sibling.parentNode) {
9201
+ sibling.parentNode.removeChild(sibling);
9202
+ }
9203
+ if (notLiveList && sibling === nodeList[idx + 1]) {
9204
+ nodeList.splice(idx + 1, 1);
9205
+ }
9206
+ }
9207
+ }
9208
+
9209
+ function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
9210
+ function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
9211
+
9212
+ if (!transcludedScope) {
9213
+ transcludedScope = scope.$new(false, containingScope);
9214
+ transcludedScope.$$transcluded = true;
9215
+ }
9216
+
9217
+ return transcludeFn(transcludedScope, cloneFn, {
9218
+ parentBoundTranscludeFn: previousBoundTranscludeFn,
9219
+ transcludeControllers: controllers,
9220
+ futureParentElement: futureParentElement
9221
+ });
9222
+ }
9223
+
9224
+ // We need to attach the transclusion slots onto the `boundTranscludeFn`
9225
+ // so that they are available inside the `controllersBoundTransclude` function
9226
+ var boundSlots = boundTranscludeFn.$$slots = createMap();
9227
+ for (var slotName in transcludeFn.$$slots) {
9228
+ if (transcludeFn.$$slots[slotName]) {
9229
+ boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn);
9230
+ } else {
9231
+ boundSlots[slotName] = null;
9232
+ }
9233
+ }
9234
+
9235
+ return boundTranscludeFn;
9236
+ }
9237
+
9238
+ /**
9239
+ * Looks for directives on the given node and adds them to the directive collection which is
9240
+ * sorted.
9241
+ *
9242
+ * @param node Node to search.
9243
+ * @param directives An array to which the directives are added to. This array is sorted before
9244
+ * the function returns.
9245
+ * @param attrs The shared attrs object which is used to populate the normalized attributes.
9246
+ * @param {number=} maxPriority Max directive priority.
9247
+ */
9248
+ function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
9249
+ var nodeType = node.nodeType,
9250
+ attrsMap = attrs.$attr,
9251
+ match,
9252
+ nodeName,
9253
+ className;
9254
+
9255
+ switch (nodeType) {
9256
+ case NODE_TYPE_ELEMENT: /* Element */
9257
+
9258
+ nodeName = nodeName_(node);
9259
+
9260
+ // use the node name: <directive>
9261
+ addDirective(directives,
9262
+ directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective);
9263
+
9264
+ // iterate over the attributes
9265
+ for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
9266
+ j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
9267
+ var attrStartName = false;
9268
+ var attrEndName = false;
9269
+
9270
+ attr = nAttrs[j];
9271
+ name = attr.name;
9272
+ value = attr.value;
9273
+
9274
+ // support ngAttr attribute binding
9275
+ ngAttrName = directiveNormalize(name);
9276
+ isNgAttr = NG_ATTR_BINDING.test(ngAttrName);
9277
+ if (isNgAttr) {
9278
+ name = name.replace(PREFIX_REGEXP, '')
9279
+ .substr(8).replace(/_(.)/g, function(match, letter) {
9280
+ return letter.toUpperCase();
9281
+ });
9282
+ }
9283
+
9284
+ var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
9285
+ if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
9286
+ attrStartName = name;
9287
+ attrEndName = name.substr(0, name.length - 5) + 'end';
9288
+ name = name.substr(0, name.length - 6);
9289
+ }
9290
+
9291
+ nName = directiveNormalize(name.toLowerCase());
9292
+ attrsMap[nName] = name;
9293
+ if (isNgAttr || !attrs.hasOwnProperty(nName)) {
9294
+ attrs[nName] = value;
9295
+ if (getBooleanAttrName(node, nName)) {
9296
+ attrs[nName] = true; // presence means true
9297
+ }
9298
+ }
9299
+ addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
9300
+ addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
9301
+ attrEndName);
9302
+ }
9303
+
9304
+ if (nodeName === 'input' && node.getAttribute('type') === 'hidden') {
9305
+ // Hidden input elements can have strange behaviour when navigating back to the page
9306
+ // This tells the browser not to try to cache and reinstate previous values
9307
+ node.setAttribute('autocomplete', 'off');
9308
+ }
9309
+
9310
+ // use class as directive
9311
+ if (!cssClassDirectivesEnabled) break;
9312
+ className = node.className;
9313
+ if (isObject(className)) {
9314
+ // Maybe SVGAnimatedString
9315
+ className = className.animVal;
9316
+ }
9317
+ if (isString(className) && className !== '') {
9318
+ while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) {
9319
+ nName = directiveNormalize(match[2]);
9320
+ if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
9321
+ attrs[nName] = trim(match[3]);
9322
+ }
9323
+ className = className.substr(match.index + match[0].length);
9324
+ }
9325
+ }
9326
+ break;
9327
+ case NODE_TYPE_TEXT: /* Text Node */
9328
+ addTextInterpolateDirective(directives, node.nodeValue);
9329
+ break;
9330
+ case NODE_TYPE_COMMENT: /* Comment */
9331
+ if (!commentDirectivesEnabled) break;
9332
+ collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective);
9333
+ break;
9334
+ }
9335
+
9336
+ directives.sort(byPriority);
9337
+ return directives;
9338
+ }
9339
+
9340
+ function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
9341
+ // function created because of performance, try/catch disables
9342
+ // the optimization of the whole function #14848
9343
+ try {
9344
+ var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
9345
+ if (match) {
9346
+ var nName = directiveNormalize(match[1]);
9347
+ if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
9348
+ attrs[nName] = trim(match[2]);
9349
+ }
9350
+ }
9351
+ } catch (e) {
9352
+ // turns out that under some circumstances IE9 throws errors when one attempts to read
9353
+ // comment's node value.
9354
+ // Just ignore it and continue. (Can't seem to reproduce in test case.)
9355
+ }
9356
+ }
9357
+
9358
+ /**
9359
+ * Given a node with a directive-start it collects all of the siblings until it finds
9360
+ * directive-end.
9361
+ * @param node
9362
+ * @param attrStart
9363
+ * @param attrEnd
9364
+ * @returns {*}
9365
+ */
9366
+ function groupScan(node, attrStart, attrEnd) {
9367
+ var nodes = [];
9368
+ var depth = 0;
9369
+ if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
9370
+ do {
9371
+ if (!node) {
9372
+ throw $compileMinErr('uterdir',
9373
+ 'Unterminated attribute, found \'{0}\' but no matching \'{1}\' found.',
9374
+ attrStart, attrEnd);
9375
+ }
9376
+ if (node.nodeType === NODE_TYPE_ELEMENT) {
9377
+ if (node.hasAttribute(attrStart)) depth++;
9378
+ if (node.hasAttribute(attrEnd)) depth--;
9379
+ }
9380
+ nodes.push(node);
9381
+ node = node.nextSibling;
9382
+ } while (depth > 0);
9383
+ } else {
9384
+ nodes.push(node);
9385
+ }
9386
+
9387
+ return jqLite(nodes);
9388
+ }
9389
+
9390
+ /**
9391
+ * Wrapper for linking function which converts normal linking function into a grouped
9392
+ * linking function.
9393
+ * @param linkFn
9394
+ * @param attrStart
9395
+ * @param attrEnd
9396
+ * @returns {Function}
9397
+ */
9398
+ function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
9399
+ return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) {
9400
+ element = groupScan(element[0], attrStart, attrEnd);
9401
+ return linkFn(scope, element, attrs, controllers, transcludeFn);
9402
+ };
9403
+ }
9404
+
9405
+ /**
9406
+ * A function generator that is used to support both eager and lazy compilation
9407
+ * linking function.
9408
+ * @param eager
9409
+ * @param $compileNodes
9410
+ * @param transcludeFn
9411
+ * @param maxPriority
9412
+ * @param ignoreDirective
9413
+ * @param previousCompileContext
9414
+ * @returns {Function}
9415
+ */
9416
+ function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
9417
+ var compiled;
9418
+
9419
+ if (eager) {
9420
+ return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
9421
+ }
9422
+ return /** @this */ function lazyCompilation() {
9423
+ if (!compiled) {
9424
+ compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
9425
+
9426
+ // Null out all of these references in order to make them eligible for garbage collection
9427
+ // since this is a potentially long lived closure
9428
+ $compileNodes = transcludeFn = previousCompileContext = null;
9429
+ }
9430
+ return compiled.apply(this, arguments);
9431
+ };
9432
+ }
9433
+
9434
+ /**
9435
+ * Once the directives have been collected, their compile functions are executed. This method
9436
+ * is responsible for inlining directive templates as well as terminating the application
9437
+ * of the directives if the terminal directive has been reached.
9438
+ *
9439
+ * @param {Array} directives Array of collected directives to execute their compile function.
9440
+ * this needs to be pre-sorted by priority order.
9441
+ * @param {Node} compileNode The raw DOM node to apply the compile functions to
9442
+ * @param {Object} templateAttrs The shared attribute function
9443
+ * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
9444
+ * scope argument is auto-generated to the new
9445
+ * child of the transcluded parent scope.
9446
+ * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
9447
+ * argument has the root jqLite array so that we can replace nodes
9448
+ * on it.
9449
+ * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
9450
+ * compiling the transclusion.
9451
+ * @param {Array.<Function>} preLinkFns
9452
+ * @param {Array.<Function>} postLinkFns
9453
+ * @param {Object} previousCompileContext Context used for previous compilation of the current
9454
+ * node
9455
+ * @returns {Function} linkFn
9456
+ */
9457
+ function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
9458
+ jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
9459
+ previousCompileContext) {
9460
+ previousCompileContext = previousCompileContext || {};
9461
+
9462
+ var terminalPriority = -Number.MAX_VALUE,
9463
+ newScopeDirective = previousCompileContext.newScopeDirective,
9464
+ controllerDirectives = previousCompileContext.controllerDirectives,
9465
+ newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
9466
+ templateDirective = previousCompileContext.templateDirective,
9467
+ nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
9468
+ hasTranscludeDirective = false,
9469
+ hasTemplate = false,
9470
+ hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
9471
+ $compileNode = templateAttrs.$$element = jqLite(compileNode),
9472
+ directive,
9473
+ directiveName,
9474
+ $template,
9475
+ replaceDirective = originalReplaceDirective,
9476
+ childTranscludeFn = transcludeFn,
9477
+ linkFn,
9478
+ didScanForMultipleTransclusion = false,
9479
+ mightHaveMultipleTransclusionError = false,
9480
+ directiveValue;
9481
+
9482
+ // executes all directives on the current element
9483
+ for (var i = 0, ii = directives.length; i < ii; i++) {
9484
+ directive = directives[i];
9485
+ var attrStart = directive.$$start;
9486
+ var attrEnd = directive.$$end;
9487
+
9488
+ // collect multiblock sections
9489
+ if (attrStart) {
9490
+ $compileNode = groupScan(compileNode, attrStart, attrEnd);
9491
+ }
9492
+ $template = undefined;
9493
+
9494
+ if (terminalPriority > directive.priority) {
9495
+ break; // prevent further processing of directives
9496
+ }
9497
+
9498
+ directiveValue = directive.scope;
9499
+
9500
+ if (directiveValue) {
9501
+
9502
+ // skip the check for directives with async templates, we'll check the derived sync
9503
+ // directive when the template arrives
9504
+ if (!directive.templateUrl) {
9505
+ if (isObject(directiveValue)) {
9506
+ // This directive is trying to add an isolated scope.
9507
+ // Check that there is no scope of any kind already
9508
+ assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
9509
+ directive, $compileNode);
9510
+ newIsolateScopeDirective = directive;
9511
+ } else {
9512
+ // This directive is trying to add a child scope.
9513
+ // Check that there is no isolated scope already
9514
+ assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
9515
+ $compileNode);
9516
+ }
9517
+ }
9518
+
9519
+ newScopeDirective = newScopeDirective || directive;
9520
+ }
9521
+
9522
+ directiveName = directive.name;
9523
+
9524
+ // If we encounter a condition that can result in transclusion on the directive,
9525
+ // then scan ahead in the remaining directives for others that may cause a multiple
9526
+ // transclusion error to be thrown during the compilation process. If a matching directive
9527
+ // is found, then we know that when we encounter a transcluded directive, we need to eagerly
9528
+ // compile the `transclude` function rather than doing it lazily in order to throw
9529
+ // exceptions at the correct time
9530
+ if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template))
9531
+ || (directive.transclude && !directive.$$tlb))) {
9532
+ var candidateDirective;
9533
+
9534
+ for (var scanningIndex = i + 1; (candidateDirective = directives[scanningIndex++]);) {
9535
+ if ((candidateDirective.transclude && !candidateDirective.$$tlb)
9536
+ || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) {
9537
+ mightHaveMultipleTransclusionError = true;
9538
+ break;
9539
+ }
9540
+ }
9541
+
9542
+ didScanForMultipleTransclusion = true;
9543
+ }
9544
+
9545
+ if (!directive.templateUrl && directive.controller) {
9546
+ controllerDirectives = controllerDirectives || createMap();
9547
+ assertNoDuplicate('\'' + directiveName + '\' controller',
9548
+ controllerDirectives[directiveName], directive, $compileNode);
9549
+ controllerDirectives[directiveName] = directive;
9550
+ }
9551
+
9552
+ directiveValue = directive.transclude;
9553
+
9554
+ if (directiveValue) {
9555
+ hasTranscludeDirective = true;
9556
+
9557
+ // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
9558
+ // This option should only be used by directives that know how to safely handle element transclusion,
9559
+ // where the transcluded nodes are added or replaced after linking.
9560
+ if (!directive.$$tlb) {
9561
+ assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
9562
+ nonTlbTranscludeDirective = directive;
9563
+ }
9564
+
9565
+ if (directiveValue === 'element') {
9566
+ hasElementTranscludeDirective = true;
9567
+ terminalPriority = directive.priority;
9568
+ $template = $compileNode;
9569
+ $compileNode = templateAttrs.$$element =
9570
+ jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName]));
9571
+ compileNode = $compileNode[0];
9572
+ replaceWith(jqCollection, sliceArgs($template), compileNode);
9573
+
9574
+ // Support: Chrome < 50
9575
+ // https://github.com/angular/angular.js/issues/14041
9576
+
9577
+ // In the versions of V8 prior to Chrome 50, the document fragment that is created
9578
+ // in the `replaceWith` function is improperly garbage collected despite still
9579
+ // being referenced by the `parentNode` property of all of the child nodes. By adding
9580
+ // a reference to the fragment via a different property, we can avoid that incorrect
9581
+ // behavior.
9582
+ // TODO: remove this line after Chrome 50 has been released
9583
+ $template[0].$$parentNode = $template[0].parentNode;
9584
+
9585
+ childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
9586
+ replaceDirective && replaceDirective.name, {
9587
+ // Don't pass in:
9588
+ // - controllerDirectives - otherwise we'll create duplicates controllers
9589
+ // - newIsolateScopeDirective or templateDirective - combining templates with
9590
+ // element transclusion doesn't make sense.
9591
+ //
9592
+ // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
9593
+ // on the same element more than once.
9594
+ nonTlbTranscludeDirective: nonTlbTranscludeDirective
9595
+ });
9596
+ } else {
9597
+
9598
+ var slots = createMap();
9599
+
9600
+ if (!isObject(directiveValue)) {
9601
+ $template = jqLite(jqLiteClone(compileNode)).contents();
9602
+ } else {
9603
+
9604
+ // We have transclusion slots,
9605
+ // collect them up, compile them and store their transclusion functions
9606
+ $template = [];
9607
+
9608
+ var slotMap = createMap();
9609
+ var filledSlots = createMap();
9610
+
9611
+ // Parse the element selectors
9612
+ forEach(directiveValue, function(elementSelector, slotName) {
9613
+ // If an element selector starts with a ? then it is optional
9614
+ var optional = (elementSelector.charAt(0) === '?');
9615
+ elementSelector = optional ? elementSelector.substring(1) : elementSelector;
9616
+
9617
+ slotMap[elementSelector] = slotName;
9618
+
9619
+ // We explicitly assign `null` since this implies that a slot was defined but not filled.
9620
+ // Later when calling boundTransclusion functions with a slot name we only error if the
9621
+ // slot is `undefined`
9622
+ slots[slotName] = null;
9623
+
9624
+ // filledSlots contains `true` for all slots that are either optional or have been
9625
+ // filled. This is used to check that we have not missed any required slots
9626
+ filledSlots[slotName] = optional;
9627
+ });
9628
+
9629
+ // Add the matching elements into their slot
9630
+ forEach($compileNode.contents(), function(node) {
9631
+ var slotName = slotMap[directiveNormalize(nodeName_(node))];
9632
+ if (slotName) {
9633
+ filledSlots[slotName] = true;
9634
+ slots[slotName] = slots[slotName] || [];
9635
+ slots[slotName].push(node);
9636
+ } else {
9637
+ $template.push(node);
9638
+ }
9639
+ });
9640
+
9641
+ // Check for required slots that were not filled
9642
+ forEach(filledSlots, function(filled, slotName) {
9643
+ if (!filled) {
9644
+ throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName);
9645
+ }
9646
+ });
9647
+
9648
+ for (var slotName in slots) {
9649
+ if (slots[slotName]) {
9650
+ // Only define a transclusion function if the slot was filled
9651
+ slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn);
9652
+ }
9653
+ }
9654
+ }
9655
+
9656
+ $compileNode.empty(); // clear contents
9657
+ childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined,
9658
+ undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
9659
+ childTranscludeFn.$$slots = slots;
9660
+ }
9661
+ }
9662
+
9663
+ if (directive.template) {
9664
+ hasTemplate = true;
9665
+ assertNoDuplicate('template', templateDirective, directive, $compileNode);
9666
+ templateDirective = directive;
9667
+
9668
+ directiveValue = (isFunction(directive.template))
9669
+ ? directive.template($compileNode, templateAttrs)
9670
+ : directive.template;
9671
+
9672
+ directiveValue = denormalizeTemplate(directiveValue);
9673
+
9674
+ if (directive.replace) {
9675
+ replaceDirective = directive;
9676
+ if (jqLiteIsTextNode(directiveValue)) {
9677
+ $template = [];
9678
+ } else {
9679
+ $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
9680
+ }
9681
+ compileNode = $template[0];
9682
+
9683
+ if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
9684
+ throw $compileMinErr('tplrt',
9685
+ 'Template for directive \'{0}\' must have exactly one root element. {1}',
9686
+ directiveName, '');
9687
+ }
9688
+
9689
+ replaceWith(jqCollection, $compileNode, compileNode);
9690
+
9691
+ var newTemplateAttrs = {$attr: {}};
9692
+
9693
+ // combine directives from the original node and from the template:
9694
+ // - take the array of directives for this element
9695
+ // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
9696
+ // - collect directives from the template and sort them by priority
9697
+ // - combine directives as: processed + template + unprocessed
9698
+ var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
9699
+ var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
9700
+
9701
+ if (newIsolateScopeDirective || newScopeDirective) {
9702
+ // The original directive caused the current element to be replaced but this element
9703
+ // also needs to have a new scope, so we need to tell the template directives
9704
+ // that they would need to get their scope from further up, if they require transclusion
9705
+ markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
9706
+ }
9707
+ directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
9708
+ mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
9709
+
9710
+ ii = directives.length;
9711
+ } else {
9712
+ $compileNode.html(directiveValue);
9713
+ }
9714
+ }
9715
+
9716
+ if (directive.templateUrl) {
9717
+ hasTemplate = true;
9718
+ assertNoDuplicate('template', templateDirective, directive, $compileNode);
9719
+ templateDirective = directive;
9720
+
9721
+ if (directive.replace) {
9722
+ replaceDirective = directive;
9723
+ }
9724
+
9725
+ // eslint-disable-next-line no-func-assign
9726
+ nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
9727
+ templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
9728
+ controllerDirectives: controllerDirectives,
9729
+ newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
9730
+ newIsolateScopeDirective: newIsolateScopeDirective,
9731
+ templateDirective: templateDirective,
9732
+ nonTlbTranscludeDirective: nonTlbTranscludeDirective
9733
+ });
9734
+ ii = directives.length;
9735
+ } else if (directive.compile) {
9736
+ try {
9737
+ linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
9738
+ var context = directive.$$originalDirective || directive;
9739
+ if (isFunction(linkFn)) {
9740
+ addLinkFns(null, bind(context, linkFn), attrStart, attrEnd);
9741
+ } else if (linkFn) {
9742
+ addLinkFns(bind(context, linkFn.pre), bind(context, linkFn.post), attrStart, attrEnd);
9743
+ }
9744
+ } catch (e) {
9745
+ $exceptionHandler(e, startingTag($compileNode));
9746
+ }
9747
+ }
9748
+
9749
+ if (directive.terminal) {
9750
+ nodeLinkFn.terminal = true;
9751
+ terminalPriority = Math.max(terminalPriority, directive.priority);
9752
+ }
9753
+
9754
+ }
9755
+
9756
+ nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
9757
+ nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
9758
+ nodeLinkFn.templateOnThisElement = hasTemplate;
9759
+ nodeLinkFn.transclude = childTranscludeFn;
9760
+
9761
+ previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
9762
+
9763
+ // might be normal or delayed nodeLinkFn depending on if templateUrl is present
9764
+ return nodeLinkFn;
9765
+
9766
+ ////////////////////
9767
+
9768
+ function addLinkFns(pre, post, attrStart, attrEnd) {
9769
+ if (pre) {
9770
+ if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
9771
+ pre.require = directive.require;
9772
+ pre.directiveName = directiveName;
9773
+ if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
9774
+ pre = cloneAndAnnotateFn(pre, {isolateScope: true});
9775
+ }
9776
+ preLinkFns.push(pre);
9777
+ }
9778
+ if (post) {
9779
+ if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
9780
+ post.require = directive.require;
9781
+ post.directiveName = directiveName;
9782
+ if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
9783
+ post = cloneAndAnnotateFn(post, {isolateScope: true});
9784
+ }
9785
+ postLinkFns.push(post);
9786
+ }
9787
+ }
9788
+
9789
+ function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
9790
+ var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
9791
+ attrs, scopeBindingInfo;
9792
+
9793
+ if (compileNode === linkNode) {
9794
+ attrs = templateAttrs;
9795
+ $element = templateAttrs.$$element;
9796
+ } else {
9797
+ $element = jqLite(linkNode);
9798
+ attrs = new Attributes($element, templateAttrs);
9799
+ }
9800
+
9801
+ controllerScope = scope;
9802
+ if (newIsolateScopeDirective) {
9803
+ isolateScope = scope.$new(true);
9804
+ } else if (newScopeDirective) {
9805
+ controllerScope = scope.$parent;
9806
+ }
9807
+
9808
+ if (boundTranscludeFn) {
9809
+ // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
9810
+ // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
9811
+ transcludeFn = controllersBoundTransclude;
9812
+ transcludeFn.$$boundTransclude = boundTranscludeFn;
9813
+ // expose the slots on the `$transclude` function
9814
+ transcludeFn.isSlotFilled = function(slotName) {
9815
+ return !!boundTranscludeFn.$$slots[slotName];
9816
+ };
9817
+ }
9818
+
9819
+ if (controllerDirectives) {
9820
+ elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective);
9821
+ }
9822
+
9823
+ if (newIsolateScopeDirective) {
9824
+ // Initialize isolate scope bindings for new isolate scope directive.
9825
+ compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
9826
+ templateDirective === newIsolateScopeDirective.$$originalDirective)));
9827
+ compile.$$addScopeClass($element, true);
9828
+ isolateScope.$$isolateBindings =
9829
+ newIsolateScopeDirective.$$isolateBindings;
9830
+ scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope,
9831
+ isolateScope.$$isolateBindings,
9832
+ newIsolateScopeDirective);
9833
+ if (scopeBindingInfo.removeWatches) {
9834
+ isolateScope.$on('$destroy', scopeBindingInfo.removeWatches);
9835
+ }
9836
+ }
9837
+
9838
+ // Initialize bindToController bindings
9839
+ for (var name in elementControllers) {
9840
+ var controllerDirective = controllerDirectives[name];
9841
+ var controller = elementControllers[name];
9842
+ var bindings = controllerDirective.$$bindings.bindToController;
9843
+
9844
+ if (preAssignBindingsEnabled) {
9845
+ if (bindings) {
9846
+ controller.bindingInfo =
9847
+ initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
9848
+ } else {
9849
+ controller.bindingInfo = {};
9850
+ }
9851
+
9852
+ var controllerResult = controller();
9853
+ if (controllerResult !== controller.instance) {
9854
+ // If the controller constructor has a return value, overwrite the instance
9855
+ // from setupControllers
9856
+ controller.instance = controllerResult;
9857
+ $element.data('$' + controllerDirective.name + 'Controller', controllerResult);
9858
+ if (controller.bindingInfo.removeWatches) {
9859
+ controller.bindingInfo.removeWatches();
9860
+ }
9861
+ controller.bindingInfo =
9862
+ initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
9863
+ }
9864
+ } else {
9865
+ controller.instance = controller();
9866
+ $element.data('$' + controllerDirective.name + 'Controller', controller.instance);
9867
+ controller.bindingInfo =
9868
+ initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
9869
+ }
9870
+ }
9871
+
9872
+ // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
9873
+ forEach(controllerDirectives, function(controllerDirective, name) {
9874
+ var require = controllerDirective.require;
9875
+ if (controllerDirective.bindToController && !isArray(require) && isObject(require)) {
9876
+ extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers));
9877
+ }
9878
+ });
9879
+
9880
+ // Handle the init and destroy lifecycle hooks on all controllers that have them
9881
+ forEach(elementControllers, function(controller) {
9882
+ var controllerInstance = controller.instance;
9883
+ if (isFunction(controllerInstance.$onChanges)) {
9884
+ try {
9885
+ controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
9886
+ } catch (e) {
9887
+ $exceptionHandler(e);
9888
+ }
9889
+ }
9890
+ if (isFunction(controllerInstance.$onInit)) {
9891
+ try {
9892
+ controllerInstance.$onInit();
9893
+ } catch (e) {
9894
+ $exceptionHandler(e);
9895
+ }
9896
+ }
9897
+ if (isFunction(controllerInstance.$doCheck)) {
9898
+ controllerScope.$watch(function() { controllerInstance.$doCheck(); });
9899
+ controllerInstance.$doCheck();
9900
+ }
9901
+ if (isFunction(controllerInstance.$onDestroy)) {
9902
+ controllerScope.$on('$destroy', function callOnDestroyHook() {
9903
+ controllerInstance.$onDestroy();
9904
+ });
9905
+ }
9906
+ });
9907
+
9908
+ // PRELINKING
9909
+ for (i = 0, ii = preLinkFns.length; i < ii; i++) {
9910
+ linkFn = preLinkFns[i];
9911
+ invokeLinkFn(linkFn,
9912
+ linkFn.isolateScope ? isolateScope : scope,
9913
+ $element,
9914
+ attrs,
9915
+ linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
9916
+ transcludeFn
9917
+ );
9918
+ }
9919
+
9920
+ // RECURSION
9921
+ // We only pass the isolate scope, if the isolate directive has a template,
9922
+ // otherwise the child elements do not belong to the isolate directive.
9923
+ var scopeToChild = scope;
9924
+ if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
9925
+ scopeToChild = isolateScope;
9926
+ }
9927
+ if (childLinkFn) {
9928
+ childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
9929
+ }
9930
+
9931
+ // POSTLINKING
9932
+ for (i = postLinkFns.length - 1; i >= 0; i--) {
9933
+ linkFn = postLinkFns[i];
9934
+ invokeLinkFn(linkFn,
9935
+ linkFn.isolateScope ? isolateScope : scope,
9936
+ $element,
9937
+ attrs,
9938
+ linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
9939
+ transcludeFn
9940
+ );
9941
+ }
9942
+
9943
+ // Trigger $postLink lifecycle hooks
9944
+ forEach(elementControllers, function(controller) {
9945
+ var controllerInstance = controller.instance;
9946
+ if (isFunction(controllerInstance.$postLink)) {
9947
+ controllerInstance.$postLink();
9948
+ }
9949
+ });
9950
+
9951
+ // This is the function that is injected as `$transclude`.
9952
+ // Note: all arguments are optional!
9953
+ function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) {
9954
+ var transcludeControllers;
9955
+ // No scope passed in:
9956
+ if (!isScope(scope)) {
9957
+ slotName = futureParentElement;
9958
+ futureParentElement = cloneAttachFn;
9959
+ cloneAttachFn = scope;
9960
+ scope = undefined;
9961
+ }
9962
+
9963
+ if (hasElementTranscludeDirective) {
9964
+ transcludeControllers = elementControllers;
9965
+ }
9966
+ if (!futureParentElement) {
9967
+ futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
9968
+ }
9969
+ if (slotName) {
9970
+ // slotTranscludeFn can be one of three things:
9971
+ // * a transclude function - a filled slot
9972
+ // * `null` - an optional slot that was not filled
9973
+ // * `undefined` - a slot that was not declared (i.e. invalid)
9974
+ var slotTranscludeFn = boundTranscludeFn.$$slots[slotName];
9975
+ if (slotTranscludeFn) {
9976
+ return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
9977
+ } else if (isUndefined(slotTranscludeFn)) {
9978
+ throw $compileMinErr('noslot',
9979
+ 'No parent directive that requires a transclusion with slot name "{0}". ' +
9980
+ 'Element: {1}',
9981
+ slotName, startingTag($element));
9982
+ }
9983
+ } else {
9984
+ return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
9985
+ }
9986
+ }
9987
+ }
9988
+ }
9989
+
9990
+ function getControllers(directiveName, require, $element, elementControllers) {
9991
+ var value;
9992
+
9993
+ if (isString(require)) {
9994
+ var match = require.match(REQUIRE_PREFIX_REGEXP);
9995
+ var name = require.substring(match[0].length);
9996
+ var inheritType = match[1] || match[3];
9997
+ var optional = match[2] === '?';
9998
+
9999
+ //If only parents then start at the parent element
10000
+ if (inheritType === '^^') {
10001
+ $element = $element.parent();
10002
+ //Otherwise attempt getting the controller from elementControllers in case
10003
+ //the element is transcluded (and has no data) and to avoid .data if possible
10004
+ } else {
10005
+ value = elementControllers && elementControllers[name];
10006
+ value = value && value.instance;
10007
+ }
10008
+
10009
+ if (!value) {
10010
+ var dataName = '$' + name + 'Controller';
10011
+ value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
10012
+ }
10013
+
10014
+ if (!value && !optional) {
10015
+ throw $compileMinErr('ctreq',
10016
+ 'Controller \'{0}\', required by directive \'{1}\', can\'t be found!',
10017
+ name, directiveName);
10018
+ }
10019
+ } else if (isArray(require)) {
10020
+ value = [];
10021
+ for (var i = 0, ii = require.length; i < ii; i++) {
10022
+ value[i] = getControllers(directiveName, require[i], $element, elementControllers);
10023
+ }
10024
+ } else if (isObject(require)) {
10025
+ value = {};
10026
+ forEach(require, function(controller, property) {
10027
+ value[property] = getControllers(directiveName, controller, $element, elementControllers);
10028
+ });
10029
+ }
10030
+
10031
+ return value || null;
10032
+ }
10033
+
10034
+ function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) {
10035
+ var elementControllers = createMap();
10036
+ for (var controllerKey in controllerDirectives) {
10037
+ var directive = controllerDirectives[controllerKey];
10038
+ var locals = {
10039
+ $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
10040
+ $element: $element,
10041
+ $attrs: attrs,
10042
+ $transclude: transcludeFn
10043
+ };
10044
+
10045
+ var controller = directive.controller;
10046
+ if (controller === '@') {
10047
+ controller = attrs[directive.name];
10048
+ }
10049
+
10050
+ var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
10051
+
10052
+ // For directives with element transclusion the element is a comment.
10053
+ // In this case .data will not attach any data.
10054
+ // Instead, we save the controllers for the element in a local hash and attach to .data
10055
+ // later, once we have the actual element.
10056
+ elementControllers[directive.name] = controllerInstance;
10057
+ $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
10058
+ }
10059
+ return elementControllers;
10060
+ }
10061
+
10062
+ // Depending upon the context in which a directive finds itself it might need to have a new isolated
10063
+ // or child scope created. For instance:
10064
+ // * if the directive has been pulled into a template because another directive with a higher priority
10065
+ // asked for element transclusion
10066
+ // * if the directive itself asks for transclusion but it is at the root of a template and the original
10067
+ // element was replaced. See https://github.com/angular/angular.js/issues/12936
10068
+ function markDirectiveScope(directives, isolateScope, newScope) {
10069
+ for (var j = 0, jj = directives.length; j < jj; j++) {
10070
+ directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
10071
+ }
10072
+ }
10073
+
10074
+ /**
10075
+ * looks up the directive and decorates it with exception handling and proper parameters. We
10076
+ * call this the boundDirective.
10077
+ *
10078
+ * @param {string} name name of the directive to look up.
10079
+ * @param {string} location The directive must be found in specific format.
10080
+ * String containing any of theses characters:
10081
+ *
10082
+ * * `E`: element name
10083
+ * * `A': attribute
10084
+ * * `C`: class
10085
+ * * `M`: comment
10086
+ * @returns {boolean} true if directive was added.
10087
+ */
10088
+ function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
10089
+ endAttrName) {
10090
+ if (name === ignoreDirective) return null;
10091
+ var match = null;
10092
+ if (hasDirectives.hasOwnProperty(name)) {
10093
+ for (var directive, directives = $injector.get(name + Suffix),
10094
+ i = 0, ii = directives.length; i < ii; i++) {
10095
+ directive = directives[i];
10096
+ if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
10097
+ directive.restrict.indexOf(location) !== -1) {
10098
+ if (startAttrName) {
10099
+ directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
10100
+ }
10101
+ if (!directive.$$bindings) {
10102
+ var bindings = directive.$$bindings =
10103
+ parseDirectiveBindings(directive, directive.name);
10104
+ if (isObject(bindings.isolateScope)) {
10105
+ directive.$$isolateBindings = bindings.isolateScope;
10106
+ }
10107
+ }
10108
+ tDirectives.push(directive);
10109
+ match = directive;
10110
+ }
10111
+ }
10112
+ }
10113
+ return match;
10114
+ }
10115
+
10116
+
10117
+ /**
10118
+ * looks up the directive and returns true if it is a multi-element directive,
10119
+ * and therefore requires DOM nodes between -start and -end markers to be grouped
10120
+ * together.
10121
+ *
10122
+ * @param {string} name name of the directive to look up.
10123
+ * @returns true if directive was registered as multi-element.
10124
+ */
10125
+ function directiveIsMultiElement(name) {
10126
+ if (hasDirectives.hasOwnProperty(name)) {
10127
+ for (var directive, directives = $injector.get(name + Suffix),
10128
+ i = 0, ii = directives.length; i < ii; i++) {
10129
+ directive = directives[i];
10130
+ if (directive.multiElement) {
10131
+ return true;
10132
+ }
10133
+ }
10134
+ }
10135
+ return false;
10136
+ }
10137
+
10138
+ /**
10139
+ * When the element is replaced with HTML template then the new attributes
10140
+ * on the template need to be merged with the existing attributes in the DOM.
10141
+ * The desired effect is to have both of the attributes present.
10142
+ *
10143
+ * @param {object} dst destination attributes (original DOM)
10144
+ * @param {object} src source attributes (from the directive template)
10145
+ */
10146
+ function mergeTemplateAttributes(dst, src) {
10147
+ var srcAttr = src.$attr,
10148
+ dstAttr = dst.$attr;
10149
+
10150
+ // reapply the old attributes to the new element
10151
+ forEach(dst, function(value, key) {
10152
+ if (key.charAt(0) !== '$') {
10153
+ if (src[key] && src[key] !== value) {
10154
+ if (value.length) {
10155
+ value += (key === 'style' ? ';' : ' ') + src[key];
10156
+ } else {
10157
+ value = src[key];
10158
+ }
10159
+ }
10160
+ dst.$set(key, value, true, srcAttr[key]);
10161
+ }
10162
+ });
10163
+
10164
+ // copy the new attributes on the old attrs object
10165
+ forEach(src, function(value, key) {
10166
+ // Check if we already set this attribute in the loop above.
10167
+ // `dst` will never contain hasOwnProperty as DOM parser won't let it.
10168
+ // You will get an "InvalidCharacterError: DOM Exception 5" error if you
10169
+ // have an attribute like "has-own-property" or "data-has-own-property", etc.
10170
+ if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') {
10171
+ dst[key] = value;
10172
+
10173
+ if (key !== 'class' && key !== 'style') {
10174
+ dstAttr[key] = srcAttr[key];
10175
+ }
10176
+ }
10177
+ });
10178
+ }
10179
+
10180
+
10181
+ function compileTemplateUrl(directives, $compileNode, tAttrs,
10182
+ $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
10183
+ var linkQueue = [],
10184
+ afterTemplateNodeLinkFn,
10185
+ afterTemplateChildLinkFn,
10186
+ beforeTemplateCompileNode = $compileNode[0],
10187
+ origAsyncDirective = directives.shift(),
10188
+ derivedSyncDirective = inherit(origAsyncDirective, {
10189
+ templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
10190
+ }),
10191
+ templateUrl = (isFunction(origAsyncDirective.templateUrl))
10192
+ ? origAsyncDirective.templateUrl($compileNode, tAttrs)
10193
+ : origAsyncDirective.templateUrl,
10194
+ templateNamespace = origAsyncDirective.templateNamespace;
10195
+
10196
+ $compileNode.empty();
10197
+
10198
+ $templateRequest(templateUrl)
10199
+ .then(function(content) {
10200
+ var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
10201
+
10202
+ content = denormalizeTemplate(content);
10203
+
10204
+ if (origAsyncDirective.replace) {
10205
+ if (jqLiteIsTextNode(content)) {
10206
+ $template = [];
10207
+ } else {
10208
+ $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
10209
+ }
10210
+ compileNode = $template[0];
10211
+
10212
+ if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
10213
+ throw $compileMinErr('tplrt',
10214
+ 'Template for directive \'{0}\' must have exactly one root element. {1}',
10215
+ origAsyncDirective.name, templateUrl);
10216
+ }
10217
+
10218
+ tempTemplateAttrs = {$attr: {}};
10219
+ replaceWith($rootElement, $compileNode, compileNode);
10220
+ var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
10221
+
10222
+ if (isObject(origAsyncDirective.scope)) {
10223
+ // the original directive that caused the template to be loaded async required
10224
+ // an isolate scope
10225
+ markDirectiveScope(templateDirectives, true);
10226
+ }
10227
+ directives = templateDirectives.concat(directives);
10228
+ mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
10229
+ } else {
10230
+ compileNode = beforeTemplateCompileNode;
10231
+ $compileNode.html(content);
10232
+ }
10233
+
10234
+ directives.unshift(derivedSyncDirective);
10235
+
10236
+ afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
10237
+ childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
10238
+ previousCompileContext);
10239
+ forEach($rootElement, function(node, i) {
10240
+ if (node === compileNode) {
10241
+ $rootElement[i] = $compileNode[0];
10242
+ }
10243
+ });
10244
+ afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
10245
+
10246
+ while (linkQueue.length) {
10247
+ var scope = linkQueue.shift(),
10248
+ beforeTemplateLinkNode = linkQueue.shift(),
10249
+ linkRootElement = linkQueue.shift(),
10250
+ boundTranscludeFn = linkQueue.shift(),
10251
+ linkNode = $compileNode[0];
10252
+
10253
+ if (scope.$$destroyed) continue;
10254
+
10255
+ if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
10256
+ var oldClasses = beforeTemplateLinkNode.className;
10257
+
10258
+ if (!(previousCompileContext.hasElementTranscludeDirective &&
10259
+ origAsyncDirective.replace)) {
10260
+ // it was cloned therefore we have to clone as well.
10261
+ linkNode = jqLiteClone(compileNode);
10262
+ }
10263
+ replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
10264
+
10265
+ // Copy in CSS classes from original node
10266
+ safeAddClass(jqLite(linkNode), oldClasses);
10267
+ }
10268
+ if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
10269
+ childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
10270
+ } else {
10271
+ childBoundTranscludeFn = boundTranscludeFn;
10272
+ }
10273
+ afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
10274
+ childBoundTranscludeFn);
10275
+ }
10276
+ linkQueue = null;
10277
+ }).catch(function(error) {
10278
+ if (isError(error)) {
10279
+ $exceptionHandler(error);
10280
+ }
10281
+ });
10282
+
10283
+ return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
10284
+ var childBoundTranscludeFn = boundTranscludeFn;
10285
+ if (scope.$$destroyed) return;
10286
+ if (linkQueue) {
10287
+ linkQueue.push(scope,
10288
+ node,
10289
+ rootElement,
10290
+ childBoundTranscludeFn);
10291
+ } else {
10292
+ if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
10293
+ childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
10294
+ }
10295
+ afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
10296
+ }
10297
+ };
10298
+ }
10299
+
10300
+
10301
+ /**
10302
+ * Sorting function for bound directives.
10303
+ */
10304
+ function byPriority(a, b) {
10305
+ var diff = b.priority - a.priority;
10306
+ if (diff !== 0) return diff;
10307
+ if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
10308
+ return a.index - b.index;
10309
+ }
10310
+
10311
+ function assertNoDuplicate(what, previousDirective, directive, element) {
10312
+
10313
+ function wrapModuleNameIfDefined(moduleName) {
10314
+ return moduleName ?
10315
+ (' (module: ' + moduleName + ')') :
10316
+ '';
10317
+ }
10318
+
10319
+ if (previousDirective) {
10320
+ throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
10321
+ previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
10322
+ directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
10323
+ }
10324
+ }
10325
+
10326
+
10327
+ function addTextInterpolateDirective(directives, text) {
10328
+ var interpolateFn = $interpolate(text, true);
10329
+ if (interpolateFn) {
10330
+ directives.push({
10331
+ priority: 0,
10332
+ compile: function textInterpolateCompileFn(templateNode) {
10333
+ var templateNodeParent = templateNode.parent(),
10334
+ hasCompileParent = !!templateNodeParent.length;
10335
+
10336
+ // When transcluding a template that has bindings in the root
10337
+ // we don't have a parent and thus need to add the class during linking fn.
10338
+ if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
10339
+
10340
+ return function textInterpolateLinkFn(scope, node) {
10341
+ var parent = node.parent();
10342
+ if (!hasCompileParent) compile.$$addBindingClass(parent);
10343
+ compile.$$addBindingInfo(parent, interpolateFn.expressions);
10344
+ scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
10345
+ node[0].nodeValue = value;
10346
+ });
10347
+ };
10348
+ }
10349
+ });
10350
+ }
10351
+ }
10352
+
10353
+
10354
+ function wrapTemplate(type, template) {
10355
+ type = lowercase(type || 'html');
10356
+ switch (type) {
10357
+ case 'svg':
10358
+ case 'math':
10359
+ var wrapper = window.document.createElement('div');
10360
+ wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
10361
+ return wrapper.childNodes[0].childNodes;
10362
+ default:
10363
+ return template;
10364
+ }
10365
+ }
10366
+
10367
+
10368
+ function getTrustedContext(node, attrNormalizedName) {
10369
+ if (attrNormalizedName === 'srcdoc') {
10370
+ return $sce.HTML;
10371
+ }
10372
+ var tag = nodeName_(node);
10373
+ // All tags with src attributes require a RESOURCE_URL value, except for
10374
+ // img and various html5 media tags.
10375
+ if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') {
10376
+ if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) {
10377
+ return $sce.RESOURCE_URL;
10378
+ }
10379
+ // maction[xlink:href] can source SVG. It's not limited to <maction>.
10380
+ } else if (attrNormalizedName === 'xlinkHref' ||
10381
+ (tag === 'form' && attrNormalizedName === 'action') ||
10382
+ // links can be stylesheets or imports, which can run script in the current origin
10383
+ (tag === 'link' && attrNormalizedName === 'href')
10384
+ ) {
10385
+ return $sce.RESOURCE_URL;
10386
+ }
10387
+ }
10388
+
10389
+
10390
+ function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) {
10391
+ var trustedContext = getTrustedContext(node, name);
10392
+ var mustHaveExpression = !isNgAttr;
10393
+ var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr;
10394
+
10395
+ var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing);
10396
+
10397
+ // no interpolation found -> ignore
10398
+ if (!interpolateFn) return;
10399
+
10400
+ if (name === 'multiple' && nodeName_(node) === 'select') {
10401
+ throw $compileMinErr('selmulti',
10402
+ 'Binding to the \'multiple\' attribute is not supported. Element: {0}',
10403
+ startingTag(node));
10404
+ }
10405
+
10406
+ if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
10407
+ throw $compileMinErr('nodomevents',
10408
+ 'Interpolations for HTML DOM event attributes are disallowed. Please use the ' +
10409
+ 'ng- versions (such as ng-click instead of onclick) instead.');
10410
+ }
10411
+
10412
+ directives.push({
10413
+ priority: 100,
10414
+ compile: function() {
10415
+ return {
10416
+ pre: function attrInterpolatePreLinkFn(scope, element, attr) {
10417
+ var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
10418
+
10419
+ // If the attribute has changed since last $interpolate()ed
10420
+ var newValue = attr[name];
10421
+ if (newValue !== value) {
10422
+ // we need to interpolate again since the attribute value has been updated
10423
+ // (e.g. by another directive's compile function)
10424
+ // ensure unset/empty values make interpolateFn falsy
10425
+ interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
10426
+ value = newValue;
10427
+ }
10428
+
10429
+ // if attribute was updated so that there is no interpolation going on we don't want to
10430
+ // register any observers
10431
+ if (!interpolateFn) return;
10432
+
10433
+ // initialize attr object so that it's ready in case we need the value for isolate
10434
+ // scope initialization, otherwise the value would not be available from isolate
10435
+ // directive's linking fn during linking phase
10436
+ attr[name] = interpolateFn(scope);
10437
+
10438
+ ($$observers[name] || ($$observers[name] = [])).$$inter = true;
10439
+ (attr.$$observers && attr.$$observers[name].$$scope || scope).
10440
+ $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
10441
+ //special case for class attribute addition + removal
10442
+ //so that class changes can tap into the animation
10443
+ //hooks provided by the $animate service. Be sure to
10444
+ //skip animations when the first digest occurs (when
10445
+ //both the new and the old values are the same) since
10446
+ //the CSS classes are the non-interpolated values
10447
+ if (name === 'class' && newValue !== oldValue) {
10448
+ attr.$updateClass(newValue, oldValue);
10449
+ } else {
10450
+ attr.$set(name, newValue);
10451
+ }
10452
+ });
10453
+ }
10454
+ };
10455
+ }
10456
+ });
10457
+ }
10458
+
10459
+
10460
+ /**
10461
+ * This is a special jqLite.replaceWith, which can replace items which
10462
+ * have no parents, provided that the containing jqLite collection is provided.
10463
+ *
10464
+ * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
10465
+ * in the root of the tree.
10466
+ * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
10467
+ * the shell, but replace its DOM node reference.
10468
+ * @param {Node} newNode The new DOM node.
10469
+ */
10470
+ function replaceWith($rootElement, elementsToRemove, newNode) {
10471
+ var firstElementToRemove = elementsToRemove[0],
10472
+ removeCount = elementsToRemove.length,
10473
+ parent = firstElementToRemove.parentNode,
10474
+ i, ii;
10475
+
10476
+ if ($rootElement) {
10477
+ for (i = 0, ii = $rootElement.length; i < ii; i++) {
10478
+ if ($rootElement[i] === firstElementToRemove) {
10479
+ $rootElement[i++] = newNode;
10480
+ for (var j = i, j2 = j + removeCount - 1,
10481
+ jj = $rootElement.length;
10482
+ j < jj; j++, j2++) {
10483
+ if (j2 < jj) {
10484
+ $rootElement[j] = $rootElement[j2];
10485
+ } else {
10486
+ delete $rootElement[j];
10487
+ }
10488
+ }
10489
+ $rootElement.length -= removeCount - 1;
10490
+
10491
+ // If the replaced element is also the jQuery .context then replace it
10492
+ // .context is a deprecated jQuery api, so we should set it only when jQuery set it
10493
+ // http://api.jquery.com/context/
10494
+ if ($rootElement.context === firstElementToRemove) {
10495
+ $rootElement.context = newNode;
10496
+ }
10497
+ break;
10498
+ }
10499
+ }
10500
+ }
10501
+
10502
+ if (parent) {
10503
+ parent.replaceChild(newNode, firstElementToRemove);
10504
+ }
10505
+
10506
+ // Append all the `elementsToRemove` to a fragment. This will...
10507
+ // - remove them from the DOM
10508
+ // - allow them to still be traversed with .nextSibling
10509
+ // - allow a single fragment.qSA to fetch all elements being removed
10510
+ var fragment = window.document.createDocumentFragment();
10511
+ for (i = 0; i < removeCount; i++) {
10512
+ fragment.appendChild(elementsToRemove[i]);
10513
+ }
10514
+
10515
+ if (jqLite.hasData(firstElementToRemove)) {
10516
+ // Copy over user data (that includes Angular's $scope etc.). Don't copy private
10517
+ // data here because there's no public interface in jQuery to do that and copying over
10518
+ // event listeners (which is the main use of private data) wouldn't work anyway.
10519
+ jqLite.data(newNode, jqLite.data(firstElementToRemove));
10520
+
10521
+ // Remove $destroy event listeners from `firstElementToRemove`
10522
+ jqLite(firstElementToRemove).off('$destroy');
10523
+ }
10524
+
10525
+ // Cleanup any data/listeners on the elements and children.
10526
+ // This includes invoking the $destroy event on any elements with listeners.
10527
+ jqLite.cleanData(fragment.querySelectorAll('*'));
10528
+
10529
+ // Update the jqLite collection to only contain the `newNode`
10530
+ for (i = 1; i < removeCount; i++) {
10531
+ delete elementsToRemove[i];
10532
+ }
10533
+ elementsToRemove[0] = newNode;
10534
+ elementsToRemove.length = 1;
10535
+ }
10536
+
10537
+
10538
+ function cloneAndAnnotateFn(fn, annotation) {
10539
+ return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
10540
+ }
10541
+
10542
+
10543
+ function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
10544
+ try {
10545
+ linkFn(scope, $element, attrs, controllers, transcludeFn);
10546
+ } catch (e) {
10547
+ $exceptionHandler(e, startingTag($element));
10548
+ }
10549
+ }
10550
+
10551
+ function strictBindingsCheck(attrName, directiveName) {
10552
+ if (strictComponentBindingsEnabled) {
10553
+ throw $compileMinErr('missingattr',
10554
+ 'Attribute \'{0}\' of \'{1}\' is non-optional and must be set!',
10555
+ attrName, directiveName);
10556
+ }
10557
+ }
10558
+
10559
+ // Set up $watches for isolate scope and controller bindings.
10560
+ function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
10561
+ var removeWatchCollection = [];
10562
+ var initialChanges = {};
10563
+ var changes;
10564
+
10565
+ forEach(bindings, function initializeBinding(definition, scopeName) {
10566
+ var attrName = definition.attrName,
10567
+ optional = definition.optional,
10568
+ mode = definition.mode, // @, =, <, or &
10569
+ lastValue,
10570
+ parentGet, parentSet, compare, removeWatch;
10571
+
10572
+ switch (mode) {
10573
+
10574
+ case '@':
10575
+ if (!optional && !hasOwnProperty.call(attrs, attrName)) {
10576
+ strictBindingsCheck(attrName, directive.name);
10577
+ destination[scopeName] = attrs[attrName] = undefined;
10578
+
10579
+ }
10580
+ removeWatch = attrs.$observe(attrName, function(value) {
10581
+ if (isString(value) || isBoolean(value)) {
10582
+ var oldValue = destination[scopeName];
10583
+ recordChanges(scopeName, value, oldValue);
10584
+ destination[scopeName] = value;
10585
+ }
10586
+ });
10587
+ attrs.$$observers[attrName].$$scope = scope;
10588
+ lastValue = attrs[attrName];
10589
+ if (isString(lastValue)) {
10590
+ // If the attribute has been provided then we trigger an interpolation to ensure
10591
+ // the value is there for use in the link fn
10592
+ destination[scopeName] = $interpolate(lastValue)(scope);
10593
+ } else if (isBoolean(lastValue)) {
10594
+ // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted
10595
+ // the value to boolean rather than a string, so we special case this situation
10596
+ destination[scopeName] = lastValue;
10597
+ }
10598
+ initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
10599
+ removeWatchCollection.push(removeWatch);
10600
+ break;
10601
+
10602
+ case '=':
10603
+ if (!hasOwnProperty.call(attrs, attrName)) {
10604
+ if (optional) break;
10605
+ strictBindingsCheck(attrName, directive.name);
10606
+ attrs[attrName] = undefined;
10607
+ }
10608
+ if (optional && !attrs[attrName]) break;
10609
+
10610
+ parentGet = $parse(attrs[attrName]);
10611
+ if (parentGet.literal) {
10612
+ compare = equals;
10613
+ } else {
10614
+ compare = simpleCompare;
10615
+ }
10616
+ parentSet = parentGet.assign || function() {
10617
+ // reset the change, or we will throw this exception on every $digest
10618
+ lastValue = destination[scopeName] = parentGet(scope);
10619
+ throw $compileMinErr('nonassign',
10620
+ 'Expression \'{0}\' in attribute \'{1}\' used with directive \'{2}\' is non-assignable!',
10621
+ attrs[attrName], attrName, directive.name);
10622
+ };
10623
+ lastValue = destination[scopeName] = parentGet(scope);
10624
+ var parentValueWatch = function parentValueWatch(parentValue) {
10625
+ if (!compare(parentValue, destination[scopeName])) {
10626
+ // we are out of sync and need to copy
10627
+ if (!compare(parentValue, lastValue)) {
10628
+ // parent changed and it has precedence
10629
+ destination[scopeName] = parentValue;
10630
+ } else {
10631
+ // if the parent can be assigned then do so
10632
+ parentSet(scope, parentValue = destination[scopeName]);
10633
+ }
10634
+ }
10635
+ lastValue = parentValue;
10636
+ return lastValue;
10637
+ };
10638
+ parentValueWatch.$stateful = true;
10639
+ if (definition.collection) {
10640
+ removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
10641
+ } else {
10642
+ removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
10643
+ }
10644
+ removeWatchCollection.push(removeWatch);
10645
+ break;
10646
+
10647
+ case '<':
10648
+ if (!hasOwnProperty.call(attrs, attrName)) {
10649
+ if (optional) break;
10650
+ strictBindingsCheck(attrName, directive.name);
10651
+ attrs[attrName] = undefined;
10652
+ }
10653
+ if (optional && !attrs[attrName]) break;
10654
+
10655
+ parentGet = $parse(attrs[attrName]);
10656
+ var deepWatch = parentGet.literal;
10657
+
10658
+ var initialValue = destination[scopeName] = parentGet(scope);
10659
+ initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
10660
+
10661
+ removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) {
10662
+ if (oldValue === newValue) {
10663
+ if (oldValue === initialValue || (deepWatch && equals(oldValue, initialValue))) {
10664
+ return;
10665
+ }
10666
+ oldValue = initialValue;
10667
+ }
10668
+ recordChanges(scopeName, newValue, oldValue);
10669
+ destination[scopeName] = newValue;
10670
+ }, deepWatch);
10671
+
10672
+ removeWatchCollection.push(removeWatch);
10673
+ break;
10674
+
10675
+ case '&':
10676
+ if (!optional && !hasOwnProperty.call(attrs, attrName)) {
10677
+ strictBindingsCheck(attrName, directive.name);
10678
+ }
10679
+ // Don't assign Object.prototype method to scope
10680
+ parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
10681
+
10682
+ // Don't assign noop to destination if expression is not valid
10683
+ if (parentGet === noop && optional) break;
10684
+
10685
+ destination[scopeName] = function(locals) {
10686
+ return parentGet(scope, locals);
10687
+ };
10688
+ break;
10689
+ }
10690
+ });
10691
+
10692
+ function recordChanges(key, currentValue, previousValue) {
10693
+ if (isFunction(destination.$onChanges) && !simpleCompare(currentValue, previousValue)) {
10694
+ // If we have not already scheduled the top level onChangesQueue handler then do so now
10695
+ if (!onChangesQueue) {
10696
+ scope.$$postDigest(flushOnChangesQueue);
10697
+ onChangesQueue = [];
10698
+ }
10699
+ // If we have not already queued a trigger of onChanges for this controller then do so now
10700
+ if (!changes) {
10701
+ changes = {};
10702
+ onChangesQueue.push(triggerOnChangesHook);
10703
+ }
10704
+ // If the has been a change on this property already then we need to reuse the previous value
10705
+ if (changes[key]) {
10706
+ previousValue = changes[key].previousValue;
10707
+ }
10708
+ // Store this change
10709
+ changes[key] = new SimpleChange(previousValue, currentValue);
10710
+ }
10711
+ }
10712
+
10713
+ function triggerOnChangesHook() {
10714
+ destination.$onChanges(changes);
10715
+ // Now clear the changes so that we schedule onChanges when more changes arrive
10716
+ changes = undefined;
10717
+ }
10718
+
10719
+ return {
10720
+ initialChanges: initialChanges,
10721
+ removeWatches: removeWatchCollection.length && function removeWatches() {
10722
+ for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
10723
+ removeWatchCollection[i]();
10724
+ }
10725
+ }
10726
+ };
10727
+ }
10728
+ }];
10729
+ }
10730
+
10731
+ function SimpleChange(previous, current) {
10732
+ this.previousValue = previous;
10733
+ this.currentValue = current;
10734
+ }
10735
+ SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; };
10736
+
10737
+
10738
+ var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i;
10739
+ var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g;
10740
+
10741
+ /**
10742
+ * Converts all accepted directives format into proper directive name.
10743
+ * @param name Name to normalize
10744
+ */
10745
+ function directiveNormalize(name) {
10746
+ return name
10747
+ .replace(PREFIX_REGEXP, '')
10748
+ .replace(SPECIAL_CHARS_REGEXP, fnCamelCaseReplace);
10749
+ }
10750
+
10751
+ /**
10752
+ * @ngdoc type
10753
+ * @name $compile.directive.Attributes
10754
+ *
10755
+ * @description
10756
+ * A shared object between directive compile / linking functions which contains normalized DOM
10757
+ * element attributes. The values reflect current binding state `{{ }}`. The normalization is
10758
+ * needed since all of these are treated as equivalent in Angular:
10759
+ *
10760
+ * ```
10761
+ * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
10762
+ * ```
10763
+ */
10764
+
10765
+ /**
10766
+ * @ngdoc property
10767
+ * @name $compile.directive.Attributes#$attr
10768
+ *
10769
+ * @description
10770
+ * A map of DOM element attribute names to the normalized name. This is
10771
+ * needed to do reverse lookup from normalized name back to actual name.
10772
+ */
10773
+
10774
+
10775
+ /**
10776
+ * @ngdoc method
10777
+ * @name $compile.directive.Attributes#$set
10778
+ * @kind function
10779
+ *
10780
+ * @description
10781
+ * Set DOM element attribute value.
10782
+ *
10783
+ *
10784
+ * @param {string} name Normalized element attribute name of the property to modify. The name is
10785
+ * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
10786
+ * property to the original name.
10787
+ * @param {string} value Value to set the attribute to. The value can be an interpolated string.
10788
+ */
10789
+
10790
+
10791
+
10792
+ /**
10793
+ * Closure compiler type information
10794
+ */
10795
+
10796
+ function nodesetLinkingFn(
10797
+ /* angular.Scope */ scope,
10798
+ /* NodeList */ nodeList,
10799
+ /* Element */ rootElement,
10800
+ /* function(Function) */ boundTranscludeFn
10801
+ ) {}
10802
+
10803
+ function directiveLinkingFn(
10804
+ /* nodesetLinkingFn */ nodesetLinkingFn,
10805
+ /* angular.Scope */ scope,
10806
+ /* Node */ node,
10807
+ /* Element */ rootElement,
10808
+ /* function(Function) */ boundTranscludeFn
10809
+ ) {}
10810
+
10811
+ function tokenDifference(str1, str2) {
10812
+ var values = '',
10813
+ tokens1 = str1.split(/\s+/),
10814
+ tokens2 = str2.split(/\s+/);
10815
+
10816
+ outer:
10817
+ for (var i = 0; i < tokens1.length; i++) {
10818
+ var token = tokens1[i];
10819
+ for (var j = 0; j < tokens2.length; j++) {
10820
+ if (token === tokens2[j]) continue outer;
10821
+ }
10822
+ values += (values.length > 0 ? ' ' : '') + token;
10823
+ }
10824
+ return values;
10825
+ }
10826
+
10827
+ function removeComments(jqNodes) {
10828
+ jqNodes = jqLite(jqNodes);
10829
+ var i = jqNodes.length;
10830
+
10831
+ if (i <= 1) {
10832
+ return jqNodes;
10833
+ }
10834
+
10835
+ while (i--) {
10836
+ var node = jqNodes[i];
10837
+ if (node.nodeType === NODE_TYPE_COMMENT ||
10838
+ (node.nodeType === NODE_TYPE_TEXT && node.nodeValue.trim() === '')) {
10839
+ splice.call(jqNodes, i, 1);
10840
+ }
10841
+ }
10842
+ return jqNodes;
10843
+ }
10844
+
10845
+ var $controllerMinErr = minErr('$controller');
10846
+
10847
+
10848
+ var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/;
10849
+ function identifierForController(controller, ident) {
10850
+ if (ident && isString(ident)) return ident;
10851
+ if (isString(controller)) {
10852
+ var match = CNTRL_REG.exec(controller);
10853
+ if (match) return match[3];
10854
+ }
10855
+ }
10856
+
10857
+
10858
+ /**
10859
+ * @ngdoc provider
10860
+ * @name $controllerProvider
10861
+ * @this
10862
+ *
10863
+ * @description
10864
+ * The {@link ng.$controller $controller service} is used by Angular to create new
10865
+ * controllers.
10866
+ *
10867
+ * This provider allows controller registration via the
10868
+ * {@link ng.$controllerProvider#register register} method.
10869
+ */
10870
+ function $ControllerProvider() {
10871
+ var controllers = {},
10872
+ globals = false;
10873
+
10874
+ /**
10875
+ * @ngdoc method
10876
+ * @name $controllerProvider#has
10877
+ * @param {string} name Controller name to check.
10878
+ */
10879
+ this.has = function(name) {
10880
+ return controllers.hasOwnProperty(name);
10881
+ };
10882
+
10883
+ /**
10884
+ * @ngdoc method
10885
+ * @name $controllerProvider#register
10886
+ * @param {string|Object} name Controller name, or an object map of controllers where the keys are
10887
+ * the names and the values are the constructors.
10888
+ * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
10889
+ * annotations in the array notation).
10890
+ */
10891
+ this.register = function(name, constructor) {
10892
+ assertNotHasOwnProperty(name, 'controller');
10893
+ if (isObject(name)) {
10894
+ extend(controllers, name);
10895
+ } else {
10896
+ controllers[name] = constructor;
10897
+ }
10898
+ };
10899
+
10900
+ /**
10901
+ * @ngdoc method
10902
+ * @name $controllerProvider#allowGlobals
10903
+ * @description If called, allows `$controller` to find controller constructors on `window`
10904
+ *
10905
+ * @deprecated
10906
+ * sinceVersion="v1.3.0"
10907
+ * removeVersion="v1.7.0"
10908
+ * This method of finding controllers has been deprecated.
10909
+ */
10910
+ this.allowGlobals = function() {
10911
+ globals = true;
10912
+ };
10913
+
10914
+
10915
+ this.$get = ['$injector', '$window', function($injector, $window) {
10916
+
10917
+ /**
10918
+ * @ngdoc service
10919
+ * @name $controller
10920
+ * @requires $injector
10921
+ *
10922
+ * @param {Function|string} constructor If called with a function then it's considered to be the
10923
+ * controller constructor function. Otherwise it's considered to be a string which is used
10924
+ * to retrieve the controller constructor using the following steps:
10925
+ *
10926
+ * * check if a controller with given name is registered via `$controllerProvider`
10927
+ * * check if evaluating the string on the current scope returns a constructor
10928
+ * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
10929
+ * `window` object (deprecated, not recommended)
10930
+ *
10931
+ * The string can use the `controller as property` syntax, where the controller instance is published
10932
+ * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
10933
+ * to work correctly.
10934
+ *
10935
+ * @param {Object} locals Injection locals for Controller.
10936
+ * @return {Object} Instance of given controller.
10937
+ *
10938
+ * @description
10939
+ * `$controller` service is responsible for instantiating controllers.
10940
+ *
10941
+ * It's just a simple call to {@link auto.$injector $injector}, but extracted into
10942
+ * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
10943
+ */
10944
+ return function $controller(expression, locals, later, ident) {
10945
+ // PRIVATE API:
10946
+ // param `later` --- indicates that the controller's constructor is invoked at a later time.
10947
+ // If true, $controller will allocate the object with the correct
10948
+ // prototype chain, but will not invoke the controller until a returned
10949
+ // callback is invoked.
10950
+ // param `ident` --- An optional label which overrides the label parsed from the controller
10951
+ // expression, if any.
10952
+ var instance, match, constructor, identifier;
10953
+ later = later === true;
10954
+ if (ident && isString(ident)) {
10955
+ identifier = ident;
10956
+ }
10957
+
10958
+ if (isString(expression)) {
10959
+ match = expression.match(CNTRL_REG);
10960
+ if (!match) {
10961
+ throw $controllerMinErr('ctrlfmt',
10962
+ 'Badly formed controller string \'{0}\'. ' +
10963
+ 'Must match `__name__ as __id__` or `__name__`.', expression);
10964
+ }
10965
+ constructor = match[1];
10966
+ identifier = identifier || match[3];
10967
+ expression = controllers.hasOwnProperty(constructor)
10968
+ ? controllers[constructor]
10969
+ : getter(locals.$scope, constructor, true) ||
10970
+ (globals ? getter($window, constructor, true) : undefined);
10971
+
10972
+ if (!expression) {
10973
+ throw $controllerMinErr('ctrlreg',
10974
+ 'The controller with the name \'{0}\' is not registered.', constructor);
10975
+ }
10976
+
10977
+ assertArgFn(expression, constructor, true);
10978
+ }
10979
+
10980
+ if (later) {
10981
+ // Instantiate controller later:
10982
+ // This machinery is used to create an instance of the object before calling the
10983
+ // controller's constructor itself.
10984
+ //
10985
+ // This allows properties to be added to the controller before the constructor is
10986
+ // invoked. Primarily, this is used for isolate scope bindings in $compile.
10987
+ //
10988
+ // This feature is not intended for use by applications, and is thus not documented
10989
+ // publicly.
10990
+ // Object creation: http://jsperf.com/create-constructor/2
10991
+ var controllerPrototype = (isArray(expression) ?
10992
+ expression[expression.length - 1] : expression).prototype;
10993
+ instance = Object.create(controllerPrototype || null);
10994
+
10995
+ if (identifier) {
10996
+ addIdentifier(locals, identifier, instance, constructor || expression.name);
10997
+ }
10998
+
10999
+ return extend(function $controllerInit() {
11000
+ var result = $injector.invoke(expression, instance, locals, constructor);
11001
+ if (result !== instance && (isObject(result) || isFunction(result))) {
11002
+ instance = result;
11003
+ if (identifier) {
11004
+ // If result changed, re-assign controllerAs value to scope.
11005
+ addIdentifier(locals, identifier, instance, constructor || expression.name);
11006
+ }
11007
+ }
11008
+ return instance;
11009
+ }, {
11010
+ instance: instance,
11011
+ identifier: identifier
11012
+ });
11013
+ }
11014
+
11015
+ instance = $injector.instantiate(expression, locals, constructor);
11016
+
11017
+ if (identifier) {
11018
+ addIdentifier(locals, identifier, instance, constructor || expression.name);
11019
+ }
11020
+
11021
+ return instance;
11022
+ };
11023
+
11024
+ function addIdentifier(locals, identifier, instance, name) {
11025
+ if (!(locals && isObject(locals.$scope))) {
11026
+ throw minErr('$controller')('noscp',
11027
+ 'Cannot export controller \'{0}\' as \'{1}\'! No $scope object provided via `locals`.',
11028
+ name, identifier);
11029
+ }
11030
+
11031
+ locals.$scope[identifier] = instance;
11032
+ }
11033
+ }];
11034
+ }
11035
+
11036
+ /**
11037
+ * @ngdoc service
11038
+ * @name $document
11039
+ * @requires $window
11040
+ * @this
11041
+ *
11042
+ * @description
11043
+ * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
11044
+ *
11045
+ * @example
11046
+ <example module="documentExample" name="document">
11047
+ <file name="index.html">
11048
+ <div ng-controller="ExampleController">
11049
+ <p>$document title: <b ng-bind="title"></b></p>
11050
+ <p>window.document title: <b ng-bind="windowTitle"></b></p>
11051
+ </div>
11052
+ </file>
11053
+ <file name="script.js">
11054
+ angular.module('documentExample', [])
11055
+ .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
11056
+ $scope.title = $document[0].title;
11057
+ $scope.windowTitle = angular.element(window.document)[0].title;
11058
+ }]);
11059
+ </file>
11060
+ </example>
11061
+ */
11062
+ function $DocumentProvider() {
11063
+ this.$get = ['$window', function(window) {
11064
+ return jqLite(window.document);
11065
+ }];
11066
+ }
11067
+
11068
+
11069
+ /**
11070
+ * @private
11071
+ * @this
11072
+ * Listens for document visibility change and makes the current status accessible.
11073
+ */
11074
+ function $$IsDocumentHiddenProvider() {
11075
+ this.$get = ['$document', '$rootScope', function($document, $rootScope) {
11076
+ var doc = $document[0];
11077
+ var hidden = doc && doc.hidden;
11078
+
11079
+ $document.on('visibilitychange', changeListener);
11080
+
11081
+ $rootScope.$on('$destroy', function() {
11082
+ $document.off('visibilitychange', changeListener);
11083
+ });
11084
+
11085
+ function changeListener() {
11086
+ hidden = doc.hidden;
11087
+ }
11088
+
11089
+ return function() {
11090
+ return hidden;
11091
+ };
11092
+ }];
11093
+ }
11094
+
11095
+ /**
11096
+ * @ngdoc service
11097
+ * @name $exceptionHandler
11098
+ * @requires ng.$log
11099
+ * @this
11100
+ *
11101
+ * @description
11102
+ * Any uncaught exception in angular expressions is delegated to this service.
11103
+ * The default implementation simply delegates to `$log.error` which logs it into
11104
+ * the browser console.
11105
+ *
11106
+ * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
11107
+ * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
11108
+ *
11109
+ * ## Example:
11110
+ *
11111
+ * The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught
11112
+ * errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead
11113
+ * of `$log.error()`.
11114
+ *
11115
+ * ```js
11116
+ * angular.
11117
+ * module('exceptionOverwrite', []).
11118
+ * factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) {
11119
+ * return function myExceptionHandler(exception, cause) {
11120
+ * logErrorsToBackend(exception, cause);
11121
+ * $log.warn(exception, cause);
11122
+ * };
11123
+ * }]);
11124
+ * ```
11125
+ *
11126
+ * <hr />
11127
+ * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
11128
+ * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
11129
+ * (unless executed during a digest).
11130
+ *
11131
+ * If you wish, you can manually delegate exceptions, e.g.
11132
+ * `try { ... } catch(e) { $exceptionHandler(e); }`
11133
+ *
11134
+ * @param {Error} exception Exception associated with the error.
11135
+ * @param {string=} cause Optional information about the context in which
11136
+ * the error was thrown.
11137
+ *
11138
+ */
11139
+ function $ExceptionHandlerProvider() {
11140
+ this.$get = ['$log', function($log) {
11141
+ return function(exception, cause) {
11142
+ $log.error.apply($log, arguments);
11143
+ };
11144
+ }];
11145
+ }
11146
+
11147
+ var $$ForceReflowProvider = /** @this */ function() {
11148
+ this.$get = ['$document', function($document) {
11149
+ return function(domNode) {
11150
+ //the line below will force the browser to perform a repaint so
11151
+ //that all the animated elements within the animation frame will
11152
+ //be properly updated and drawn on screen. This is required to
11153
+ //ensure that the preparation animation is properly flushed so that
11154
+ //the active state picks up from there. DO NOT REMOVE THIS LINE.
11155
+ //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
11156
+ //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
11157
+ //WILL TAKE YEARS AWAY FROM YOUR LIFE.
11158
+ if (domNode) {
11159
+ if (!domNode.nodeType && domNode instanceof jqLite) {
11160
+ domNode = domNode[0];
11161
+ }
11162
+ } else {
11163
+ domNode = $document[0].body;
11164
+ }
11165
+ return domNode.offsetWidth + 1;
11166
+ };
11167
+ }];
11168
+ };
11169
+
11170
+ var APPLICATION_JSON = 'application/json';
11171
+ var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
11172
+ var JSON_START = /^\[|^\{(?!\{)/;
11173
+ var JSON_ENDS = {
11174
+ '[': /]$/,
11175
+ '{': /}$/
11176
+ };
11177
+ var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/;
11178
+ var $httpMinErr = minErr('$http');
11179
+
11180
+ function serializeValue(v) {
11181
+ if (isObject(v)) {
11182
+ return isDate(v) ? v.toISOString() : toJson(v);
11183
+ }
11184
+ return v;
11185
+ }
11186
+
11187
+
11188
+ /** @this */
11189
+ function $HttpParamSerializerProvider() {
11190
+ /**
11191
+ * @ngdoc service
11192
+ * @name $httpParamSerializer
11193
+ * @description
11194
+ *
11195
+ * Default {@link $http `$http`} params serializer that converts objects to strings
11196
+ * according to the following rules:
11197
+ *
11198
+ * * `{'foo': 'bar'}` results in `foo=bar`
11199
+ * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
11200
+ * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
11201
+ * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object)
11202
+ *
11203
+ * Note that serializer will sort the request parameters alphabetically.
11204
+ * */
11205
+
11206
+ this.$get = function() {
11207
+ return function ngParamSerializer(params) {
11208
+ if (!params) return '';
11209
+ var parts = [];
11210
+ forEachSorted(params, function(value, key) {
11211
+ if (value === null || isUndefined(value) || isFunction(value)) return;
11212
+ if (isArray(value)) {
11213
+ forEach(value, function(v) {
11214
+ parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
11215
+ });
11216
+ } else {
11217
+ parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
11218
+ }
11219
+ });
11220
+
11221
+ return parts.join('&');
11222
+ };
11223
+ };
11224
+ }
11225
+
11226
+ /** @this */
11227
+ function $HttpParamSerializerJQLikeProvider() {
11228
+ /**
11229
+ * @ngdoc service
11230
+ * @name $httpParamSerializerJQLike
11231
+ *
11232
+ * @description
11233
+ *
11234
+ * Alternative {@link $http `$http`} params serializer that follows
11235
+ * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
11236
+ * The serializer will also sort the params alphabetically.
11237
+ *
11238
+ * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
11239
+ *
11240
+ * ```js
11241
+ * $http({
11242
+ * url: myUrl,
11243
+ * method: 'GET',
11244
+ * params: myParams,
11245
+ * paramSerializer: '$httpParamSerializerJQLike'
11246
+ * });
11247
+ * ```
11248
+ *
11249
+ * It is also possible to set it as the default `paramSerializer` in the
11250
+ * {@link $httpProvider#defaults `$httpProvider`}.
11251
+ *
11252
+ * Additionally, you can inject the serializer and use it explicitly, for example to serialize
11253
+ * form data for submission:
11254
+ *
11255
+ * ```js
11256
+ * .controller(function($http, $httpParamSerializerJQLike) {
11257
+ * //...
11258
+ *
11259
+ * $http({
11260
+ * url: myUrl,
11261
+ * method: 'POST',
11262
+ * data: $httpParamSerializerJQLike(myData),
11263
+ * headers: {
11264
+ * 'Content-Type': 'application/x-www-form-urlencoded'
11265
+ * }
11266
+ * });
11267
+ *
11268
+ * });
11269
+ * ```
11270
+ *
11271
+ * */
11272
+ this.$get = function() {
11273
+ return function jQueryLikeParamSerializer(params) {
11274
+ if (!params) return '';
11275
+ var parts = [];
11276
+ serialize(params, '', true);
11277
+ return parts.join('&');
11278
+
11279
+ function serialize(toSerialize, prefix, topLevel) {
11280
+ if (toSerialize === null || isUndefined(toSerialize)) return;
11281
+ if (isArray(toSerialize)) {
11282
+ forEach(toSerialize, function(value, index) {
11283
+ serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
11284
+ });
11285
+ } else if (isObject(toSerialize) && !isDate(toSerialize)) {
11286
+ forEachSorted(toSerialize, function(value, key) {
11287
+ serialize(value, prefix +
11288
+ (topLevel ? '' : '[') +
11289
+ key +
11290
+ (topLevel ? '' : ']'));
11291
+ });
11292
+ } else {
11293
+ parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
11294
+ }
11295
+ }
11296
+ };
11297
+ };
11298
+ }
11299
+
11300
+ function defaultHttpResponseTransform(data, headers) {
11301
+ if (isString(data)) {
11302
+ // Strip json vulnerability protection prefix and trim whitespace
11303
+ var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
11304
+
11305
+ if (tempData) {
11306
+ var contentType = headers('Content-Type');
11307
+ var hasJsonContentType = contentType && (contentType.indexOf(APPLICATION_JSON) === 0);
11308
+
11309
+ if (hasJsonContentType || isJsonLike(tempData)) {
11310
+ try {
11311
+ data = fromJson(tempData);
11312
+ } catch (e) {
11313
+ if (!hasJsonContentType) {
11314
+ return data;
11315
+ }
11316
+ throw $httpMinErr('baddata', 'Data must be a valid JSON object. Received: "{0}". ' +
11317
+ 'Parse error: "{1}"', data, e);
11318
+ }
11319
+ }
11320
+ }
11321
+ }
11322
+
11323
+ return data;
11324
+ }
11325
+
11326
+ function isJsonLike(str) {
11327
+ var jsonStart = str.match(JSON_START);
11328
+ return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
11329
+ }
11330
+
11331
+ /**
11332
+ * Parse headers into key value object
11333
+ *
11334
+ * @param {string} headers Raw headers as a string
11335
+ * @returns {Object} Parsed headers as key value object
11336
+ */
11337
+ function parseHeaders(headers) {
11338
+ var parsed = createMap(), i;
11339
+
11340
+ function fillInParsed(key, val) {
11341
+ if (key) {
11342
+ parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
11343
+ }
11344
+ }
11345
+
11346
+ if (isString(headers)) {
11347
+ forEach(headers.split('\n'), function(line) {
11348
+ i = line.indexOf(':');
11349
+ fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
11350
+ });
11351
+ } else if (isObject(headers)) {
11352
+ forEach(headers, function(headerVal, headerKey) {
11353
+ fillInParsed(lowercase(headerKey), trim(headerVal));
11354
+ });
11355
+ }
11356
+
11357
+ return parsed;
11358
+ }
11359
+
11360
+
11361
+ /**
11362
+ * Returns a function that provides access to parsed headers.
11363
+ *
11364
+ * Headers are lazy parsed when first requested.
11365
+ * @see parseHeaders
11366
+ *
11367
+ * @param {(string|Object)} headers Headers to provide access to.
11368
+ * @returns {function(string=)} Returns a getter function which if called with:
11369
+ *
11370
+ * - if called with an argument returns a single header value or null
11371
+ * - if called with no arguments returns an object containing all headers.
11372
+ */
11373
+ function headersGetter(headers) {
11374
+ var headersObj;
11375
+
11376
+ return function(name) {
11377
+ if (!headersObj) headersObj = parseHeaders(headers);
11378
+
11379
+ if (name) {
11380
+ var value = headersObj[lowercase(name)];
11381
+ if (value === undefined) {
11382
+ value = null;
11383
+ }
11384
+ return value;
11385
+ }
11386
+
11387
+ return headersObj;
11388
+ };
11389
+ }
11390
+
11391
+
11392
+ /**
11393
+ * Chain all given functions
11394
+ *
11395
+ * This function is used for both request and response transforming
11396
+ *
11397
+ * @param {*} data Data to transform.
11398
+ * @param {function(string=)} headers HTTP headers getter fn.
11399
+ * @param {number} status HTTP status code of the response.
11400
+ * @param {(Function|Array.<Function>)} fns Function or an array of functions.
11401
+ * @returns {*} Transformed data.
11402
+ */
11403
+ function transformData(data, headers, status, fns) {
11404
+ if (isFunction(fns)) {
11405
+ return fns(data, headers, status);
11406
+ }
11407
+
11408
+ forEach(fns, function(fn) {
11409
+ data = fn(data, headers, status);
11410
+ });
11411
+
11412
+ return data;
11413
+ }
11414
+
11415
+
11416
+ function isSuccess(status) {
11417
+ return 200 <= status && status < 300;
11418
+ }
11419
+
11420
+
11421
+ /**
11422
+ * @ngdoc provider
11423
+ * @name $httpProvider
11424
+ * @this
11425
+ *
11426
+ * @description
11427
+ * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
11428
+ * */
11429
+ function $HttpProvider() {
11430
+ /**
11431
+ * @ngdoc property
11432
+ * @name $httpProvider#defaults
11433
+ * @description
11434
+ *
11435
+ * Object containing default values for all {@link ng.$http $http} requests.
11436
+ *
11437
+ * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with
11438
+ * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses
11439
+ * by default. See {@link $http#caching $http Caching} for more information.
11440
+ *
11441
+ * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
11442
+ * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
11443
+ * setting default headers.
11444
+ * - **`defaults.headers.common`**
11445
+ * - **`defaults.headers.post`**
11446
+ * - **`defaults.headers.put`**
11447
+ * - **`defaults.headers.patch`**
11448
+ *
11449
+ * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the
11450
+ * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the
11451
+ * {@link $jsonpCallbacks} service. Defaults to `'callback'`.
11452
+ *
11453
+ * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
11454
+ * used to the prepare string representation of request parameters (specified as an object).
11455
+ * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
11456
+ * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
11457
+ *
11458
+ * - **`defaults.transformRequest`** -
11459
+ * `{Array<function(data, headersGetter)>|function(data, headersGetter)}` -
11460
+ * An array of functions (or a single function) which are applied to the request data.
11461
+ * By default, this is an array with one request transformation function:
11462
+ *
11463
+ * - If the `data` property of the request configuration object contains an object, serialize it
11464
+ * into JSON format.
11465
+ *
11466
+ * - **`defaults.transformResponse`** -
11467
+ * `{Array<function(data, headersGetter, status)>|function(data, headersGetter, status)}` -
11468
+ * An array of functions (or a single function) which are applied to the response data. By default,
11469
+ * this is an array which applies one response transformation function that does two things:
11470
+ *
11471
+ * - If XSRF prefix is detected, strip it
11472
+ * (see {@link ng.$http#security-considerations Security Considerations in the $http docs}).
11473
+ * - If the `Content-Type` is `application/json` or the response looks like JSON,
11474
+ * deserialize it using a JSON parser.
11475
+ *
11476
+ * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
11477
+ * Defaults value is `'XSRF-TOKEN'`.
11478
+ *
11479
+ * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
11480
+ * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
11481
+ *
11482
+ **/
11483
+ var defaults = this.defaults = {
11484
+ // transform incoming response data
11485
+ transformResponse: [defaultHttpResponseTransform],
11486
+
11487
+ // transform outgoing request data
11488
+ transformRequest: [function(d) {
11489
+ return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
11490
+ }],
11491
+
11492
+ // default headers
11493
+ headers: {
11494
+ common: {
11495
+ 'Accept': 'application/json, text/plain, */*'
11496
+ },
11497
+ post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
11498
+ put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
11499
+ patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
11500
+ },
11501
+
11502
+ xsrfCookieName: 'XSRF-TOKEN',
11503
+ xsrfHeaderName: 'X-XSRF-TOKEN',
11504
+
11505
+ paramSerializer: '$httpParamSerializer',
11506
+
11507
+ jsonpCallbackParam: 'callback'
11508
+ };
11509
+
11510
+ var useApplyAsync = false;
11511
+ /**
11512
+ * @ngdoc method
11513
+ * @name $httpProvider#useApplyAsync
11514
+ * @description
11515
+ *
11516
+ * Configure $http service to combine processing of multiple http responses received at around
11517
+ * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
11518
+ * significant performance improvement for bigger applications that make many HTTP requests
11519
+ * concurrently (common during application bootstrap).
11520
+ *
11521
+ * Defaults to false. If no value is specified, returns the current configured value.
11522
+ *
11523
+ * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
11524
+ * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
11525
+ * to load and share the same digest cycle.
11526
+ *
11527
+ * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
11528
+ * otherwise, returns the current configured value.
11529
+ **/
11530
+ this.useApplyAsync = function(value) {
11531
+ if (isDefined(value)) {
11532
+ useApplyAsync = !!value;
11533
+ return this;
11534
+ }
11535
+ return useApplyAsync;
11536
+ };
11537
+
11538
+ /**
11539
+ * @ngdoc property
11540
+ * @name $httpProvider#interceptors
11541
+ * @description
11542
+ *
11543
+ * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
11544
+ * pre-processing of request or postprocessing of responses.
11545
+ *
11546
+ * These service factories are ordered by request, i.e. they are applied in the same order as the
11547
+ * array, on request, but reverse order, on response.
11548
+ *
11549
+ * {@link ng.$http#interceptors Interceptors detailed info}
11550
+ **/
11551
+ var interceptorFactories = this.interceptors = [];
11552
+
11553
+ this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce',
11554
+ function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) {
11555
+
11556
+ var defaultCache = $cacheFactory('$http');
11557
+
11558
+ /**
11559
+ * Make sure that default param serializer is exposed as a function
11560
+ */
11561
+ defaults.paramSerializer = isString(defaults.paramSerializer) ?
11562
+ $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
11563
+
11564
+ /**
11565
+ * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
11566
+ * The reversal is needed so that we can build up the interception chain around the
11567
+ * server request.
11568
+ */
11569
+ var reversedInterceptors = [];
11570
+
11571
+ forEach(interceptorFactories, function(interceptorFactory) {
11572
+ reversedInterceptors.unshift(isString(interceptorFactory)
11573
+ ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
11574
+ });
11575
+
11576
+ /**
11577
+ * @ngdoc service
11578
+ * @kind function
11579
+ * @name $http
11580
+ * @requires ng.$httpBackend
11581
+ * @requires $cacheFactory
11582
+ * @requires $rootScope
11583
+ * @requires $q
11584
+ * @requires $injector
11585
+ *
11586
+ * @description
11587
+ * The `$http` service is a core Angular service that facilitates communication with the remote
11588
+ * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
11589
+ * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
11590
+ *
11591
+ * For unit testing applications that use `$http` service, see
11592
+ * {@link ngMock.$httpBackend $httpBackend mock}.
11593
+ *
11594
+ * For a higher level of abstraction, please check out the {@link ngResource.$resource
11595
+ * $resource} service.
11596
+ *
11597
+ * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
11598
+ * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
11599
+ * it is important to familiarize yourself with these APIs and the guarantees they provide.
11600
+ *
11601
+ *
11602
+ * ## General usage
11603
+ * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
11604
+ * that is used to generate an HTTP request and returns a {@link ng.$q promise}.
11605
+ *
11606
+ * ```js
11607
+ * // Simple GET request example:
11608
+ * $http({
11609
+ * method: 'GET',
11610
+ * url: '/someUrl'
11611
+ * }).then(function successCallback(response) {
11612
+ * // this callback will be called asynchronously
11613
+ * // when the response is available
11614
+ * }, function errorCallback(response) {
11615
+ * // called asynchronously if an error occurs
11616
+ * // or server returns response with an error status.
11617
+ * });
11618
+ * ```
11619
+ *
11620
+ * The response object has these properties:
11621
+ *
11622
+ * - **data** – `{string|Object}` – The response body transformed with the transform
11623
+ * functions.
11624
+ * - **status** – `{number}` – HTTP status code of the response.
11625
+ * - **headers** – `{function([headerName])}` – Header getter function.
11626
+ * - **config** – `{Object}` – The configuration object that was used to generate the request.
11627
+ * - **statusText** – `{string}` – HTTP status text of the response.
11628
+ * - **xhrStatus** – `{string}` – Status of the XMLHttpRequest (`complete`, `error`, `timeout` or `abort`).
11629
+ *
11630
+ * A response status code between 200 and 299 is considered a success status and will result in
11631
+ * the success callback being called. Any response status code outside of that range is
11632
+ * considered an error status and will result in the error callback being called.
11633
+ * Also, status codes less than -1 are normalized to zero. -1 usually means the request was
11634
+ * aborted, e.g. using a `config.timeout`.
11635
+ * Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning
11636
+ * that the outcome (success or error) will be determined by the final response status code.
11637
+ *
11638
+ *
11639
+ * ## Shortcut methods
11640
+ *
11641
+ * Shortcut methods are also available. All shortcut methods require passing in the URL, and
11642
+ * request data must be passed in for POST/PUT requests. An optional config can be passed as the
11643
+ * last argument.
11644
+ *
11645
+ * ```js
11646
+ * $http.get('/someUrl', config).then(successCallback, errorCallback);
11647
+ * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
11648
+ * ```
11649
+ *
11650
+ * Complete list of shortcut methods:
11651
+ *
11652
+ * - {@link ng.$http#get $http.get}
11653
+ * - {@link ng.$http#head $http.head}
11654
+ * - {@link ng.$http#post $http.post}
11655
+ * - {@link ng.$http#put $http.put}
11656
+ * - {@link ng.$http#delete $http.delete}
11657
+ * - {@link ng.$http#jsonp $http.jsonp}
11658
+ * - {@link ng.$http#patch $http.patch}
11659
+ *
11660
+ *
11661
+ * ## Writing Unit Tests that use $http
11662
+ * When unit testing (using {@link ngMock ngMock}), it is necessary to call
11663
+ * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
11664
+ * request using trained responses.
11665
+ *
11666
+ * ```
11667
+ * $httpBackend.expectGET(...);
11668
+ * $http.get(...);
11669
+ * $httpBackend.flush();
11670
+ * ```
11671
+ *
11672
+ * ## Setting HTTP Headers
11673
+ *
11674
+ * The $http service will automatically add certain HTTP headers to all requests. These defaults
11675
+ * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
11676
+ * object, which currently contains this default configuration:
11677
+ *
11678
+ * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
11679
+ * - <code>Accept: application/json, text/plain, \*&#65279;/&#65279;\*</code>
11680
+ * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
11681
+ * - `Content-Type: application/json`
11682
+ * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
11683
+ * - `Content-Type: application/json`
11684
+ *
11685
+ * To add or overwrite these defaults, simply add or remove a property from these configuration
11686
+ * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
11687
+ * with the lowercased HTTP method name as the key, e.g.
11688
+ * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
11689
+ *
11690
+ * The defaults can also be set at runtime via the `$http.defaults` object in the same
11691
+ * fashion. For example:
11692
+ *
11693
+ * ```
11694
+ * module.run(function($http) {
11695
+ * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w';
11696
+ * });
11697
+ * ```
11698
+ *
11699
+ * In addition, you can supply a `headers` property in the config object passed when
11700
+ * calling `$http(config)`, which overrides the defaults without changing them globally.
11701
+ *
11702
+ * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
11703
+ * Use the `headers` property, setting the desired header to `undefined`. For example:
11704
+ *
11705
+ * ```js
11706
+ * var req = {
11707
+ * method: 'POST',
11708
+ * url: 'http://example.com',
11709
+ * headers: {
11710
+ * 'Content-Type': undefined
11711
+ * },
11712
+ * data: { test: 'test' }
11713
+ * }
11714
+ *
11715
+ * $http(req).then(function(){...}, function(){...});
11716
+ * ```
11717
+ *
11718
+ * ## Transforming Requests and Responses
11719
+ *
11720
+ * Both requests and responses can be transformed using transformation functions: `transformRequest`
11721
+ * and `transformResponse`. These properties can be a single function that returns
11722
+ * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
11723
+ * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
11724
+ *
11725
+ * <div class="alert alert-warning">
11726
+ * **Note:** Angular does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline.
11727
+ * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference).
11728
+ * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest
11729
+ * function will be reflected on the scope and in any templates where the object is data-bound.
11730
+ * To prevent this, transform functions should have no side-effects.
11731
+ * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return.
11732
+ * </div>
11733
+ *
11734
+ * ### Default Transformations
11735
+ *
11736
+ * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
11737
+ * `defaults.transformResponse` properties. If a request does not provide its own transformations
11738
+ * then these will be applied.
11739
+ *
11740
+ * You can augment or replace the default transformations by modifying these properties by adding to or
11741
+ * replacing the array.
11742
+ *
11743
+ * Angular provides the following default transformations:
11744
+ *
11745
+ * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`) is
11746
+ * an array with one function that does the following:
11747
+ *
11748
+ * - If the `data` property of the request configuration object contains an object, serialize it
11749
+ * into JSON format.
11750
+ *
11751
+ * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`) is
11752
+ * an array with one function that does the following:
11753
+ *
11754
+ * - If XSRF prefix is detected, strip it (see Security Considerations section below).
11755
+ * - If the `Content-Type` is `application/json` or the response looks like JSON,
11756
+ * deserialize it using a JSON parser.
11757
+ *
11758
+ *
11759
+ * ### Overriding the Default Transformations Per Request
11760
+ *
11761
+ * If you wish to override the request/response transformations only for a single request then provide
11762
+ * `transformRequest` and/or `transformResponse` properties on the configuration object passed
11763
+ * into `$http`.
11764
+ *
11765
+ * Note that if you provide these properties on the config object the default transformations will be
11766
+ * overwritten. If you wish to augment the default transformations then you must include them in your
11767
+ * local transformation array.
11768
+ *
11769
+ * The following code demonstrates adding a new response transformation to be run after the default response
11770
+ * transformations have been run.
11771
+ *
11772
+ * ```js
11773
+ * function appendTransform(defaults, transform) {
11774
+ *
11775
+ * // We can't guarantee that the default transformation is an array
11776
+ * defaults = angular.isArray(defaults) ? defaults : [defaults];
11777
+ *
11778
+ * // Append the new transformation to the defaults
11779
+ * return defaults.concat(transform);
11780
+ * }
11781
+ *
11782
+ * $http({
11783
+ * url: '...',
11784
+ * method: 'GET',
11785
+ * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
11786
+ * return doTransform(value);
11787
+ * })
11788
+ * });
11789
+ * ```
11790
+ *
11791
+ *
11792
+ * ## Caching
11793
+ *
11794
+ * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must
11795
+ * set the config.cache value or the default cache value to TRUE or to a cache object (created
11796
+ * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes
11797
+ * precedence over the default cache value.
11798
+ *
11799
+ * In order to:
11800
+ * * cache all responses - set the default cache value to TRUE or to a cache object
11801
+ * * cache a specific response - set config.cache value to TRUE or to a cache object
11802
+ *
11803
+ * If caching is enabled, but neither the default cache nor config.cache are set to a cache object,
11804
+ * then the default `$cacheFactory("$http")` object is used.
11805
+ *
11806
+ * The default cache value can be set by updating the
11807
+ * {@link ng.$http#defaults `$http.defaults.cache`} property or the
11808
+ * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property.
11809
+ *
11810
+ * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using
11811
+ * the relevant cache object. The next time the same request is made, the response is returned
11812
+ * from the cache without sending a request to the server.
11813
+ *
11814
+ * Take note that:
11815
+ *
11816
+ * * Only GET and JSONP requests are cached.
11817
+ * * The cache key is the request URL including search parameters; headers are not considered.
11818
+ * * Cached responses are returned asynchronously, in the same way as responses from the server.
11819
+ * * If multiple identical requests are made using the same cache, which is not yet populated,
11820
+ * one request will be made to the server and remaining requests will return the same response.
11821
+ * * A cache-control header on the response does not affect if or how responses are cached.
11822
+ *
11823
+ *
11824
+ * ## Interceptors
11825
+ *
11826
+ * Before you start creating interceptors, be sure to understand the
11827
+ * {@link ng.$q $q and deferred/promise APIs}.
11828
+ *
11829
+ * For purposes of global error handling, authentication, or any kind of synchronous or
11830
+ * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
11831
+ * able to intercept requests before they are handed to the server and
11832
+ * responses before they are handed over to the application code that
11833
+ * initiated these requests. The interceptors leverage the {@link ng.$q
11834
+ * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
11835
+ *
11836
+ * The interceptors are service factories that are registered with the `$httpProvider` by
11837
+ * adding them to the `$httpProvider.interceptors` array. The factory is called and
11838
+ * injected with dependencies (if specified) and returns the interceptor.
11839
+ *
11840
+ * There are two kinds of interceptors (and two kinds of rejection interceptors):
11841
+ *
11842
+ * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
11843
+ * modify the `config` object or create a new one. The function needs to return the `config`
11844
+ * object directly, or a promise containing the `config` or a new `config` object.
11845
+ * * `requestError`: interceptor gets called when a previous interceptor threw an error or
11846
+ * resolved with a rejection.
11847
+ * * `response`: interceptors get called with http `response` object. The function is free to
11848
+ * modify the `response` object or create a new one. The function needs to return the `response`
11849
+ * object directly, or as a promise containing the `response` or a new `response` object.
11850
+ * * `responseError`: interceptor gets called when a previous interceptor threw an error or
11851
+ * resolved with a rejection.
11852
+ *
11853
+ *
11854
+ * ```js
11855
+ * // register the interceptor as a service
11856
+ * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
11857
+ * return {
11858
+ * // optional method
11859
+ * 'request': function(config) {
11860
+ * // do something on success
11861
+ * return config;
11862
+ * },
11863
+ *
11864
+ * // optional method
11865
+ * 'requestError': function(rejection) {
11866
+ * // do something on error
11867
+ * if (canRecover(rejection)) {
11868
+ * return responseOrNewPromise
11869
+ * }
11870
+ * return $q.reject(rejection);
11871
+ * },
11872
+ *
11873
+ *
11874
+ *
11875
+ * // optional method
11876
+ * 'response': function(response) {
11877
+ * // do something on success
11878
+ * return response;
11879
+ * },
11880
+ *
11881
+ * // optional method
11882
+ * 'responseError': function(rejection) {
11883
+ * // do something on error
11884
+ * if (canRecover(rejection)) {
11885
+ * return responseOrNewPromise
11886
+ * }
11887
+ * return $q.reject(rejection);
11888
+ * }
11889
+ * };
11890
+ * });
11891
+ *
11892
+ * $httpProvider.interceptors.push('myHttpInterceptor');
11893
+ *
11894
+ *
11895
+ * // alternatively, register the interceptor via an anonymous factory
11896
+ * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
11897
+ * return {
11898
+ * 'request': function(config) {
11899
+ * // same as above
11900
+ * },
11901
+ *
11902
+ * 'response': function(response) {
11903
+ * // same as above
11904
+ * }
11905
+ * };
11906
+ * });
11907
+ * ```
11908
+ *
11909
+ * ## Security Considerations
11910
+ *
11911
+ * When designing web applications, consider security threats from:
11912
+ *
11913
+ * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
11914
+ * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
11915
+ *
11916
+ * Both server and the client must cooperate in order to eliminate these threats. Angular comes
11917
+ * pre-configured with strategies that address these issues, but for this to work backend server
11918
+ * cooperation is required.
11919
+ *
11920
+ * ### JSON Vulnerability Protection
11921
+ *
11922
+ * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
11923
+ * allows third party website to turn your JSON resource URL into
11924
+ * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
11925
+ * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
11926
+ * Angular will automatically strip the prefix before processing it as JSON.
11927
+ *
11928
+ * For example if your server needs to return:
11929
+ * ```js
11930
+ * ['one','two']
11931
+ * ```
11932
+ *
11933
+ * which is vulnerable to attack, your server can return:
11934
+ * ```js
11935
+ * )]}',
11936
+ * ['one','two']
11937
+ * ```
11938
+ *
11939
+ * Angular will strip the prefix, before processing the JSON.
11940
+ *
11941
+ *
11942
+ * ### Cross Site Request Forgery (XSRF) Protection
11943
+ *
11944
+ * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by
11945
+ * which the attacker can trick an authenticated user into unknowingly executing actions on your
11946
+ * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the
11947
+ * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP
11948
+ * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the
11949
+ * cookie, your server can be assured that the XHR came from JavaScript running on your domain.
11950
+ * The header will not be set for cross-domain requests.
11951
+ *
11952
+ * To take advantage of this, your server needs to set a token in a JavaScript readable session
11953
+ * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
11954
+ * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
11955
+ * that only JavaScript running on your domain could have sent the request. The token must be
11956
+ * unique for each user and must be verifiable by the server (to prevent the JavaScript from
11957
+ * making up its own tokens). We recommend that the token is a digest of your site's
11958
+ * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography&#41;)
11959
+ * for added security.
11960
+ *
11961
+ * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
11962
+ * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
11963
+ * or the per-request config object.
11964
+ *
11965
+ * In order to prevent collisions in environments where multiple Angular apps share the
11966
+ * same domain or subdomain, we recommend that each application uses unique cookie name.
11967
+ *
11968
+ * @param {object} config Object describing the request to be made and how it should be
11969
+ * processed. The object has following properties:
11970
+ *
11971
+ * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
11972
+ * - **url** – `{string|TrustedObject}` – Absolute or relative URL of the resource that is being requested;
11973
+ * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
11974
+ * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
11975
+ * with the `paramSerializer` and appended as GET parameters.
11976
+ * - **data** – `{string|Object}` – Data to be sent as the request message data.
11977
+ * - **headers** – `{Object}` – Map of strings or functions which return strings representing
11978
+ * HTTP headers to send to the server. If the return value of a function is null, the
11979
+ * header will not be sent. Functions accept a config object as an argument.
11980
+ * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object.
11981
+ * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`.
11982
+ * The handler will be called in the context of a `$apply` block.
11983
+ * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload
11984
+ * object. To bind events to the XMLHttpRequest object, use `eventHandlers`.
11985
+ * The handler will be called in the context of a `$apply` block.
11986
+ * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
11987
+ * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
11988
+ * - **transformRequest** –
11989
+ * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
11990
+ * transform function or an array of such functions. The transform function takes the http
11991
+ * request body and headers and returns its transformed (typically serialized) version.
11992
+ * See {@link ng.$http#overriding-the-default-transformations-per-request
11993
+ * Overriding the Default Transformations}
11994
+ * - **transformResponse** –
11995
+ * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
11996
+ * transform function or an array of such functions. The transform function takes the http
11997
+ * response body, headers and status and returns its transformed (typically deserialized) version.
11998
+ * See {@link ng.$http#overriding-the-default-transformations-per-request
11999
+ * Overriding the Default Transformations}
12000
+ * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
12001
+ * prepare the string representation of request parameters (specified as an object).
12002
+ * If specified as string, it is interpreted as function registered with the
12003
+ * {@link $injector $injector}, which means you can create your own serializer
12004
+ * by registering it as a {@link auto.$provide#service service}.
12005
+ * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
12006
+ * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
12007
+ * - **cache** – `{boolean|Object}` – A boolean value or object created with
12008
+ * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response.
12009
+ * See {@link $http#caching $http Caching} for more information.
12010
+ * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
12011
+ * that should abort the request when resolved.
12012
+ * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
12013
+ * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
12014
+ * for more information.
12015
+ * - **responseType** - `{string}` - see
12016
+ * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
12017
+ *
12018
+ * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
12019
+ * when the request succeeds or fails.
12020
+ *
12021
+ *
12022
+ * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
12023
+ * requests. This is primarily meant to be used for debugging purposes.
12024
+ *
12025
+ *
12026
+ * @example
12027
+ <example module="httpExample" name="http-service">
12028
+ <file name="index.html">
12029
+ <div ng-controller="FetchController">
12030
+ <select ng-model="method" aria-label="Request method">
12031
+ <option>GET</option>
12032
+ <option>JSONP</option>
12033
+ </select>
12034
+ <input type="text" ng-model="url" size="80" aria-label="URL" />
12035
+ <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
12036
+ <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
12037
+ <button id="samplejsonpbtn"
12038
+ ng-click="updateModel('JSONP',
12039
+ 'https://angularjs.org/greet.php?name=Super%20Hero')">
12040
+ Sample JSONP
12041
+ </button>
12042
+ <button id="invalidjsonpbtn"
12043
+ ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist')">
12044
+ Invalid JSONP
12045
+ </button>
12046
+ <pre>http status code: {{status}}</pre>
12047
+ <pre>http response data: {{data}}</pre>
12048
+ </div>
12049
+ </file>
12050
+ <file name="script.js">
12051
+ angular.module('httpExample', [])
12052
+ .config(['$sceDelegateProvider', function($sceDelegateProvider) {
12053
+ // We must whitelist the JSONP endpoint that we are using to show that we trust it
12054
+ $sceDelegateProvider.resourceUrlWhitelist([
12055
+ 'self',
12056
+ 'https://angularjs.org/**'
12057
+ ]);
12058
+ }])
12059
+ .controller('FetchController', ['$scope', '$http', '$templateCache',
12060
+ function($scope, $http, $templateCache) {
12061
+ $scope.method = 'GET';
12062
+ $scope.url = 'http-hello.html';
12063
+
12064
+ $scope.fetch = function() {
12065
+ $scope.code = null;
12066
+ $scope.response = null;
12067
+
12068
+ $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
12069
+ then(function(response) {
12070
+ $scope.status = response.status;
12071
+ $scope.data = response.data;
12072
+ }, function(response) {
12073
+ $scope.data = response.data || 'Request failed';
12074
+ $scope.status = response.status;
12075
+ });
12076
+ };
12077
+
12078
+ $scope.updateModel = function(method, url) {
12079
+ $scope.method = method;
12080
+ $scope.url = url;
12081
+ };
12082
+ }]);
12083
+ </file>
12084
+ <file name="http-hello.html">
12085
+ Hello, $http!
12086
+ </file>
12087
+ <file name="protractor.js" type="protractor">
12088
+ var status = element(by.binding('status'));
12089
+ var data = element(by.binding('data'));
12090
+ var fetchBtn = element(by.id('fetchbtn'));
12091
+ var sampleGetBtn = element(by.id('samplegetbtn'));
12092
+ var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
12093
+
12094
+ it('should make an xhr GET request', function() {
12095
+ sampleGetBtn.click();
12096
+ fetchBtn.click();
12097
+ expect(status.getText()).toMatch('200');
12098
+ expect(data.getText()).toMatch(/Hello, \$http!/);
12099
+ });
12100
+
12101
+ // Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
12102
+ // it('should make a JSONP request to angularjs.org', function() {
12103
+ // var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
12104
+ // sampleJsonpBtn.click();
12105
+ // fetchBtn.click();
12106
+ // expect(status.getText()).toMatch('200');
12107
+ // expect(data.getText()).toMatch(/Super Hero!/);
12108
+ // });
12109
+
12110
+ it('should make JSONP request to invalid URL and invoke the error handler',
12111
+ function() {
12112
+ invalidJsonpBtn.click();
12113
+ fetchBtn.click();
12114
+ expect(status.getText()).toMatch('0');
12115
+ expect(data.getText()).toMatch('Request failed');
12116
+ });
12117
+ </file>
12118
+ </example>
12119
+ */
12120
+ function $http(requestConfig) {
12121
+
12122
+ if (!isObject(requestConfig)) {
12123
+ throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
12124
+ }
12125
+
12126
+ if (!isString($sce.valueOf(requestConfig.url))) {
12127
+ throw minErr('$http')('badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: {0}', requestConfig.url);
12128
+ }
12129
+
12130
+ var config = extend({
12131
+ method: 'get',
12132
+ transformRequest: defaults.transformRequest,
12133
+ transformResponse: defaults.transformResponse,
12134
+ paramSerializer: defaults.paramSerializer,
12135
+ jsonpCallbackParam: defaults.jsonpCallbackParam
12136
+ }, requestConfig);
12137
+
12138
+ config.headers = mergeHeaders(requestConfig);
12139
+ config.method = uppercase(config.method);
12140
+ config.paramSerializer = isString(config.paramSerializer) ?
12141
+ $injector.get(config.paramSerializer) : config.paramSerializer;
12142
+
12143
+ $browser.$$incOutstandingRequestCount();
12144
+
12145
+ var requestInterceptors = [];
12146
+ var responseInterceptors = [];
12147
+ var promise = $q.resolve(config);
12148
+
12149
+ // apply interceptors
12150
+ forEach(reversedInterceptors, function(interceptor) {
12151
+ if (interceptor.request || interceptor.requestError) {
12152
+ requestInterceptors.unshift(interceptor.request, interceptor.requestError);
12153
+ }
12154
+ if (interceptor.response || interceptor.responseError) {
12155
+ responseInterceptors.push(interceptor.response, interceptor.responseError);
12156
+ }
12157
+ });
12158
+
12159
+ promise = chainInterceptors(promise, requestInterceptors);
12160
+ promise = promise.then(serverRequest);
12161
+ promise = chainInterceptors(promise, responseInterceptors);
12162
+ promise = promise.finally(completeOutstandingRequest);
12163
+
12164
+ return promise;
12165
+
12166
+
12167
+ function chainInterceptors(promise, interceptors) {
12168
+ for (var i = 0, ii = interceptors.length; i < ii;) {
12169
+ var thenFn = interceptors[i++];
12170
+ var rejectFn = interceptors[i++];
12171
+
12172
+ promise = promise.then(thenFn, rejectFn);
12173
+ }
12174
+
12175
+ interceptors.length = 0;
12176
+
12177
+ return promise;
12178
+ }
12179
+
12180
+ function completeOutstandingRequest() {
12181
+ $browser.$$completeOutstandingRequest(noop);
12182
+ }
12183
+
12184
+ function executeHeaderFns(headers, config) {
12185
+ var headerContent, processedHeaders = {};
12186
+
12187
+ forEach(headers, function(headerFn, header) {
12188
+ if (isFunction(headerFn)) {
12189
+ headerContent = headerFn(config);
12190
+ if (headerContent != null) {
12191
+ processedHeaders[header] = headerContent;
12192
+ }
12193
+ } else {
12194
+ processedHeaders[header] = headerFn;
12195
+ }
12196
+ });
12197
+
12198
+ return processedHeaders;
12199
+ }
12200
+
12201
+ function mergeHeaders(config) {
12202
+ var defHeaders = defaults.headers,
12203
+ reqHeaders = extend({}, config.headers),
12204
+ defHeaderName, lowercaseDefHeaderName, reqHeaderName;
12205
+
12206
+ defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
12207
+
12208
+ // using for-in instead of forEach to avoid unnecessary iteration after header has been found
12209
+ defaultHeadersIteration:
12210
+ for (defHeaderName in defHeaders) {
12211
+ lowercaseDefHeaderName = lowercase(defHeaderName);
12212
+
12213
+ for (reqHeaderName in reqHeaders) {
12214
+ if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
12215
+ continue defaultHeadersIteration;
12216
+ }
12217
+ }
12218
+
12219
+ reqHeaders[defHeaderName] = defHeaders[defHeaderName];
12220
+ }
12221
+
12222
+ // execute if header value is a function for merged headers
12223
+ return executeHeaderFns(reqHeaders, shallowCopy(config));
12224
+ }
12225
+
12226
+ function serverRequest(config) {
12227
+ var headers = config.headers;
12228
+ var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
12229
+
12230
+ // strip content-type if data is undefined
12231
+ if (isUndefined(reqData)) {
12232
+ forEach(headers, function(value, header) {
12233
+ if (lowercase(header) === 'content-type') {
12234
+ delete headers[header];
12235
+ }
12236
+ });
12237
+ }
12238
+
12239
+ if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
12240
+ config.withCredentials = defaults.withCredentials;
12241
+ }
12242
+
12243
+ // send request
12244
+ return sendReq(config, reqData).then(transformResponse, transformResponse);
12245
+ }
12246
+
12247
+ function transformResponse(response) {
12248
+ // make a copy since the response must be cacheable
12249
+ var resp = extend({}, response);
12250
+ resp.data = transformData(response.data, response.headers, response.status,
12251
+ config.transformResponse);
12252
+ return (isSuccess(response.status))
12253
+ ? resp
12254
+ : $q.reject(resp);
12255
+ }
12256
+ }
12257
+
12258
+ $http.pendingRequests = [];
12259
+
12260
+ /**
12261
+ * @ngdoc method
12262
+ * @name $http#get
12263
+ *
12264
+ * @description
12265
+ * Shortcut method to perform `GET` request.
12266
+ *
12267
+ * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12268
+ * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
12269
+ * @param {Object=} config Optional configuration object
12270
+ * @returns {HttpPromise} Future object
12271
+ */
12272
+
12273
+ /**
12274
+ * @ngdoc method
12275
+ * @name $http#delete
12276
+ *
12277
+ * @description
12278
+ * Shortcut method to perform `DELETE` request.
12279
+ *
12280
+ * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12281
+ * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
12282
+ * @param {Object=} config Optional configuration object
12283
+ * @returns {HttpPromise} Future object
12284
+ */
12285
+
12286
+ /**
12287
+ * @ngdoc method
12288
+ * @name $http#head
12289
+ *
12290
+ * @description
12291
+ * Shortcut method to perform `HEAD` request.
12292
+ *
12293
+ * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12294
+ * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
12295
+ * @param {Object=} config Optional configuration object
12296
+ * @returns {HttpPromise} Future object
12297
+ */
12298
+
12299
+ /**
12300
+ * @ngdoc method
12301
+ * @name $http#jsonp
12302
+ *
12303
+ * @description
12304
+ * Shortcut method to perform `JSONP` request.
12305
+ *
12306
+ * Note that, since JSONP requests are sensitive because the response is given full access to the browser,
12307
+ * the url must be declared, via {@link $sce} as a trusted resource URL.
12308
+ * You can trust a URL by adding it to the whitelist via
12309
+ * {@link $sceDelegateProvider#resourceUrlWhitelist `$sceDelegateProvider.resourceUrlWhitelist`} or
12310
+ * by explicitly trusting the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}.
12311
+ *
12312
+ * JSONP requests must specify a callback to be used in the response from the server. This callback
12313
+ * is passed as a query parameter in the request. You must specify the name of this parameter by
12314
+ * setting the `jsonpCallbackParam` property on the request config object.
12315
+ *
12316
+ * ```
12317
+ * $http.jsonp('some/trusted/url', {jsonpCallbackParam: 'callback'})
12318
+ * ```
12319
+ *
12320
+ * You can also specify a default callback parameter name in `$http.defaults.jsonpCallbackParam`.
12321
+ * Initially this is set to `'callback'`.
12322
+ *
12323
+ * <div class="alert alert-danger">
12324
+ * You can no longer use the `JSON_CALLBACK` string as a placeholder for specifying where the callback
12325
+ * parameter value should go.
12326
+ * </div>
12327
+ *
12328
+ * If you would like to customise where and how the callbacks are stored then try overriding
12329
+ * or decorating the {@link $jsonpCallbacks} service.
12330
+ *
12331
+ * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12332
+ * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
12333
+ * @param {Object=} config Optional configuration object
12334
+ * @returns {HttpPromise} Future object
12335
+ */
12336
+ createShortMethods('get', 'delete', 'head', 'jsonp');
12337
+
12338
+ /**
12339
+ * @ngdoc method
12340
+ * @name $http#post
12341
+ *
12342
+ * @description
12343
+ * Shortcut method to perform `POST` request.
12344
+ *
12345
+ * @param {string} url Relative or absolute URL specifying the destination of the request
12346
+ * @param {*} data Request content
12347
+ * @param {Object=} config Optional configuration object
12348
+ * @returns {HttpPromise} Future object
12349
+ */
12350
+
12351
+ /**
12352
+ * @ngdoc method
12353
+ * @name $http#put
12354
+ *
12355
+ * @description
12356
+ * Shortcut method to perform `PUT` request.
12357
+ *
12358
+ * @param {string} url Relative or absolute URL specifying the destination of the request
12359
+ * @param {*} data Request content
12360
+ * @param {Object=} config Optional configuration object
12361
+ * @returns {HttpPromise} Future object
12362
+ */
12363
+
12364
+ /**
12365
+ * @ngdoc method
12366
+ * @name $http#patch
12367
+ *
12368
+ * @description
12369
+ * Shortcut method to perform `PATCH` request.
12370
+ *
12371
+ * @param {string} url Relative or absolute URL specifying the destination of the request
12372
+ * @param {*} data Request content
12373
+ * @param {Object=} config Optional configuration object
12374
+ * @returns {HttpPromise} Future object
12375
+ */
12376
+ createShortMethodsWithData('post', 'put', 'patch');
12377
+
12378
+ /**
12379
+ * @ngdoc property
12380
+ * @name $http#defaults
12381
+ *
12382
+ * @description
12383
+ * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
12384
+ * default headers, withCredentials as well as request and response transformations.
12385
+ *
12386
+ * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
12387
+ */
12388
+ $http.defaults = defaults;
12389
+
12390
+
12391
+ return $http;
12392
+
12393
+
12394
+ function createShortMethods(names) {
12395
+ forEach(arguments, function(name) {
12396
+ $http[name] = function(url, config) {
12397
+ return $http(extend({}, config || {}, {
12398
+ method: name,
12399
+ url: url
12400
+ }));
12401
+ };
12402
+ });
12403
+ }
12404
+
12405
+
12406
+ function createShortMethodsWithData(name) {
12407
+ forEach(arguments, function(name) {
12408
+ $http[name] = function(url, data, config) {
12409
+ return $http(extend({}, config || {}, {
12410
+ method: name,
12411
+ url: url,
12412
+ data: data
12413
+ }));
12414
+ };
12415
+ });
12416
+ }
12417
+
12418
+
12419
+ /**
12420
+ * Makes the request.
12421
+ *
12422
+ * !!! ACCESSES CLOSURE VARS:
12423
+ * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
12424
+ */
12425
+ function sendReq(config, reqData) {
12426
+ var deferred = $q.defer(),
12427
+ promise = deferred.promise,
12428
+ cache,
12429
+ cachedResp,
12430
+ reqHeaders = config.headers,
12431
+ isJsonp = lowercase(config.method) === 'jsonp',
12432
+ url = config.url;
12433
+
12434
+ if (isJsonp) {
12435
+ // JSONP is a pretty sensitive operation where we're allowing a script to have full access to
12436
+ // our DOM and JS space. So we require that the URL satisfies SCE.RESOURCE_URL.
12437
+ url = $sce.getTrustedResourceUrl(url);
12438
+ } else if (!isString(url)) {
12439
+ // If it is not a string then the URL must be a $sce trusted object
12440
+ url = $sce.valueOf(url);
12441
+ }
12442
+
12443
+ url = buildUrl(url, config.paramSerializer(config.params));
12444
+
12445
+ if (isJsonp) {
12446
+ // Check the url and add the JSONP callback placeholder
12447
+ url = sanitizeJsonpCallbackParam(url, config.jsonpCallbackParam);
12448
+ }
12449
+
12450
+ $http.pendingRequests.push(config);
12451
+ promise.then(removePendingReq, removePendingReq);
12452
+
12453
+ if ((config.cache || defaults.cache) && config.cache !== false &&
12454
+ (config.method === 'GET' || config.method === 'JSONP')) {
12455
+ cache = isObject(config.cache) ? config.cache
12456
+ : isObject(/** @type {?} */ (defaults).cache)
12457
+ ? /** @type {?} */ (defaults).cache
12458
+ : defaultCache;
12459
+ }
12460
+
12461
+ if (cache) {
12462
+ cachedResp = cache.get(url);
12463
+ if (isDefined(cachedResp)) {
12464
+ if (isPromiseLike(cachedResp)) {
12465
+ // cached request has already been sent, but there is no response yet
12466
+ cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
12467
+ } else {
12468
+ // serving from cache
12469
+ if (isArray(cachedResp)) {
12470
+ resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3], cachedResp[4]);
12471
+ } else {
12472
+ resolvePromise(cachedResp, 200, {}, 'OK', 'complete');
12473
+ }
12474
+ }
12475
+ } else {
12476
+ // put the promise for the non-transformed response into cache as a placeholder
12477
+ cache.put(url, promise);
12478
+ }
12479
+ }
12480
+
12481
+
12482
+ // if we won't have the response in cache, set the xsrf headers and
12483
+ // send the request to the backend
12484
+ if (isUndefined(cachedResp)) {
12485
+ var xsrfValue = urlIsSameOrigin(config.url)
12486
+ ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
12487
+ : undefined;
12488
+ if (xsrfValue) {
12489
+ reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
12490
+ }
12491
+
12492
+ $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
12493
+ config.withCredentials, config.responseType,
12494
+ createApplyHandlers(config.eventHandlers),
12495
+ createApplyHandlers(config.uploadEventHandlers));
12496
+ }
12497
+
12498
+ return promise;
12499
+
12500
+ function createApplyHandlers(eventHandlers) {
12501
+ if (eventHandlers) {
12502
+ var applyHandlers = {};
12503
+ forEach(eventHandlers, function(eventHandler, key) {
12504
+ applyHandlers[key] = function(event) {
12505
+ if (useApplyAsync) {
12506
+ $rootScope.$applyAsync(callEventHandler);
12507
+ } else if ($rootScope.$$phase) {
12508
+ callEventHandler();
12509
+ } else {
12510
+ $rootScope.$apply(callEventHandler);
12511
+ }
12512
+
12513
+ function callEventHandler() {
12514
+ eventHandler(event);
12515
+ }
12516
+ };
12517
+ });
12518
+ return applyHandlers;
12519
+ }
12520
+ }
12521
+
12522
+
12523
+ /**
12524
+ * Callback registered to $httpBackend():
12525
+ * - caches the response if desired
12526
+ * - resolves the raw $http promise
12527
+ * - calls $apply
12528
+ */
12529
+ function done(status, response, headersString, statusText, xhrStatus) {
12530
+ if (cache) {
12531
+ if (isSuccess(status)) {
12532
+ cache.put(url, [status, response, parseHeaders(headersString), statusText, xhrStatus]);
12533
+ } else {
12534
+ // remove promise from the cache
12535
+ cache.remove(url);
12536
+ }
12537
+ }
12538
+
12539
+ function resolveHttpPromise() {
12540
+ resolvePromise(response, status, headersString, statusText, xhrStatus);
12541
+ }
12542
+
12543
+ if (useApplyAsync) {
12544
+ $rootScope.$applyAsync(resolveHttpPromise);
12545
+ } else {
12546
+ resolveHttpPromise();
12547
+ if (!$rootScope.$$phase) $rootScope.$apply();
12548
+ }
12549
+ }
12550
+
12551
+
12552
+ /**
12553
+ * Resolves the raw $http promise.
12554
+ */
12555
+ function resolvePromise(response, status, headers, statusText, xhrStatus) {
12556
+ //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
12557
+ status = status >= -1 ? status : 0;
12558
+
12559
+ (isSuccess(status) ? deferred.resolve : deferred.reject)({
12560
+ data: response,
12561
+ status: status,
12562
+ headers: headersGetter(headers),
12563
+ config: config,
12564
+ statusText: statusText,
12565
+ xhrStatus: xhrStatus
12566
+ });
12567
+ }
12568
+
12569
+ function resolvePromiseWithResult(result) {
12570
+ resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText, result.xhrStatus);
12571
+ }
12572
+
12573
+ function removePendingReq() {
12574
+ var idx = $http.pendingRequests.indexOf(config);
12575
+ if (idx !== -1) $http.pendingRequests.splice(idx, 1);
12576
+ }
12577
+ }
12578
+
12579
+
12580
+ function buildUrl(url, serializedParams) {
12581
+ if (serializedParams.length > 0) {
12582
+ url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams;
12583
+ }
12584
+ return url;
12585
+ }
12586
+
12587
+ function sanitizeJsonpCallbackParam(url, key) {
12588
+ if (/[&?][^=]+=JSON_CALLBACK/.test(url)) {
12589
+ // Throw if the url already contains a reference to JSON_CALLBACK
12590
+ throw $httpMinErr('badjsonp', 'Illegal use of JSON_CALLBACK in url, "{0}"', url);
12591
+ }
12592
+
12593
+ var callbackParamRegex = new RegExp('[&?]' + key + '=');
12594
+ if (callbackParamRegex.test(url)) {
12595
+ // Throw if the callback param was already provided
12596
+ throw $httpMinErr('badjsonp', 'Illegal use of callback param, "{0}", in url, "{1}"', key, url);
12597
+ }
12598
+
12599
+ // Add in the JSON_CALLBACK callback param value
12600
+ url += ((url.indexOf('?') === -1) ? '?' : '&') + key + '=JSON_CALLBACK';
12601
+
12602
+ return url;
12603
+ }
12604
+ }];
12605
+ }
12606
+
12607
+ /**
12608
+ * @ngdoc service
12609
+ * @name $xhrFactory
12610
+ * @this
12611
+ *
12612
+ * @description
12613
+ * Factory function used to create XMLHttpRequest objects.
12614
+ *
12615
+ * Replace or decorate this service to create your own custom XMLHttpRequest objects.
12616
+ *
12617
+ * ```
12618
+ * angular.module('myApp', [])
12619
+ * .factory('$xhrFactory', function() {
12620
+ * return function createXhr(method, url) {
12621
+ * return new window.XMLHttpRequest({mozSystem: true});
12622
+ * };
12623
+ * });
12624
+ * ```
12625
+ *
12626
+ * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
12627
+ * @param {string} url URL of the request.
12628
+ */
12629
+ function $xhrFactoryProvider() {
12630
+ this.$get = function() {
12631
+ return function createXhr() {
12632
+ return new window.XMLHttpRequest();
12633
+ };
12634
+ };
12635
+ }
12636
+
12637
+ /**
12638
+ * @ngdoc service
12639
+ * @name $httpBackend
12640
+ * @requires $jsonpCallbacks
12641
+ * @requires $document
12642
+ * @requires $xhrFactory
12643
+ * @this
12644
+ *
12645
+ * @description
12646
+ * HTTP backend used by the {@link ng.$http service} that delegates to
12647
+ * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
12648
+ *
12649
+ * You should never need to use this service directly, instead use the higher-level abstractions:
12650
+ * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
12651
+ *
12652
+ * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
12653
+ * $httpBackend} which can be trained with responses.
12654
+ */
12655
+ function $HttpBackendProvider() {
12656
+ this.$get = ['$browser', '$jsonpCallbacks', '$document', '$xhrFactory', function($browser, $jsonpCallbacks, $document, $xhrFactory) {
12657
+ return createHttpBackend($browser, $xhrFactory, $browser.defer, $jsonpCallbacks, $document[0]);
12658
+ }];
12659
+ }
12660
+
12661
+ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
12662
+ // TODO(vojta): fix the signature
12663
+ return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) {
12664
+ url = url || $browser.url();
12665
+
12666
+ if (lowercase(method) === 'jsonp') {
12667
+ var callbackPath = callbacks.createCallback(url);
12668
+ var jsonpDone = jsonpReq(url, callbackPath, function(status, text) {
12669
+ // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING)
12670
+ var response = (status === 200) && callbacks.getResponse(callbackPath);
12671
+ completeRequest(callback, status, response, '', text, 'complete');
12672
+ callbacks.removeCallback(callbackPath);
12673
+ });
12674
+ } else {
12675
+
12676
+ var xhr = createXhr(method, url);
12677
+
12678
+ xhr.open(method, url, true);
12679
+ forEach(headers, function(value, key) {
12680
+ if (isDefined(value)) {
12681
+ xhr.setRequestHeader(key, value);
12682
+ }
12683
+ });
12684
+
12685
+ xhr.onload = function requestLoaded() {
12686
+ var statusText = xhr.statusText || '';
12687
+
12688
+ // responseText is the old-school way of retrieving response (supported by IE9)
12689
+ // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
12690
+ var response = ('response' in xhr) ? xhr.response : xhr.responseText;
12691
+
12692
+ // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
12693
+ var status = xhr.status === 1223 ? 204 : xhr.status;
12694
+
12695
+ // fix status code when it is 0 (0 status is undocumented).
12696
+ // Occurs when accessing file resources or on Android 4.1 stock browser
12697
+ // while retrieving files from application cache.
12698
+ if (status === 0) {
12699
+ status = response ? 200 : urlResolve(url).protocol === 'file' ? 404 : 0;
12700
+ }
12701
+
12702
+ completeRequest(callback,
12703
+ status,
12704
+ response,
12705
+ xhr.getAllResponseHeaders(),
12706
+ statusText,
12707
+ 'complete');
12708
+ };
12709
+
12710
+ var requestError = function() {
12711
+ // The response is always empty
12712
+ // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
12713
+ completeRequest(callback, -1, null, null, '', 'error');
12714
+ };
12715
+
12716
+ var requestAborted = function() {
12717
+ completeRequest(callback, -1, null, null, '', 'abort');
12718
+ };
12719
+
12720
+ var requestTimeout = function() {
12721
+ // The response is always empty
12722
+ // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
12723
+ completeRequest(callback, -1, null, null, '', 'timeout');
12724
+ };
12725
+
12726
+ xhr.onerror = requestError;
12727
+ xhr.onabort = requestAborted;
12728
+ xhr.ontimeout = requestTimeout;
12729
+
12730
+ forEach(eventHandlers, function(value, key) {
12731
+ xhr.addEventListener(key, value);
12732
+ });
12733
+
12734
+ forEach(uploadEventHandlers, function(value, key) {
12735
+ xhr.upload.addEventListener(key, value);
12736
+ });
12737
+
12738
+ if (withCredentials) {
12739
+ xhr.withCredentials = true;
12740
+ }
12741
+
12742
+ if (responseType) {
12743
+ try {
12744
+ xhr.responseType = responseType;
12745
+ } catch (e) {
12746
+ // WebKit added support for the json responseType value on 09/03/2013
12747
+ // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
12748
+ // known to throw when setting the value "json" as the response type. Other older
12749
+ // browsers implementing the responseType
12750
+ //
12751
+ // The json response type can be ignored if not supported, because JSON payloads are
12752
+ // parsed on the client-side regardless.
12753
+ if (responseType !== 'json') {
12754
+ throw e;
12755
+ }
12756
+ }
12757
+ }
12758
+
12759
+ xhr.send(isUndefined(post) ? null : post);
12760
+ }
12761
+
12762
+ if (timeout > 0) {
12763
+ var timeoutId = $browserDefer(timeoutRequest, timeout);
12764
+ } else if (isPromiseLike(timeout)) {
12765
+ timeout.then(timeoutRequest);
12766
+ }
12767
+
12768
+
12769
+ function timeoutRequest() {
12770
+ if (jsonpDone) {
12771
+ jsonpDone();
12772
+ }
12773
+ if (xhr) {
12774
+ xhr.abort();
12775
+ }
12776
+ }
12777
+
12778
+ function completeRequest(callback, status, response, headersString, statusText, xhrStatus) {
12779
+ // cancel timeout and subsequent timeout promise resolution
12780
+ if (isDefined(timeoutId)) {
12781
+ $browserDefer.cancel(timeoutId);
12782
+ }
12783
+ jsonpDone = xhr = null;
12784
+
12785
+ callback(status, response, headersString, statusText, xhrStatus);
12786
+ }
12787
+ };
12788
+
12789
+ function jsonpReq(url, callbackPath, done) {
12790
+ url = url.replace('JSON_CALLBACK', callbackPath);
12791
+ // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
12792
+ // - fetches local scripts via XHR and evals them
12793
+ // - adds and immediately removes script elements from the document
12794
+ var script = rawDocument.createElement('script'), callback = null;
12795
+ script.type = 'text/javascript';
12796
+ script.src = url;
12797
+ script.async = true;
12798
+
12799
+ callback = function(event) {
12800
+ script.removeEventListener('load', callback);
12801
+ script.removeEventListener('error', callback);
12802
+ rawDocument.body.removeChild(script);
12803
+ script = null;
12804
+ var status = -1;
12805
+ var text = 'unknown';
12806
+
12807
+ if (event) {
12808
+ if (event.type === 'load' && !callbacks.wasCalled(callbackPath)) {
12809
+ event = { type: 'error' };
12810
+ }
12811
+ text = event.type;
12812
+ status = event.type === 'error' ? 404 : 200;
12813
+ }
12814
+
12815
+ if (done) {
12816
+ done(status, text);
12817
+ }
12818
+ };
12819
+
12820
+ script.addEventListener('load', callback);
12821
+ script.addEventListener('error', callback);
12822
+ rawDocument.body.appendChild(script);
12823
+ return callback;
12824
+ }
12825
+ }
12826
+
12827
+ var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
12828
+ $interpolateMinErr.throwNoconcat = function(text) {
12829
+ throw $interpolateMinErr('noconcat',
12830
+ 'Error while interpolating: {0}\nStrict Contextual Escaping disallows ' +
12831
+ 'interpolations that concatenate multiple expressions when a trusted value is ' +
12832
+ 'required. See http://docs.angularjs.org/api/ng.$sce', text);
12833
+ };
12834
+
12835
+ $interpolateMinErr.interr = function(text, err) {
12836
+ return $interpolateMinErr('interr', 'Can\'t interpolate: {0}\n{1}', text, err.toString());
12837
+ };
12838
+
12839
+ /**
12840
+ * @ngdoc provider
12841
+ * @name $interpolateProvider
12842
+ * @this
12843
+ *
12844
+ * @description
12845
+ *
12846
+ * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
12847
+ *
12848
+ * <div class="alert alert-danger">
12849
+ * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular
12850
+ * template within a Python Jinja template (or any other template language). Mixing templating
12851
+ * languages is **very dangerous**. The embedding template language will not safely escape Angular
12852
+ * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS)
12853
+ * security bugs!
12854
+ * </div>
12855
+ *
12856
+ * @example
12857
+ <example name="custom-interpolation-markup" module="customInterpolationApp">
12858
+ <file name="index.html">
12859
+ <script>
12860
+ var customInterpolationApp = angular.module('customInterpolationApp', []);
12861
+
12862
+ customInterpolationApp.config(function($interpolateProvider) {
12863
+ $interpolateProvider.startSymbol('//');
12864
+ $interpolateProvider.endSymbol('//');
12865
+ });
12866
+
12867
+
12868
+ customInterpolationApp.controller('DemoController', function() {
12869
+ this.label = "This binding is brought you by // interpolation symbols.";
12870
+ });
12871
+ </script>
12872
+ <div ng-controller="DemoController as demo">
12873
+ //demo.label//
12874
+ </div>
12875
+ </file>
12876
+ <file name="protractor.js" type="protractor">
12877
+ it('should interpolate binding with custom symbols', function() {
12878
+ expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
12879
+ });
12880
+ </file>
12881
+ </example>
12882
+ */
12883
+ function $InterpolateProvider() {
12884
+ var startSymbol = '{{';
12885
+ var endSymbol = '}}';
12886
+
12887
+ /**
12888
+ * @ngdoc method
12889
+ * @name $interpolateProvider#startSymbol
12890
+ * @description
12891
+ * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
12892
+ *
12893
+ * @param {string=} value new value to set the starting symbol to.
12894
+ * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
12895
+ */
12896
+ this.startSymbol = function(value) {
12897
+ if (value) {
12898
+ startSymbol = value;
12899
+ return this;
12900
+ } else {
12901
+ return startSymbol;
12902
+ }
12903
+ };
12904
+
12905
+ /**
12906
+ * @ngdoc method
12907
+ * @name $interpolateProvider#endSymbol
12908
+ * @description
12909
+ * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
12910
+ *
12911
+ * @param {string=} value new value to set the ending symbol to.
12912
+ * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
12913
+ */
12914
+ this.endSymbol = function(value) {
12915
+ if (value) {
12916
+ endSymbol = value;
12917
+ return this;
12918
+ } else {
12919
+ return endSymbol;
12920
+ }
12921
+ };
12922
+
12923
+
12924
+ this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
12925
+ var startSymbolLength = startSymbol.length,
12926
+ endSymbolLength = endSymbol.length,
12927
+ escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
12928
+ escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
12929
+
12930
+ function escape(ch) {
12931
+ return '\\\\\\' + ch;
12932
+ }
12933
+
12934
+ function unescapeText(text) {
12935
+ return text.replace(escapedStartRegexp, startSymbol).
12936
+ replace(escapedEndRegexp, endSymbol);
12937
+ }
12938
+
12939
+ // TODO: this is the same as the constantWatchDelegate in parse.js
12940
+ function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
12941
+ var unwatch = scope.$watch(function constantInterpolateWatch(scope) {
12942
+ unwatch();
12943
+ return constantInterp(scope);
12944
+ }, listener, objectEquality);
12945
+ return unwatch;
12946
+ }
12947
+
12948
+ /**
12949
+ * @ngdoc service
12950
+ * @name $interpolate
12951
+ * @kind function
12952
+ *
12953
+ * @requires $parse
12954
+ * @requires $sce
12955
+ *
12956
+ * @description
12957
+ *
12958
+ * Compiles a string with markup into an interpolation function. This service is used by the
12959
+ * HTML {@link ng.$compile $compile} service for data binding. See
12960
+ * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
12961
+ * interpolation markup.
12962
+ *
12963
+ *
12964
+ * ```js
12965
+ * var $interpolate = ...; // injected
12966
+ * var exp = $interpolate('Hello {{name | uppercase}}!');
12967
+ * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
12968
+ * ```
12969
+ *
12970
+ * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
12971
+ * `true`, the interpolation function will return `undefined` unless all embedded expressions
12972
+ * evaluate to a value other than `undefined`.
12973
+ *
12974
+ * ```js
12975
+ * var $interpolate = ...; // injected
12976
+ * var context = {greeting: 'Hello', name: undefined };
12977
+ *
12978
+ * // default "forgiving" mode
12979
+ * var exp = $interpolate('{{greeting}} {{name}}!');
12980
+ * expect(exp(context)).toEqual('Hello !');
12981
+ *
12982
+ * // "allOrNothing" mode
12983
+ * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
12984
+ * expect(exp(context)).toBeUndefined();
12985
+ * context.name = 'Angular';
12986
+ * expect(exp(context)).toEqual('Hello Angular!');
12987
+ * ```
12988
+ *
12989
+ * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
12990
+ *
12991
+ * #### Escaped Interpolation
12992
+ * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
12993
+ * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
12994
+ * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
12995
+ * or binding.
12996
+ *
12997
+ * This enables web-servers to prevent script injection attacks and defacing attacks, to some
12998
+ * degree, while also enabling code examples to work without relying on the
12999
+ * {@link ng.directive:ngNonBindable ngNonBindable} directive.
13000
+ *
13001
+ * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
13002
+ * replacing angle brackets (&lt;, &gt;) with &amp;lt; and &amp;gt; respectively, and replacing all
13003
+ * interpolation start/end markers with their escaped counterparts.**
13004
+ *
13005
+ * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
13006
+ * output when the $interpolate service processes the text. So, for HTML elements interpolated
13007
+ * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
13008
+ * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
13009
+ * this is typically useful only when user-data is used in rendering a template from the server, or
13010
+ * when otherwise untrusted data is used by a directive.
13011
+ *
13012
+ * <example name="interpolation">
13013
+ * <file name="index.html">
13014
+ * <div ng-init="username='A user'">
13015
+ * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
13016
+ * </p>
13017
+ * <p><strong>{{username}}</strong> attempts to inject code which will deface the
13018
+ * application, but fails to accomplish their task, because the server has correctly
13019
+ * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
13020
+ * characters.</p>
13021
+ * <p>Instead, the result of the attempted script injection is visible, and can be removed
13022
+ * from the database by an administrator.</p>
13023
+ * </div>
13024
+ * </file>
13025
+ * </example>
13026
+ *
13027
+ * @knownIssue
13028
+ * It is currently not possible for an interpolated expression to contain the interpolation end
13029
+ * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e.
13030
+ * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string.
13031
+ *
13032
+ * @knownIssue
13033
+ * All directives and components must use the standard `{{` `}}` interpolation symbols
13034
+ * in their templates. If you change the application interpolation symbols the {@link $compile}
13035
+ * service will attempt to denormalize the standard symbols to the custom symbols.
13036
+ * The denormalization process is not clever enough to know not to replace instances of the standard
13037
+ * symbols where they would not normally be treated as interpolation symbols. For example in the following
13038
+ * code snippet the closing braces of the literal object will get incorrectly denormalized:
13039
+ *
13040
+ * ```
13041
+ * <div data-context='{"context":{"id":3,"type":"page"}}">
13042
+ * ```
13043
+ *
13044
+ * The workaround is to ensure that such instances are separated by whitespace:
13045
+ * ```
13046
+ * <div data-context='{"context":{"id":3,"type":"page"} }">
13047
+ * ```
13048
+ *
13049
+ * See https://github.com/angular/angular.js/pull/14610#issuecomment-219401099 for more information.
13050
+ *
13051
+ * @param {string} text The text with markup to interpolate.
13052
+ * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
13053
+ * embedded expression in order to return an interpolation function. Strings with no
13054
+ * embedded expression will return null for the interpolation function.
13055
+ * @param {string=} trustedContext when provided, the returned function passes the interpolated
13056
+ * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
13057
+ * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
13058
+ * provides Strict Contextual Escaping for details.
13059
+ * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
13060
+ * unless all embedded expressions evaluate to a value other than `undefined`.
13061
+ * @returns {function(context)} an interpolation function which is used to compute the
13062
+ * interpolated string. The function has these parameters:
13063
+ *
13064
+ * - `context`: evaluation context for all expressions embedded in the interpolated text
13065
+ */
13066
+ function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
13067
+ // Provide a quick exit and simplified result function for text with no interpolation
13068
+ if (!text.length || text.indexOf(startSymbol) === -1) {
13069
+ var constantInterp;
13070
+ if (!mustHaveExpression) {
13071
+ var unescapedText = unescapeText(text);
13072
+ constantInterp = valueFn(unescapedText);
13073
+ constantInterp.exp = text;
13074
+ constantInterp.expressions = [];
13075
+ constantInterp.$$watchDelegate = constantWatchDelegate;
13076
+ }
13077
+ return constantInterp;
13078
+ }
13079
+
13080
+ allOrNothing = !!allOrNothing;
13081
+ var startIndex,
13082
+ endIndex,
13083
+ index = 0,
13084
+ expressions = [],
13085
+ parseFns = [],
13086
+ textLength = text.length,
13087
+ exp,
13088
+ concat = [],
13089
+ expressionPositions = [];
13090
+
13091
+ while (index < textLength) {
13092
+ if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
13093
+ ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) {
13094
+ if (index !== startIndex) {
13095
+ concat.push(unescapeText(text.substring(index, startIndex)));
13096
+ }
13097
+ exp = text.substring(startIndex + startSymbolLength, endIndex);
13098
+ expressions.push(exp);
13099
+ parseFns.push($parse(exp, parseStringifyInterceptor));
13100
+ index = endIndex + endSymbolLength;
13101
+ expressionPositions.push(concat.length);
13102
+ concat.push('');
13103
+ } else {
13104
+ // we did not find an interpolation, so we have to add the remainder to the separators array
13105
+ if (index !== textLength) {
13106
+ concat.push(unescapeText(text.substring(index)));
13107
+ }
13108
+ break;
13109
+ }
13110
+ }
13111
+
13112
+ // Concatenating expressions makes it hard to reason about whether some combination of
13113
+ // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
13114
+ // single expression be used for iframe[src], object[src], etc., we ensure that the value
13115
+ // that's used is assigned or constructed by some JS code somewhere that is more testable or
13116
+ // make it obvious that you bound the value to some user controlled value. This helps reduce
13117
+ // the load when auditing for XSS issues.
13118
+ if (trustedContext && concat.length > 1) {
13119
+ $interpolateMinErr.throwNoconcat(text);
13120
+ }
13121
+
13122
+ if (!mustHaveExpression || expressions.length) {
13123
+ var compute = function(values) {
13124
+ for (var i = 0, ii = expressions.length; i < ii; i++) {
13125
+ if (allOrNothing && isUndefined(values[i])) return;
13126
+ concat[expressionPositions[i]] = values[i];
13127
+ }
13128
+ return concat.join('');
13129
+ };
13130
+
13131
+ var getValue = function(value) {
13132
+ return trustedContext ?
13133
+ $sce.getTrusted(trustedContext, value) :
13134
+ $sce.valueOf(value);
13135
+ };
13136
+
13137
+ return extend(function interpolationFn(context) {
13138
+ var i = 0;
13139
+ var ii = expressions.length;
13140
+ var values = new Array(ii);
13141
+
13142
+ try {
13143
+ for (; i < ii; i++) {
13144
+ values[i] = parseFns[i](context);
13145
+ }
13146
+
13147
+ return compute(values);
13148
+ } catch (err) {
13149
+ $exceptionHandler($interpolateMinErr.interr(text, err));
13150
+ }
13151
+
13152
+ }, {
13153
+ // all of these properties are undocumented for now
13154
+ exp: text, //just for compatibility with regular watchers created via $watch
13155
+ expressions: expressions,
13156
+ $$watchDelegate: function(scope, listener) {
13157
+ var lastValue;
13158
+ return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) {
13159
+ var currValue = compute(values);
13160
+ if (isFunction(listener)) {
13161
+ listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
13162
+ }
13163
+ lastValue = currValue;
13164
+ });
13165
+ }
13166
+ });
13167
+ }
13168
+
13169
+ function parseStringifyInterceptor(value) {
13170
+ try {
13171
+ value = getValue(value);
13172
+ return allOrNothing && !isDefined(value) ? value : stringify(value);
13173
+ } catch (err) {
13174
+ $exceptionHandler($interpolateMinErr.interr(text, err));
13175
+ }
13176
+ }
13177
+ }
13178
+
13179
+
13180
+ /**
13181
+ * @ngdoc method
13182
+ * @name $interpolate#startSymbol
13183
+ * @description
13184
+ * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
13185
+ *
13186
+ * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
13187
+ * the symbol.
13188
+ *
13189
+ * @returns {string} start symbol.
13190
+ */
13191
+ $interpolate.startSymbol = function() {
13192
+ return startSymbol;
13193
+ };
13194
+
13195
+
13196
+ /**
13197
+ * @ngdoc method
13198
+ * @name $interpolate#endSymbol
13199
+ * @description
13200
+ * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
13201
+ *
13202
+ * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
13203
+ * the symbol.
13204
+ *
13205
+ * @returns {string} end symbol.
13206
+ */
13207
+ $interpolate.endSymbol = function() {
13208
+ return endSymbol;
13209
+ };
13210
+
13211
+ return $interpolate;
13212
+ }];
13213
+ }
13214
+
13215
+ /** @this */
13216
+ function $IntervalProvider() {
13217
+ this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser',
13218
+ function($rootScope, $window, $q, $$q, $browser) {
13219
+ var intervals = {};
13220
+
13221
+
13222
+ /**
13223
+ * @ngdoc service
13224
+ * @name $interval
13225
+ *
13226
+ * @description
13227
+ * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
13228
+ * milliseconds.
13229
+ *
13230
+ * The return value of registering an interval function is a promise. This promise will be
13231
+ * notified upon each tick of the interval, and will be resolved after `count` iterations, or
13232
+ * run indefinitely if `count` is not defined. The value of the notification will be the
13233
+ * number of iterations that have run.
13234
+ * To cancel an interval, call `$interval.cancel(promise)`.
13235
+ *
13236
+ * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
13237
+ * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
13238
+ * time.
13239
+ *
13240
+ * <div class="alert alert-warning">
13241
+ * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
13242
+ * with them. In particular they are not automatically destroyed when a controller's scope or a
13243
+ * directive's element are destroyed.
13244
+ * You should take this into consideration and make sure to always cancel the interval at the
13245
+ * appropriate moment. See the example below for more details on how and when to do this.
13246
+ * </div>
13247
+ *
13248
+ * @param {function()} fn A function that should be called repeatedly. If no additional arguments
13249
+ * are passed (see below), the function is called with the current iteration count.
13250
+ * @param {number} delay Number of milliseconds between each function call.
13251
+ * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
13252
+ * indefinitely.
13253
+ * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
13254
+ * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
13255
+ * @param {...*=} Pass additional parameters to the executed function.
13256
+ * @returns {promise} A promise which will be notified on each iteration. It will resolve once all iterations of the interval complete.
13257
+ *
13258
+ * @example
13259
+ * <example module="intervalExample" name="interval-service">
13260
+ * <file name="index.html">
13261
+ * <script>
13262
+ * angular.module('intervalExample', [])
13263
+ * .controller('ExampleController', ['$scope', '$interval',
13264
+ * function($scope, $interval) {
13265
+ * $scope.format = 'M/d/yy h:mm:ss a';
13266
+ * $scope.blood_1 = 100;
13267
+ * $scope.blood_2 = 120;
13268
+ *
13269
+ * var stop;
13270
+ * $scope.fight = function() {
13271
+ * // Don't start a new fight if we are already fighting
13272
+ * if ( angular.isDefined(stop) ) return;
13273
+ *
13274
+ * stop = $interval(function() {
13275
+ * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
13276
+ * $scope.blood_1 = $scope.blood_1 - 3;
13277
+ * $scope.blood_2 = $scope.blood_2 - 4;
13278
+ * } else {
13279
+ * $scope.stopFight();
13280
+ * }
13281
+ * }, 100);
13282
+ * };
13283
+ *
13284
+ * $scope.stopFight = function() {
13285
+ * if (angular.isDefined(stop)) {
13286
+ * $interval.cancel(stop);
13287
+ * stop = undefined;
13288
+ * }
13289
+ * };
13290
+ *
13291
+ * $scope.resetFight = function() {
13292
+ * $scope.blood_1 = 100;
13293
+ * $scope.blood_2 = 120;
13294
+ * };
13295
+ *
13296
+ * $scope.$on('$destroy', function() {
13297
+ * // Make sure that the interval is destroyed too
13298
+ * $scope.stopFight();
13299
+ * });
13300
+ * }])
13301
+ * // Register the 'myCurrentTime' directive factory method.
13302
+ * // We inject $interval and dateFilter service since the factory method is DI.
13303
+ * .directive('myCurrentTime', ['$interval', 'dateFilter',
13304
+ * function($interval, dateFilter) {
13305
+ * // return the directive link function. (compile function not needed)
13306
+ * return function(scope, element, attrs) {
13307
+ * var format, // date format
13308
+ * stopTime; // so that we can cancel the time updates
13309
+ *
13310
+ * // used to update the UI
13311
+ * function updateTime() {
13312
+ * element.text(dateFilter(new Date(), format));
13313
+ * }
13314
+ *
13315
+ * // watch the expression, and update the UI on change.
13316
+ * scope.$watch(attrs.myCurrentTime, function(value) {
13317
+ * format = value;
13318
+ * updateTime();
13319
+ * });
13320
+ *
13321
+ * stopTime = $interval(updateTime, 1000);
13322
+ *
13323
+ * // listen on DOM destroy (removal) event, and cancel the next UI update
13324
+ * // to prevent updating time after the DOM element was removed.
13325
+ * element.on('$destroy', function() {
13326
+ * $interval.cancel(stopTime);
13327
+ * });
13328
+ * }
13329
+ * }]);
13330
+ * </script>
13331
+ *
13332
+ * <div>
13333
+ * <div ng-controller="ExampleController">
13334
+ * <label>Date format: <input ng-model="format"></label> <hr/>
13335
+ * Current time is: <span my-current-time="format"></span>
13336
+ * <hr/>
13337
+ * Blood 1 : <font color='red'>{{blood_1}}</font>
13338
+ * Blood 2 : <font color='red'>{{blood_2}}</font>
13339
+ * <button type="button" data-ng-click="fight()">Fight</button>
13340
+ * <button type="button" data-ng-click="stopFight()">StopFight</button>
13341
+ * <button type="button" data-ng-click="resetFight()">resetFight</button>
13342
+ * </div>
13343
+ * </div>
13344
+ *
13345
+ * </file>
13346
+ * </example>
13347
+ */
13348
+ function interval(fn, delay, count, invokeApply) {
13349
+ var hasParams = arguments.length > 4,
13350
+ args = hasParams ? sliceArgs(arguments, 4) : [],
13351
+ setInterval = $window.setInterval,
13352
+ clearInterval = $window.clearInterval,
13353
+ iteration = 0,
13354
+ skipApply = (isDefined(invokeApply) && !invokeApply),
13355
+ deferred = (skipApply ? $$q : $q).defer(),
13356
+ promise = deferred.promise;
13357
+
13358
+ count = isDefined(count) ? count : 0;
13359
+
13360
+ promise.$$intervalId = setInterval(function tick() {
13361
+ if (skipApply) {
13362
+ $browser.defer(callback);
13363
+ } else {
13364
+ $rootScope.$evalAsync(callback);
13365
+ }
13366
+ deferred.notify(iteration++);
13367
+
13368
+ if (count > 0 && iteration >= count) {
13369
+ deferred.resolve(iteration);
13370
+ clearInterval(promise.$$intervalId);
13371
+ delete intervals[promise.$$intervalId];
13372
+ }
13373
+
13374
+ if (!skipApply) $rootScope.$apply();
13375
+
13376
+ }, delay);
13377
+
13378
+ intervals[promise.$$intervalId] = deferred;
13379
+
13380
+ return promise;
13381
+
13382
+ function callback() {
13383
+ if (!hasParams) {
13384
+ fn(iteration);
13385
+ } else {
13386
+ fn.apply(null, args);
13387
+ }
13388
+ }
13389
+ }
13390
+
13391
+
13392
+ /**
13393
+ * @ngdoc method
13394
+ * @name $interval#cancel
13395
+ *
13396
+ * @description
13397
+ * Cancels a task associated with the `promise`.
13398
+ *
13399
+ * @param {Promise=} promise returned by the `$interval` function.
13400
+ * @returns {boolean} Returns `true` if the task was successfully canceled.
13401
+ */
13402
+ interval.cancel = function(promise) {
13403
+ if (promise && promise.$$intervalId in intervals) {
13404
+ // Interval cancels should not report as unhandled promise.
13405
+ markQExceptionHandled(intervals[promise.$$intervalId].promise);
13406
+ intervals[promise.$$intervalId].reject('canceled');
13407
+ $window.clearInterval(promise.$$intervalId);
13408
+ delete intervals[promise.$$intervalId];
13409
+ return true;
13410
+ }
13411
+ return false;
13412
+ };
13413
+
13414
+ return interval;
13415
+ }];
13416
+ }
13417
+
13418
+ /**
13419
+ * @ngdoc service
13420
+ * @name $jsonpCallbacks
13421
+ * @requires $window
13422
+ * @description
13423
+ * This service handles the lifecycle of callbacks to handle JSONP requests.
13424
+ * Override this service if you wish to customise where the callbacks are stored and
13425
+ * how they vary compared to the requested url.
13426
+ */
13427
+ var $jsonpCallbacksProvider = /** @this */ function() {
13428
+ this.$get = function() {
13429
+ var callbacks = angular.callbacks;
13430
+ var callbackMap = {};
13431
+
13432
+ function createCallback(callbackId) {
13433
+ var callback = function(data) {
13434
+ callback.data = data;
13435
+ callback.called = true;
13436
+ };
13437
+ callback.id = callbackId;
13438
+ return callback;
13439
+ }
13440
+
13441
+ return {
13442
+ /**
13443
+ * @ngdoc method
13444
+ * @name $jsonpCallbacks#createCallback
13445
+ * @param {string} url the url of the JSONP request
13446
+ * @returns {string} the callback path to send to the server as part of the JSONP request
13447
+ * @description
13448
+ * {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback
13449
+ * to pass to the server, which will be used to call the callback with its payload in the JSONP response.
13450
+ */
13451
+ createCallback: function(url) {
13452
+ var callbackId = '_' + (callbacks.$$counter++).toString(36);
13453
+ var callbackPath = 'angular.callbacks.' + callbackId;
13454
+ var callback = createCallback(callbackId);
13455
+ callbackMap[callbackPath] = callbacks[callbackId] = callback;
13456
+ return callbackPath;
13457
+ },
13458
+ /**
13459
+ * @ngdoc method
13460
+ * @name $jsonpCallbacks#wasCalled
13461
+ * @param {string} callbackPath the path to the callback that was sent in the JSONP request
13462
+ * @returns {boolean} whether the callback has been called, as a result of the JSONP response
13463
+ * @description
13464
+ * {@link $httpBackend} calls this method to find out whether the JSONP response actually called the
13465
+ * callback that was passed in the request.
13466
+ */
13467
+ wasCalled: function(callbackPath) {
13468
+ return callbackMap[callbackPath].called;
13469
+ },
13470
+ /**
13471
+ * @ngdoc method
13472
+ * @name $jsonpCallbacks#getResponse
13473
+ * @param {string} callbackPath the path to the callback that was sent in the JSONP request
13474
+ * @returns {*} the data received from the response via the registered callback
13475
+ * @description
13476
+ * {@link $httpBackend} calls this method to get hold of the data that was provided to the callback
13477
+ * in the JSONP response.
13478
+ */
13479
+ getResponse: function(callbackPath) {
13480
+ return callbackMap[callbackPath].data;
13481
+ },
13482
+ /**
13483
+ * @ngdoc method
13484
+ * @name $jsonpCallbacks#removeCallback
13485
+ * @param {string} callbackPath the path to the callback that was sent in the JSONP request
13486
+ * @description
13487
+ * {@link $httpBackend} calls this method to remove the callback after the JSONP request has
13488
+ * completed or timed-out.
13489
+ */
13490
+ removeCallback: function(callbackPath) {
13491
+ var callback = callbackMap[callbackPath];
13492
+ delete callbacks[callback.id];
13493
+ delete callbackMap[callbackPath];
13494
+ }
13495
+ };
13496
+ };
13497
+ };
13498
+
13499
+ /**
13500
+ * @ngdoc service
13501
+ * @name $locale
13502
+ *
13503
+ * @description
13504
+ * $locale service provides localization rules for various Angular components. As of right now the
13505
+ * only public api is:
13506
+ *
13507
+ * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
13508
+ */
13509
+
13510
+ var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/,
13511
+ DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
13512
+ var $locationMinErr = minErr('$location');
13513
+
13514
+
13515
+ /**
13516
+ * Encode path using encodeUriSegment, ignoring forward slashes
13517
+ *
13518
+ * @param {string} path Path to encode
13519
+ * @returns {string}
13520
+ */
13521
+ function encodePath(path) {
13522
+ var segments = path.split('/'),
13523
+ i = segments.length;
13524
+
13525
+ while (i--) {
13526
+ segments[i] = encodeUriSegment(segments[i]);
13527
+ }
13528
+
13529
+ return segments.join('/');
13530
+ }
13531
+
13532
+ function parseAbsoluteUrl(absoluteUrl, locationObj) {
13533
+ var parsedUrl = urlResolve(absoluteUrl);
13534
+
13535
+ locationObj.$$protocol = parsedUrl.protocol;
13536
+ locationObj.$$host = parsedUrl.hostname;
13537
+ locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
13538
+ }
13539
+
13540
+ var DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/;
13541
+ function parseAppUrl(url, locationObj) {
13542
+
13543
+ if (DOUBLE_SLASH_REGEX.test(url)) {
13544
+ throw $locationMinErr('badpath', 'Invalid url "{0}".', url);
13545
+ }
13546
+
13547
+ var prefixed = (url.charAt(0) !== '/');
13548
+ if (prefixed) {
13549
+ url = '/' + url;
13550
+ }
13551
+ var match = urlResolve(url);
13552
+ locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
13553
+ match.pathname.substring(1) : match.pathname);
13554
+ locationObj.$$search = parseKeyValue(match.search);
13555
+ locationObj.$$hash = decodeURIComponent(match.hash);
13556
+
13557
+ // make sure path starts with '/';
13558
+ if (locationObj.$$path && locationObj.$$path.charAt(0) !== '/') {
13559
+ locationObj.$$path = '/' + locationObj.$$path;
13560
+ }
13561
+ }
13562
+
13563
+ function startsWith(str, search) {
13564
+ return str.slice(0, search.length) === search;
13565
+ }
13566
+
13567
+ /**
13568
+ *
13569
+ * @param {string} base
13570
+ * @param {string} url
13571
+ * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with
13572
+ * the expected string.
13573
+ */
13574
+ function stripBaseUrl(base, url) {
13575
+ if (startsWith(url, base)) {
13576
+ return url.substr(base.length);
13577
+ }
13578
+ }
13579
+
13580
+
13581
+ function stripHash(url) {
13582
+ var index = url.indexOf('#');
13583
+ return index === -1 ? url : url.substr(0, index);
13584
+ }
13585
+
13586
+ function trimEmptyHash(url) {
13587
+ return url.replace(/(#.+)|#$/, '$1');
13588
+ }
13589
+
13590
+
13591
+ function stripFile(url) {
13592
+ return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
13593
+ }
13594
+
13595
+ /* return the server only (scheme://host:port) */
13596
+ function serverBase(url) {
13597
+ return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
13598
+ }
13599
+
13600
+
13601
+ /**
13602
+ * LocationHtml5Url represents a URL
13603
+ * This object is exposed as $location service when HTML5 mode is enabled and supported
13604
+ *
13605
+ * @constructor
13606
+ * @param {string} appBase application base URL
13607
+ * @param {string} appBaseNoFile application base URL stripped of any filename
13608
+ * @param {string} basePrefix URL path prefix
13609
+ */
13610
+ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
13611
+ this.$$html5 = true;
13612
+ basePrefix = basePrefix || '';
13613
+ parseAbsoluteUrl(appBase, this);
13614
+
13615
+
13616
+ /**
13617
+ * Parse given HTML5 (regular) URL string into properties
13618
+ * @param {string} url HTML5 URL
13619
+ * @private
13620
+ */
13621
+ this.$$parse = function(url) {
13622
+ var pathUrl = stripBaseUrl(appBaseNoFile, url);
13623
+ if (!isString(pathUrl)) {
13624
+ throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
13625
+ appBaseNoFile);
13626
+ }
13627
+
13628
+ parseAppUrl(pathUrl, this);
13629
+
13630
+ if (!this.$$path) {
13631
+ this.$$path = '/';
13632
+ }
13633
+
13634
+ this.$$compose();
13635
+ };
13636
+
13637
+ /**
13638
+ * Compose url and update `absUrl` property
13639
+ * @private
13640
+ */
13641
+ this.$$compose = function() {
13642
+ var search = toKeyValue(this.$$search),
13643
+ hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
13644
+
13645
+ this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
13646
+ this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
13647
+
13648
+ this.$$urlUpdatedByLocation = true;
13649
+ };
13650
+
13651
+ this.$$parseLinkUrl = function(url, relHref) {
13652
+ if (relHref && relHref[0] === '#') {
13653
+ // special case for links to hash fragments:
13654
+ // keep the old url and only replace the hash fragment
13655
+ this.hash(relHref.slice(1));
13656
+ return true;
13657
+ }
13658
+ var appUrl, prevAppUrl;
13659
+ var rewrittenUrl;
13660
+
13661
+
13662
+ if (isDefined(appUrl = stripBaseUrl(appBase, url))) {
13663
+ prevAppUrl = appUrl;
13664
+ if (basePrefix && isDefined(appUrl = stripBaseUrl(basePrefix, appUrl))) {
13665
+ rewrittenUrl = appBaseNoFile + (stripBaseUrl('/', appUrl) || appUrl);
13666
+ } else {
13667
+ rewrittenUrl = appBase + prevAppUrl;
13668
+ }
13669
+ } else if (isDefined(appUrl = stripBaseUrl(appBaseNoFile, url))) {
13670
+ rewrittenUrl = appBaseNoFile + appUrl;
13671
+ } else if (appBaseNoFile === url + '/') {
13672
+ rewrittenUrl = appBaseNoFile;
13673
+ }
13674
+ if (rewrittenUrl) {
13675
+ this.$$parse(rewrittenUrl);
13676
+ }
13677
+ return !!rewrittenUrl;
13678
+ };
13679
+ }
13680
+
13681
+
13682
+ /**
13683
+ * LocationHashbangUrl represents URL
13684
+ * This object is exposed as $location service when developer doesn't opt into html5 mode.
13685
+ * It also serves as the base class for html5 mode fallback on legacy browsers.
13686
+ *
13687
+ * @constructor
13688
+ * @param {string} appBase application base URL
13689
+ * @param {string} appBaseNoFile application base URL stripped of any filename
13690
+ * @param {string} hashPrefix hashbang prefix
13691
+ */
13692
+ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
13693
+
13694
+ parseAbsoluteUrl(appBase, this);
13695
+
13696
+
13697
+ /**
13698
+ * Parse given hashbang URL into properties
13699
+ * @param {string} url Hashbang URL
13700
+ * @private
13701
+ */
13702
+ this.$$parse = function(url) {
13703
+ var withoutBaseUrl = stripBaseUrl(appBase, url) || stripBaseUrl(appBaseNoFile, url);
13704
+ var withoutHashUrl;
13705
+
13706
+ if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
13707
+
13708
+ // The rest of the URL starts with a hash so we have
13709
+ // got either a hashbang path or a plain hash fragment
13710
+ withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl);
13711
+ if (isUndefined(withoutHashUrl)) {
13712
+ // There was no hashbang prefix so we just have a hash fragment
13713
+ withoutHashUrl = withoutBaseUrl;
13714
+ }
13715
+
13716
+ } else {
13717
+ // There was no hashbang path nor hash fragment:
13718
+ // If we are in HTML5 mode we use what is left as the path;
13719
+ // Otherwise we ignore what is left
13720
+ if (this.$$html5) {
13721
+ withoutHashUrl = withoutBaseUrl;
13722
+ } else {
13723
+ withoutHashUrl = '';
13724
+ if (isUndefined(withoutBaseUrl)) {
13725
+ appBase = url;
13726
+ /** @type {?} */ (this).replace();
13727
+ }
13728
+ }
13729
+ }
13730
+
13731
+ parseAppUrl(withoutHashUrl, this);
13732
+
13733
+ this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
13734
+
13735
+ this.$$compose();
13736
+
13737
+ /*
13738
+ * In Windows, on an anchor node on documents loaded from
13739
+ * the filesystem, the browser will return a pathname
13740
+ * prefixed with the drive name ('/C:/path') when a
13741
+ * pathname without a drive is set:
13742
+ * * a.setAttribute('href', '/foo')
13743
+ * * a.pathname === '/C:/foo' //true
13744
+ *
13745
+ * Inside of Angular, we're always using pathnames that
13746
+ * do not include drive names for routing.
13747
+ */
13748
+ function removeWindowsDriveName(path, url, base) {
13749
+ /*
13750
+ Matches paths for file protocol on windows,
13751
+ such as /C:/foo/bar, and captures only /foo/bar.
13752
+ */
13753
+ var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
13754
+
13755
+ var firstPathSegmentMatch;
13756
+
13757
+ //Get the relative path from the input URL.
13758
+ if (startsWith(url, base)) {
13759
+ url = url.replace(base, '');
13760
+ }
13761
+
13762
+ // The input URL intentionally contains a first path segment that ends with a colon.
13763
+ if (windowsFilePathExp.exec(url)) {
13764
+ return path;
13765
+ }
13766
+
13767
+ firstPathSegmentMatch = windowsFilePathExp.exec(path);
13768
+ return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
13769
+ }
13770
+ };
13771
+
13772
+ /**
13773
+ * Compose hashbang URL and update `absUrl` property
13774
+ * @private
13775
+ */
13776
+ this.$$compose = function() {
13777
+ var search = toKeyValue(this.$$search),
13778
+ hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
13779
+
13780
+ this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
13781
+ this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
13782
+
13783
+ this.$$urlUpdatedByLocation = true;
13784
+ };
13785
+
13786
+ this.$$parseLinkUrl = function(url, relHref) {
13787
+ if (stripHash(appBase) === stripHash(url)) {
13788
+ this.$$parse(url);
13789
+ return true;
13790
+ }
13791
+ return false;
13792
+ };
13793
+ }
13794
+
13795
+
13796
+ /**
13797
+ * LocationHashbangUrl represents URL
13798
+ * This object is exposed as $location service when html5 history api is enabled but the browser
13799
+ * does not support it.
13800
+ *
13801
+ * @constructor
13802
+ * @param {string} appBase application base URL
13803
+ * @param {string} appBaseNoFile application base URL stripped of any filename
13804
+ * @param {string} hashPrefix hashbang prefix
13805
+ */
13806
+ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
13807
+ this.$$html5 = true;
13808
+ LocationHashbangUrl.apply(this, arguments);
13809
+
13810
+ this.$$parseLinkUrl = function(url, relHref) {
13811
+ if (relHref && relHref[0] === '#') {
13812
+ // special case for links to hash fragments:
13813
+ // keep the old url and only replace the hash fragment
13814
+ this.hash(relHref.slice(1));
13815
+ return true;
13816
+ }
13817
+
13818
+ var rewrittenUrl;
13819
+ var appUrl;
13820
+
13821
+ if (appBase === stripHash(url)) {
13822
+ rewrittenUrl = url;
13823
+ } else if ((appUrl = stripBaseUrl(appBaseNoFile, url))) {
13824
+ rewrittenUrl = appBase + hashPrefix + appUrl;
13825
+ } else if (appBaseNoFile === url + '/') {
13826
+ rewrittenUrl = appBaseNoFile;
13827
+ }
13828
+ if (rewrittenUrl) {
13829
+ this.$$parse(rewrittenUrl);
13830
+ }
13831
+ return !!rewrittenUrl;
13832
+ };
13833
+
13834
+ this.$$compose = function() {
13835
+ var search = toKeyValue(this.$$search),
13836
+ hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
13837
+
13838
+ this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
13839
+ // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
13840
+ this.$$absUrl = appBase + hashPrefix + this.$$url;
13841
+
13842
+ this.$$urlUpdatedByLocation = true;
13843
+ };
13844
+
13845
+ }
13846
+
13847
+
13848
+ var locationPrototype = {
13849
+
13850
+ /**
13851
+ * Ensure absolute URL is initialized.
13852
+ * @private
13853
+ */
13854
+ $$absUrl:'',
13855
+
13856
+ /**
13857
+ * Are we in html5 mode?
13858
+ * @private
13859
+ */
13860
+ $$html5: false,
13861
+
13862
+ /**
13863
+ * Has any change been replacing?
13864
+ * @private
13865
+ */
13866
+ $$replace: false,
13867
+
13868
+ /**
13869
+ * @ngdoc method
13870
+ * @name $location#absUrl
13871
+ *
13872
+ * @description
13873
+ * This method is getter only.
13874
+ *
13875
+ * Return full URL representation with all segments encoded according to rules specified in
13876
+ * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
13877
+ *
13878
+ *
13879
+ * ```js
13880
+ * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13881
+ * var absUrl = $location.absUrl();
13882
+ * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
13883
+ * ```
13884
+ *
13885
+ * @return {string} full URL
13886
+ */
13887
+ absUrl: locationGetter('$$absUrl'),
13888
+
13889
+ /**
13890
+ * @ngdoc method
13891
+ * @name $location#url
13892
+ *
13893
+ * @description
13894
+ * This method is getter / setter.
13895
+ *
13896
+ * Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
13897
+ *
13898
+ * Change path, search and hash, when called with parameter and return `$location`.
13899
+ *
13900
+ *
13901
+ * ```js
13902
+ * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13903
+ * var url = $location.url();
13904
+ * // => "/some/path?foo=bar&baz=xoxo"
13905
+ * ```
13906
+ *
13907
+ * @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`)
13908
+ * @return {string} url
13909
+ */
13910
+ url: function(url) {
13911
+ if (isUndefined(url)) {
13912
+ return this.$$url;
13913
+ }
13914
+
13915
+ var match = PATH_MATCH.exec(url);
13916
+ if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
13917
+ if (match[2] || match[1] || url === '') this.search(match[3] || '');
13918
+ this.hash(match[5] || '');
13919
+
13920
+ return this;
13921
+ },
13922
+
13923
+ /**
13924
+ * @ngdoc method
13925
+ * @name $location#protocol
13926
+ *
13927
+ * @description
13928
+ * This method is getter only.
13929
+ *
13930
+ * Return protocol of current URL.
13931
+ *
13932
+ *
13933
+ * ```js
13934
+ * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13935
+ * var protocol = $location.protocol();
13936
+ * // => "http"
13937
+ * ```
13938
+ *
13939
+ * @return {string} protocol of current URL
13940
+ */
13941
+ protocol: locationGetter('$$protocol'),
13942
+
13943
+ /**
13944
+ * @ngdoc method
13945
+ * @name $location#host
13946
+ *
13947
+ * @description
13948
+ * This method is getter only.
13949
+ *
13950
+ * Return host of current URL.
13951
+ *
13952
+ * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
13953
+ *
13954
+ *
13955
+ * ```js
13956
+ * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13957
+ * var host = $location.host();
13958
+ * // => "example.com"
13959
+ *
13960
+ * // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
13961
+ * host = $location.host();
13962
+ * // => "example.com"
13963
+ * host = location.host;
13964
+ * // => "example.com:8080"
13965
+ * ```
13966
+ *
13967
+ * @return {string} host of current URL.
13968
+ */
13969
+ host: locationGetter('$$host'),
13970
+
13971
+ /**
13972
+ * @ngdoc method
13973
+ * @name $location#port
13974
+ *
13975
+ * @description
13976
+ * This method is getter only.
13977
+ *
13978
+ * Return port of current URL.
13979
+ *
13980
+ *
13981
+ * ```js
13982
+ * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13983
+ * var port = $location.port();
13984
+ * // => 80
13985
+ * ```
13986
+ *
13987
+ * @return {Number} port
13988
+ */
13989
+ port: locationGetter('$$port'),
13990
+
13991
+ /**
13992
+ * @ngdoc method
13993
+ * @name $location#path
13994
+ *
13995
+ * @description
13996
+ * This method is getter / setter.
13997
+ *
13998
+ * Return path of current URL when called without any parameter.
13999
+ *
14000
+ * Change path when called with parameter and return `$location`.
14001
+ *
14002
+ * Note: Path should always begin with forward slash (/), this method will add the forward slash
14003
+ * if it is missing.
14004
+ *
14005
+ *
14006
+ * ```js
14007
+ * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
14008
+ * var path = $location.path();
14009
+ * // => "/some/path"
14010
+ * ```
14011
+ *
14012
+ * @param {(string|number)=} path New path
14013
+ * @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter
14014
+ */
14015
+ path: locationGetterSetter('$$path', function(path) {
14016
+ path = path !== null ? path.toString() : '';
14017
+ return path.charAt(0) === '/' ? path : '/' + path;
14018
+ }),
14019
+
14020
+ /**
14021
+ * @ngdoc method
14022
+ * @name $location#search
14023
+ *
14024
+ * @description
14025
+ * This method is getter / setter.
14026
+ *
14027
+ * Return search part (as object) of current URL when called without any parameter.
14028
+ *
14029
+ * Change search part when called with parameter and return `$location`.
14030
+ *
14031
+ *
14032
+ * ```js
14033
+ * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
14034
+ * var searchObject = $location.search();
14035
+ * // => {foo: 'bar', baz: 'xoxo'}
14036
+ *
14037
+ * // set foo to 'yipee'
14038
+ * $location.search('foo', 'yipee');
14039
+ * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
14040
+ * ```
14041
+ *
14042
+ * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
14043
+ * hash object.
14044
+ *
14045
+ * When called with a single argument the method acts as a setter, setting the `search` component
14046
+ * of `$location` to the specified value.
14047
+ *
14048
+ * If the argument is a hash object containing an array of values, these values will be encoded
14049
+ * as duplicate search parameters in the URL.
14050
+ *
14051
+ * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
14052
+ * will override only a single search property.
14053
+ *
14054
+ * If `paramValue` is an array, it will override the property of the `search` component of
14055
+ * `$location` specified via the first argument.
14056
+ *
14057
+ * If `paramValue` is `null`, the property specified via the first argument will be deleted.
14058
+ *
14059
+ * If `paramValue` is `true`, the property specified via the first argument will be added with no
14060
+ * value nor trailing equal sign.
14061
+ *
14062
+ * @return {Object} If called with no arguments returns the parsed `search` object. If called with
14063
+ * one or more arguments returns `$location` object itself.
14064
+ */
14065
+ search: function(search, paramValue) {
14066
+ switch (arguments.length) {
14067
+ case 0:
14068
+ return this.$$search;
14069
+ case 1:
14070
+ if (isString(search) || isNumber(search)) {
14071
+ search = search.toString();
14072
+ this.$$search = parseKeyValue(search);
14073
+ } else if (isObject(search)) {
14074
+ search = copy(search, {});
14075
+ // remove object undefined or null properties
14076
+ forEach(search, function(value, key) {
14077
+ if (value == null) delete search[key];
14078
+ });
14079
+
14080
+ this.$$search = search;
14081
+ } else {
14082
+ throw $locationMinErr('isrcharg',
14083
+ 'The first argument of the `$location#search()` call must be a string or an object.');
14084
+ }
14085
+ break;
14086
+ default:
14087
+ if (isUndefined(paramValue) || paramValue === null) {
14088
+ delete this.$$search[search];
14089
+ } else {
14090
+ this.$$search[search] = paramValue;
14091
+ }
14092
+ }
14093
+
14094
+ this.$$compose();
14095
+ return this;
14096
+ },
14097
+
14098
+ /**
14099
+ * @ngdoc method
14100
+ * @name $location#hash
14101
+ *
14102
+ * @description
14103
+ * This method is getter / setter.
14104
+ *
14105
+ * Returns the hash fragment when called without any parameters.
14106
+ *
14107
+ * Changes the hash fragment when called with a parameter and returns `$location`.
14108
+ *
14109
+ *
14110
+ * ```js
14111
+ * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
14112
+ * var hash = $location.hash();
14113
+ * // => "hashValue"
14114
+ * ```
14115
+ *
14116
+ * @param {(string|number)=} hash New hash fragment
14117
+ * @return {string} hash
14118
+ */
14119
+ hash: locationGetterSetter('$$hash', function(hash) {
14120
+ return hash !== null ? hash.toString() : '';
14121
+ }),
14122
+
14123
+ /**
14124
+ * @ngdoc method
14125
+ * @name $location#replace
14126
+ *
14127
+ * @description
14128
+ * If called, all changes to $location during the current `$digest` will replace the current history
14129
+ * record, instead of adding a new one.
14130
+ */
14131
+ replace: function() {
14132
+ this.$$replace = true;
14133
+ return this;
14134
+ }
14135
+ };
14136
+
14137
+ forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
14138
+ Location.prototype = Object.create(locationPrototype);
14139
+
14140
+ /**
14141
+ * @ngdoc method
14142
+ * @name $location#state
14143
+ *
14144
+ * @description
14145
+ * This method is getter / setter.
14146
+ *
14147
+ * Return the history state object when called without any parameter.
14148
+ *
14149
+ * Change the history state object when called with one parameter and return `$location`.
14150
+ * The state object is later passed to `pushState` or `replaceState`.
14151
+ *
14152
+ * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
14153
+ * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
14154
+ * older browsers (like IE9 or Android < 4.0), don't use this method.
14155
+ *
14156
+ * @param {object=} state State object for pushState or replaceState
14157
+ * @return {object} state
14158
+ */
14159
+ Location.prototype.state = function(state) {
14160
+ if (!arguments.length) {
14161
+ return this.$$state;
14162
+ }
14163
+
14164
+ if (Location !== LocationHtml5Url || !this.$$html5) {
14165
+ throw $locationMinErr('nostate', 'History API state support is available only ' +
14166
+ 'in HTML5 mode and only in browsers supporting HTML5 History API');
14167
+ }
14168
+ // The user might modify `stateObject` after invoking `$location.state(stateObject)`
14169
+ // but we're changing the $$state reference to $browser.state() during the $digest
14170
+ // so the modification window is narrow.
14171
+ this.$$state = isUndefined(state) ? null : state;
14172
+ this.$$urlUpdatedByLocation = true;
14173
+
14174
+ return this;
14175
+ };
14176
+ });
14177
+
14178
+
14179
+ function locationGetter(property) {
14180
+ return /** @this */ function() {
14181
+ return this[property];
14182
+ };
14183
+ }
14184
+
14185
+
14186
+ function locationGetterSetter(property, preprocess) {
14187
+ return /** @this */ function(value) {
14188
+ if (isUndefined(value)) {
14189
+ return this[property];
14190
+ }
14191
+
14192
+ this[property] = preprocess(value);
14193
+ this.$$compose();
14194
+
14195
+ return this;
14196
+ };
14197
+ }
14198
+
14199
+
14200
+ /**
14201
+ * @ngdoc service
14202
+ * @name $location
14203
+ *
14204
+ * @requires $rootElement
14205
+ *
14206
+ * @description
14207
+ * The $location service parses the URL in the browser address bar (based on the
14208
+ * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
14209
+ * available to your application. Changes to the URL in the address bar are reflected into
14210
+ * $location service and changes to $location are reflected into the browser address bar.
14211
+ *
14212
+ * **The $location service:**
14213
+ *
14214
+ * - Exposes the current URL in the browser address bar, so you can
14215
+ * - Watch and observe the URL.
14216
+ * - Change the URL.
14217
+ * - Synchronizes the URL with the browser when the user
14218
+ * - Changes the address bar.
14219
+ * - Clicks the back or forward button (or clicks a History link).
14220
+ * - Clicks on a link.
14221
+ * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
14222
+ *
14223
+ * For more information see {@link guide/$location Developer Guide: Using $location}
14224
+ */
14225
+
14226
+ /**
14227
+ * @ngdoc provider
14228
+ * @name $locationProvider
14229
+ * @this
14230
+ *
14231
+ * @description
14232
+ * Use the `$locationProvider` to configure how the application deep linking paths are stored.
14233
+ */
14234
+ function $LocationProvider() {
14235
+ var hashPrefix = '!',
14236
+ html5Mode = {
14237
+ enabled: false,
14238
+ requireBase: true,
14239
+ rewriteLinks: true
14240
+ };
14241
+
14242
+ /**
14243
+ * @ngdoc method
14244
+ * @name $locationProvider#hashPrefix
14245
+ * @description
14246
+ * The default value for the prefix is `'!'`.
14247
+ * @param {string=} prefix Prefix for hash part (containing path and search)
14248
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
14249
+ */
14250
+ this.hashPrefix = function(prefix) {
14251
+ if (isDefined(prefix)) {
14252
+ hashPrefix = prefix;
14253
+ return this;
14254
+ } else {
14255
+ return hashPrefix;
14256
+ }
14257
+ };
14258
+
14259
+ /**
14260
+ * @ngdoc method
14261
+ * @name $locationProvider#html5Mode
14262
+ * @description
14263
+ * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
14264
+ * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
14265
+ * properties:
14266
+ * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
14267
+ * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
14268
+ * support `pushState`.
14269
+ * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
14270
+ * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
14271
+ * true, and a base tag is not present, an error will be thrown when `$location` is injected.
14272
+ * See the {@link guide/$location $location guide for more information}
14273
+ * - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled,
14274
+ * enables/disables URL rewriting for relative links. If set to a string, URL rewriting will
14275
+ * only happen on links with an attribute that matches the given string. For example, if set
14276
+ * to `'internal-link'`, then the URL will only be rewritten for `<a internal-link>` links.
14277
+ * Note that [attribute name normalization](guide/directive#normalization) does not apply
14278
+ * here, so `'internalLink'` will **not** match `'internal-link'`.
14279
+ *
14280
+ * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
14281
+ */
14282
+ this.html5Mode = function(mode) {
14283
+ if (isBoolean(mode)) {
14284
+ html5Mode.enabled = mode;
14285
+ return this;
14286
+ } else if (isObject(mode)) {
14287
+
14288
+ if (isBoolean(mode.enabled)) {
14289
+ html5Mode.enabled = mode.enabled;
14290
+ }
14291
+
14292
+ if (isBoolean(mode.requireBase)) {
14293
+ html5Mode.requireBase = mode.requireBase;
14294
+ }
14295
+
14296
+ if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) {
14297
+ html5Mode.rewriteLinks = mode.rewriteLinks;
14298
+ }
14299
+
14300
+ return this;
14301
+ } else {
14302
+ return html5Mode;
14303
+ }
14304
+ };
14305
+
14306
+ /**
14307
+ * @ngdoc event
14308
+ * @name $location#$locationChangeStart
14309
+ * @eventType broadcast on root scope
14310
+ * @description
14311
+ * Broadcasted before a URL will change.
14312
+ *
14313
+ * This change can be prevented by calling
14314
+ * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
14315
+ * details about event object. Upon successful change
14316
+ * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
14317
+ *
14318
+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
14319
+ * the browser supports the HTML5 History API.
14320
+ *
14321
+ * @param {Object} angularEvent Synthetic event object.
14322
+ * @param {string} newUrl New URL
14323
+ * @param {string=} oldUrl URL that was before it was changed.
14324
+ * @param {string=} newState New history state object
14325
+ * @param {string=} oldState History state object that was before it was changed.
14326
+ */
14327
+
14328
+ /**
14329
+ * @ngdoc event
14330
+ * @name $location#$locationChangeSuccess
14331
+ * @eventType broadcast on root scope
14332
+ * @description
14333
+ * Broadcasted after a URL was changed.
14334
+ *
14335
+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
14336
+ * the browser supports the HTML5 History API.
14337
+ *
14338
+ * @param {Object} angularEvent Synthetic event object.
14339
+ * @param {string} newUrl New URL
14340
+ * @param {string=} oldUrl URL that was before it was changed.
14341
+ * @param {string=} newState New history state object
14342
+ * @param {string=} oldState History state object that was before it was changed.
14343
+ */
14344
+
14345
+ this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
14346
+ function($rootScope, $browser, $sniffer, $rootElement, $window) {
14347
+ var $location,
14348
+ LocationMode,
14349
+ baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
14350
+ initialUrl = $browser.url(),
14351
+ appBase;
14352
+
14353
+ if (html5Mode.enabled) {
14354
+ if (!baseHref && html5Mode.requireBase) {
14355
+ throw $locationMinErr('nobase',
14356
+ '$location in HTML5 mode requires a <base> tag to be present!');
14357
+ }
14358
+ appBase = serverBase(initialUrl) + (baseHref || '/');
14359
+ LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
14360
+ } else {
14361
+ appBase = stripHash(initialUrl);
14362
+ LocationMode = LocationHashbangUrl;
14363
+ }
14364
+ var appBaseNoFile = stripFile(appBase);
14365
+
14366
+ $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
14367
+ $location.$$parseLinkUrl(initialUrl, initialUrl);
14368
+
14369
+ $location.$$state = $browser.state();
14370
+
14371
+ var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
14372
+
14373
+ function setBrowserUrlWithFallback(url, replace, state) {
14374
+ var oldUrl = $location.url();
14375
+ var oldState = $location.$$state;
14376
+ try {
14377
+ $browser.url(url, replace, state);
14378
+
14379
+ // Make sure $location.state() returns referentially identical (not just deeply equal)
14380
+ // state object; this makes possible quick checking if the state changed in the digest
14381
+ // loop. Checking deep equality would be too expensive.
14382
+ $location.$$state = $browser.state();
14383
+ } catch (e) {
14384
+ // Restore old values if pushState fails
14385
+ $location.url(oldUrl);
14386
+ $location.$$state = oldState;
14387
+
14388
+ throw e;
14389
+ }
14390
+ }
14391
+
14392
+ $rootElement.on('click', function(event) {
14393
+ var rewriteLinks = html5Mode.rewriteLinks;
14394
+ // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
14395
+ // currently we open nice url link and redirect then
14396
+
14397
+ if (!rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return;
14398
+
14399
+ var elm = jqLite(event.target);
14400
+
14401
+ // traverse the DOM up to find first A tag
14402
+ while (nodeName_(elm[0]) !== 'a') {
14403
+ // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
14404
+ if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
14405
+ }
14406
+
14407
+ if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks))) return;
14408
+
14409
+ var absHref = elm.prop('href');
14410
+ // get the actual href attribute - see
14411
+ // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
14412
+ var relHref = elm.attr('href') || elm.attr('xlink:href');
14413
+
14414
+ if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
14415
+ // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
14416
+ // an animation.
14417
+ absHref = urlResolve(absHref.animVal).href;
14418
+ }
14419
+
14420
+ // Ignore when url is started with javascript: or mailto:
14421
+ if (IGNORE_URI_REGEXP.test(absHref)) return;
14422
+
14423
+ if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
14424
+ if ($location.$$parseLinkUrl(absHref, relHref)) {
14425
+ // We do a preventDefault for all urls that are part of the angular application,
14426
+ // in html5mode and also without, so that we are able to abort navigation without
14427
+ // getting double entries in the location history.
14428
+ event.preventDefault();
14429
+ // update location manually
14430
+ if ($location.absUrl() !== $browser.url()) {
14431
+ $rootScope.$apply();
14432
+ // hack to work around FF6 bug 684208 when scenario runner clicks on links
14433
+ $window.angular['ff-684208-preventDefault'] = true;
14434
+ }
14435
+ }
14436
+ }
14437
+ });
14438
+
14439
+
14440
+ // rewrite hashbang url <> html5 url
14441
+ if (trimEmptyHash($location.absUrl()) !== trimEmptyHash(initialUrl)) {
14442
+ $browser.url($location.absUrl(), true);
14443
+ }
14444
+
14445
+ var initializing = true;
14446
+
14447
+ // update $location when $browser url changes
14448
+ $browser.onUrlChange(function(newUrl, newState) {
14449
+
14450
+ if (!startsWith(newUrl, appBaseNoFile)) {
14451
+ // If we are navigating outside of the app then force a reload
14452
+ $window.location.href = newUrl;
14453
+ return;
14454
+ }
14455
+
14456
+ $rootScope.$evalAsync(function() {
14457
+ var oldUrl = $location.absUrl();
14458
+ var oldState = $location.$$state;
14459
+ var defaultPrevented;
14460
+ newUrl = trimEmptyHash(newUrl);
14461
+ $location.$$parse(newUrl);
14462
+ $location.$$state = newState;
14463
+
14464
+ defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
14465
+ newState, oldState).defaultPrevented;
14466
+
14467
+ // if the location was changed by a `$locationChangeStart` handler then stop
14468
+ // processing this location change
14469
+ if ($location.absUrl() !== newUrl) return;
14470
+
14471
+ if (defaultPrevented) {
14472
+ $location.$$parse(oldUrl);
14473
+ $location.$$state = oldState;
14474
+ setBrowserUrlWithFallback(oldUrl, false, oldState);
14475
+ } else {
14476
+ initializing = false;
14477
+ afterLocationChange(oldUrl, oldState);
14478
+ }
14479
+ });
14480
+ if (!$rootScope.$$phase) $rootScope.$digest();
14481
+ });
14482
+
14483
+ // update browser
14484
+ $rootScope.$watch(function $locationWatch() {
14485
+ if (initializing || $location.$$urlUpdatedByLocation) {
14486
+ $location.$$urlUpdatedByLocation = false;
14487
+
14488
+ var oldUrl = trimEmptyHash($browser.url());
14489
+ var newUrl = trimEmptyHash($location.absUrl());
14490
+ var oldState = $browser.state();
14491
+ var currentReplace = $location.$$replace;
14492
+ var urlOrStateChanged = oldUrl !== newUrl ||
14493
+ ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
14494
+
14495
+ if (initializing || urlOrStateChanged) {
14496
+ initializing = false;
14497
+
14498
+ $rootScope.$evalAsync(function() {
14499
+ var newUrl = $location.absUrl();
14500
+ var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
14501
+ $location.$$state, oldState).defaultPrevented;
14502
+
14503
+ // if the location was changed by a `$locationChangeStart` handler then stop
14504
+ // processing this location change
14505
+ if ($location.absUrl() !== newUrl) return;
14506
+
14507
+ if (defaultPrevented) {
14508
+ $location.$$parse(oldUrl);
14509
+ $location.$$state = oldState;
14510
+ } else {
14511
+ if (urlOrStateChanged) {
14512
+ setBrowserUrlWithFallback(newUrl, currentReplace,
14513
+ oldState === $location.$$state ? null : $location.$$state);
14514
+ }
14515
+ afterLocationChange(oldUrl, oldState);
14516
+ }
14517
+ });
14518
+ }
14519
+ }
14520
+
14521
+ $location.$$replace = false;
14522
+
14523
+ // we don't need to return anything because $evalAsync will make the digest loop dirty when
14524
+ // there is a change
14525
+ });
14526
+
14527
+ return $location;
14528
+
14529
+ function afterLocationChange(oldUrl, oldState) {
14530
+ $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
14531
+ $location.$$state, oldState);
14532
+ }
14533
+ }];
14534
+ }
14535
+
14536
+ /**
14537
+ * @ngdoc service
14538
+ * @name $log
14539
+ * @requires $window
14540
+ *
14541
+ * @description
14542
+ * Simple service for logging. Default implementation safely writes the message
14543
+ * into the browser's console (if present).
14544
+ *
14545
+ * The main purpose of this service is to simplify debugging and troubleshooting.
14546
+ *
14547
+ * To reveal the location of the calls to `$log` in the JavaScript console,
14548
+ * you can "blackbox" the AngularJS source in your browser:
14549
+ *
14550
+ * [Mozilla description of blackboxing](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Black_box_a_source).
14551
+ * [Chrome description of blackboxing](https://developer.chrome.com/devtools/docs/blackboxing).
14552
+ *
14553
+ * Note: Not all browsers support blackboxing.
14554
+ *
14555
+ * The default is to log `debug` messages. You can use
14556
+ * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
14557
+ *
14558
+ * @example
14559
+ <example module="logExample" name="log-service">
14560
+ <file name="script.js">
14561
+ angular.module('logExample', [])
14562
+ .controller('LogController', ['$scope', '$log', function($scope, $log) {
14563
+ $scope.$log = $log;
14564
+ $scope.message = 'Hello World!';
14565
+ }]);
14566
+ </file>
14567
+ <file name="index.html">
14568
+ <div ng-controller="LogController">
14569
+ <p>Reload this page with open console, enter text and hit the log button...</p>
14570
+ <label>Message:
14571
+ <input type="text" ng-model="message" /></label>
14572
+ <button ng-click="$log.log(message)">log</button>
14573
+ <button ng-click="$log.warn(message)">warn</button>
14574
+ <button ng-click="$log.info(message)">info</button>
14575
+ <button ng-click="$log.error(message)">error</button>
14576
+ <button ng-click="$log.debug(message)">debug</button>
14577
+ </div>
14578
+ </file>
14579
+ </example>
14580
+ */
14581
+
14582
+ /**
14583
+ * @ngdoc provider
14584
+ * @name $logProvider
14585
+ * @this
14586
+ *
14587
+ * @description
14588
+ * Use the `$logProvider` to configure how the application logs messages
14589
+ */
14590
+ function $LogProvider() {
14591
+ var debug = true,
14592
+ self = this;
14593
+
14594
+ /**
14595
+ * @ngdoc method
14596
+ * @name $logProvider#debugEnabled
14597
+ * @description
14598
+ * @param {boolean=} flag enable or disable debug level messages
14599
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
14600
+ */
14601
+ this.debugEnabled = function(flag) {
14602
+ if (isDefined(flag)) {
14603
+ debug = flag;
14604
+ return this;
14605
+ } else {
14606
+ return debug;
14607
+ }
14608
+ };
14609
+
14610
+ this.$get = ['$window', function($window) {
14611
+ // Support: IE 9-11, Edge 12-14+
14612
+ // IE/Edge display errors in such a way that it requires the user to click in 4 places
14613
+ // to see the stack trace. There is no way to feature-detect it so there's a chance
14614
+ // of the user agent sniffing to go wrong but since it's only about logging, this shouldn't
14615
+ // break apps. Other browsers display errors in a sensible way and some of them map stack
14616
+ // traces along source maps if available so it makes sense to let browsers display it
14617
+ // as they want.
14618
+ var formatStackTrace = msie || /\bEdge\//.test($window.navigator && $window.navigator.userAgent);
14619
+
14620
+ return {
14621
+ /**
14622
+ * @ngdoc method
14623
+ * @name $log#log
14624
+ *
14625
+ * @description
14626
+ * Write a log message
14627
+ */
14628
+ log: consoleLog('log'),
14629
+
14630
+ /**
14631
+ * @ngdoc method
14632
+ * @name $log#info
14633
+ *
14634
+ * @description
14635
+ * Write an information message
14636
+ */
14637
+ info: consoleLog('info'),
14638
+
14639
+ /**
14640
+ * @ngdoc method
14641
+ * @name $log#warn
14642
+ *
14643
+ * @description
14644
+ * Write a warning message
14645
+ */
14646
+ warn: consoleLog('warn'),
14647
+
14648
+ /**
14649
+ * @ngdoc method
14650
+ * @name $log#error
14651
+ *
14652
+ * @description
14653
+ * Write an error message
14654
+ */
14655
+ error: consoleLog('error'),
14656
+
14657
+ /**
14658
+ * @ngdoc method
14659
+ * @name $log#debug
14660
+ *
14661
+ * @description
14662
+ * Write a debug message
14663
+ */
14664
+ debug: (function() {
14665
+ var fn = consoleLog('debug');
14666
+
14667
+ return function() {
14668
+ if (debug) {
14669
+ fn.apply(self, arguments);
14670
+ }
14671
+ };
14672
+ })()
14673
+ };
14674
+
14675
+ function formatError(arg) {
14676
+ if (isError(arg)) {
14677
+ if (arg.stack && formatStackTrace) {
14678
+ arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
14679
+ ? 'Error: ' + arg.message + '\n' + arg.stack
14680
+ : arg.stack;
14681
+ } else if (arg.sourceURL) {
14682
+ arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
14683
+ }
14684
+ }
14685
+ return arg;
14686
+ }
14687
+
14688
+ function consoleLog(type) {
14689
+ var console = $window.console || {},
14690
+ logFn = console[type] || console.log || noop;
14691
+
14692
+ return function() {
14693
+ var args = [];
14694
+ forEach(arguments, function(arg) {
14695
+ args.push(formatError(arg));
14696
+ });
14697
+ // Support: IE 9 only
14698
+ // console methods don't inherit from Function.prototype in IE 9 so we can't
14699
+ // call `logFn.apply(console, args)` directly.
14700
+ return Function.prototype.apply.call(logFn, console, args);
14701
+ };
14702
+ }
14703
+ }];
14704
+ }
14705
+
14706
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
14707
+ * Any commits to this file should be reviewed with security in mind. *
14708
+ * Changes to this file can potentially create security vulnerabilities. *
14709
+ * An approval from 2 Core members with history of modifying *
14710
+ * this file is required. *
14711
+ * *
14712
+ * Does the change somehow allow for arbitrary javascript to be executed? *
14713
+ * Or allows for someone to change the prototype of built-in objects? *
14714
+ * Or gives undesired access to variables likes document or window? *
14715
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
14716
+
14717
+ var $parseMinErr = minErr('$parse');
14718
+
14719
+ var objectValueOf = {}.constructor.prototype.valueOf;
14720
+
14721
+ // Sandboxing Angular Expressions
14722
+ // ------------------------------
14723
+ // Angular expressions are no longer sandboxed. So it is now even easier to access arbitrary JS code by
14724
+ // various means such as obtaining a reference to native JS functions like the Function constructor.
14725
+ //
14726
+ // As an example, consider the following Angular expression:
14727
+ //
14728
+ // {}.toString.constructor('alert("evil JS code")')
14729
+ //
14730
+ // It is important to realize that if you create an expression from a string that contains user provided
14731
+ // content then it is possible that your application contains a security vulnerability to an XSS style attack.
14732
+ //
14733
+ // See https://docs.angularjs.org/guide/security
14734
+
14735
+
14736
+ function getStringValue(name) {
14737
+ // Property names must be strings. This means that non-string objects cannot be used
14738
+ // as keys in an object. Any non-string object, including a number, is typecasted
14739
+ // into a string via the toString method.
14740
+ // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names
14741
+ //
14742
+ // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it
14743
+ // to a string. It's not always possible. If `name` is an object and its `toString` method is
14744
+ // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown:
14745
+ //
14746
+ // TypeError: Cannot convert object to primitive value
14747
+ //
14748
+ // For performance reasons, we don't catch this error here and allow it to propagate up the call
14749
+ // stack. Note that you'll get the same error in JavaScript if you try to access a property using
14750
+ // such a 'broken' object as a key.
14751
+ return name + '';
14752
+ }
14753
+
14754
+
14755
+ var OPERATORS = createMap();
14756
+ forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
14757
+ var ESCAPE = {'n':'\n', 'f':'\f', 'r':'\r', 't':'\t', 'v':'\v', '\'':'\'', '"':'"'};
14758
+
14759
+
14760
+ /////////////////////////////////////////
14761
+
14762
+
14763
+ /**
14764
+ * @constructor
14765
+ */
14766
+ var Lexer = function Lexer(options) {
14767
+ this.options = options;
14768
+ };
14769
+
14770
+ Lexer.prototype = {
14771
+ constructor: Lexer,
14772
+
14773
+ lex: function(text) {
14774
+ this.text = text;
14775
+ this.index = 0;
14776
+ this.tokens = [];
14777
+
14778
+ while (this.index < this.text.length) {
14779
+ var ch = this.text.charAt(this.index);
14780
+ if (ch === '"' || ch === '\'') {
14781
+ this.readString(ch);
14782
+ } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
14783
+ this.readNumber();
14784
+ } else if (this.isIdentifierStart(this.peekMultichar())) {
14785
+ this.readIdent();
14786
+ } else if (this.is(ch, '(){}[].,;:?')) {
14787
+ this.tokens.push({index: this.index, text: ch});
14788
+ this.index++;
14789
+ } else if (this.isWhitespace(ch)) {
14790
+ this.index++;
14791
+ } else {
14792
+ var ch2 = ch + this.peek();
14793
+ var ch3 = ch2 + this.peek(2);
14794
+ var op1 = OPERATORS[ch];
14795
+ var op2 = OPERATORS[ch2];
14796
+ var op3 = OPERATORS[ch3];
14797
+ if (op1 || op2 || op3) {
14798
+ var token = op3 ? ch3 : (op2 ? ch2 : ch);
14799
+ this.tokens.push({index: this.index, text: token, operator: true});
14800
+ this.index += token.length;
14801
+ } else {
14802
+ this.throwError('Unexpected next character ', this.index, this.index + 1);
14803
+ }
14804
+ }
14805
+ }
14806
+ return this.tokens;
14807
+ },
14808
+
14809
+ is: function(ch, chars) {
14810
+ return chars.indexOf(ch) !== -1;
14811
+ },
14812
+
14813
+ peek: function(i) {
14814
+ var num = i || 1;
14815
+ return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
14816
+ },
14817
+
14818
+ isNumber: function(ch) {
14819
+ return ('0' <= ch && ch <= '9') && typeof ch === 'string';
14820
+ },
14821
+
14822
+ isWhitespace: function(ch) {
14823
+ // IE treats non-breaking space as \u00A0
14824
+ return (ch === ' ' || ch === '\r' || ch === '\t' ||
14825
+ ch === '\n' || ch === '\v' || ch === '\u00A0');
14826
+ },
14827
+
14828
+ isIdentifierStart: function(ch) {
14829
+ return this.options.isIdentifierStart ?
14830
+ this.options.isIdentifierStart(ch, this.codePointAt(ch)) :
14831
+ this.isValidIdentifierStart(ch);
14832
+ },
14833
+
14834
+ isValidIdentifierStart: function(ch) {
14835
+ return ('a' <= ch && ch <= 'z' ||
14836
+ 'A' <= ch && ch <= 'Z' ||
14837
+ '_' === ch || ch === '$');
14838
+ },
14839
+
14840
+ isIdentifierContinue: function(ch) {
14841
+ return this.options.isIdentifierContinue ?
14842
+ this.options.isIdentifierContinue(ch, this.codePointAt(ch)) :
14843
+ this.isValidIdentifierContinue(ch);
14844
+ },
14845
+
14846
+ isValidIdentifierContinue: function(ch, cp) {
14847
+ return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch);
14848
+ },
14849
+
14850
+ codePointAt: function(ch) {
14851
+ if (ch.length === 1) return ch.charCodeAt(0);
14852
+ // eslint-disable-next-line no-bitwise
14853
+ return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00;
14854
+ },
14855
+
14856
+ peekMultichar: function() {
14857
+ var ch = this.text.charAt(this.index);
14858
+ var peek = this.peek();
14859
+ if (!peek) {
14860
+ return ch;
14861
+ }
14862
+ var cp1 = ch.charCodeAt(0);
14863
+ var cp2 = peek.charCodeAt(0);
14864
+ if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) {
14865
+ return ch + peek;
14866
+ }
14867
+ return ch;
14868
+ },
14869
+
14870
+ isExpOperator: function(ch) {
14871
+ return (ch === '-' || ch === '+' || this.isNumber(ch));
14872
+ },
14873
+
14874
+ throwError: function(error, start, end) {
14875
+ end = end || this.index;
14876
+ var colStr = (isDefined(start)
14877
+ ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
14878
+ : ' ' + end);
14879
+ throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
14880
+ error, colStr, this.text);
14881
+ },
14882
+
14883
+ readNumber: function() {
14884
+ var number = '';
14885
+ var start = this.index;
14886
+ while (this.index < this.text.length) {
14887
+ var ch = lowercase(this.text.charAt(this.index));
14888
+ if (ch === '.' || this.isNumber(ch)) {
14889
+ number += ch;
14890
+ } else {
14891
+ var peekCh = this.peek();
14892
+ if (ch === 'e' && this.isExpOperator(peekCh)) {
14893
+ number += ch;
14894
+ } else if (this.isExpOperator(ch) &&
14895
+ peekCh && this.isNumber(peekCh) &&
14896
+ number.charAt(number.length - 1) === 'e') {
14897
+ number += ch;
14898
+ } else if (this.isExpOperator(ch) &&
14899
+ (!peekCh || !this.isNumber(peekCh)) &&
14900
+ number.charAt(number.length - 1) === 'e') {
14901
+ this.throwError('Invalid exponent');
14902
+ } else {
14903
+ break;
14904
+ }
14905
+ }
14906
+ this.index++;
14907
+ }
14908
+ this.tokens.push({
14909
+ index: start,
14910
+ text: number,
14911
+ constant: true,
14912
+ value: Number(number)
14913
+ });
14914
+ },
14915
+
14916
+ readIdent: function() {
14917
+ var start = this.index;
14918
+ this.index += this.peekMultichar().length;
14919
+ while (this.index < this.text.length) {
14920
+ var ch = this.peekMultichar();
14921
+ if (!this.isIdentifierContinue(ch)) {
14922
+ break;
14923
+ }
14924
+ this.index += ch.length;
14925
+ }
14926
+ this.tokens.push({
14927
+ index: start,
14928
+ text: this.text.slice(start, this.index),
14929
+ identifier: true
14930
+ });
14931
+ },
14932
+
14933
+ readString: function(quote) {
14934
+ var start = this.index;
14935
+ this.index++;
14936
+ var string = '';
14937
+ var rawString = quote;
14938
+ var escape = false;
14939
+ while (this.index < this.text.length) {
14940
+ var ch = this.text.charAt(this.index);
14941
+ rawString += ch;
14942
+ if (escape) {
14943
+ if (ch === 'u') {
14944
+ var hex = this.text.substring(this.index + 1, this.index + 5);
14945
+ if (!hex.match(/[\da-f]{4}/i)) {
14946
+ this.throwError('Invalid unicode escape [\\u' + hex + ']');
14947
+ }
14948
+ this.index += 4;
14949
+ string += String.fromCharCode(parseInt(hex, 16));
14950
+ } else {
14951
+ var rep = ESCAPE[ch];
14952
+ string = string + (rep || ch);
14953
+ }
14954
+ escape = false;
14955
+ } else if (ch === '\\') {
14956
+ escape = true;
14957
+ } else if (ch === quote) {
14958
+ this.index++;
14959
+ this.tokens.push({
14960
+ index: start,
14961
+ text: rawString,
14962
+ constant: true,
14963
+ value: string
14964
+ });
14965
+ return;
14966
+ } else {
14967
+ string += ch;
14968
+ }
14969
+ this.index++;
14970
+ }
14971
+ this.throwError('Unterminated quote', start);
14972
+ }
14973
+ };
14974
+
14975
+ var AST = function AST(lexer, options) {
14976
+ this.lexer = lexer;
14977
+ this.options = options;
14978
+ };
14979
+
14980
+ AST.Program = 'Program';
14981
+ AST.ExpressionStatement = 'ExpressionStatement';
14982
+ AST.AssignmentExpression = 'AssignmentExpression';
14983
+ AST.ConditionalExpression = 'ConditionalExpression';
14984
+ AST.LogicalExpression = 'LogicalExpression';
14985
+ AST.BinaryExpression = 'BinaryExpression';
14986
+ AST.UnaryExpression = 'UnaryExpression';
14987
+ AST.CallExpression = 'CallExpression';
14988
+ AST.MemberExpression = 'MemberExpression';
14989
+ AST.Identifier = 'Identifier';
14990
+ AST.Literal = 'Literal';
14991
+ AST.ArrayExpression = 'ArrayExpression';
14992
+ AST.Property = 'Property';
14993
+ AST.ObjectExpression = 'ObjectExpression';
14994
+ AST.ThisExpression = 'ThisExpression';
14995
+ AST.LocalsExpression = 'LocalsExpression';
14996
+
14997
+ // Internal use only
14998
+ AST.NGValueParameter = 'NGValueParameter';
14999
+
15000
+ AST.prototype = {
15001
+ ast: function(text) {
15002
+ this.text = text;
15003
+ this.tokens = this.lexer.lex(text);
15004
+
15005
+ var value = this.program();
15006
+
15007
+ if (this.tokens.length !== 0) {
15008
+ this.throwError('is an unexpected token', this.tokens[0]);
15009
+ }
15010
+
15011
+ return value;
15012
+ },
15013
+
15014
+ program: function() {
15015
+ var body = [];
15016
+ while (true) {
15017
+ if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
15018
+ body.push(this.expressionStatement());
15019
+ if (!this.expect(';')) {
15020
+ return { type: AST.Program, body: body};
15021
+ }
15022
+ }
15023
+ },
15024
+
15025
+ expressionStatement: function() {
15026
+ return { type: AST.ExpressionStatement, expression: this.filterChain() };
15027
+ },
15028
+
15029
+ filterChain: function() {
15030
+ var left = this.expression();
15031
+ while (this.expect('|')) {
15032
+ left = this.filter(left);
15033
+ }
15034
+ return left;
15035
+ },
15036
+
15037
+ expression: function() {
15038
+ return this.assignment();
15039
+ },
15040
+
15041
+ assignment: function() {
15042
+ var result = this.ternary();
15043
+ if (this.expect('=')) {
15044
+ if (!isAssignable(result)) {
15045
+ throw $parseMinErr('lval', 'Trying to assign a value to a non l-value');
15046
+ }
15047
+
15048
+ result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
15049
+ }
15050
+ return result;
15051
+ },
15052
+
15053
+ ternary: function() {
15054
+ var test = this.logicalOR();
15055
+ var alternate;
15056
+ var consequent;
15057
+ if (this.expect('?')) {
15058
+ alternate = this.expression();
15059
+ if (this.consume(':')) {
15060
+ consequent = this.expression();
15061
+ return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
15062
+ }
15063
+ }
15064
+ return test;
15065
+ },
15066
+
15067
+ logicalOR: function() {
15068
+ var left = this.logicalAND();
15069
+ while (this.expect('||')) {
15070
+ left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
15071
+ }
15072
+ return left;
15073
+ },
15074
+
15075
+ logicalAND: function() {
15076
+ var left = this.equality();
15077
+ while (this.expect('&&')) {
15078
+ left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
15079
+ }
15080
+ return left;
15081
+ },
15082
+
15083
+ equality: function() {
15084
+ var left = this.relational();
15085
+ var token;
15086
+ while ((token = this.expect('==','!=','===','!=='))) {
15087
+ left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
15088
+ }
15089
+ return left;
15090
+ },
15091
+
15092
+ relational: function() {
15093
+ var left = this.additive();
15094
+ var token;
15095
+ while ((token = this.expect('<', '>', '<=', '>='))) {
15096
+ left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
15097
+ }
15098
+ return left;
15099
+ },
15100
+
15101
+ additive: function() {
15102
+ var left = this.multiplicative();
15103
+ var token;
15104
+ while ((token = this.expect('+','-'))) {
15105
+ left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
15106
+ }
15107
+ return left;
15108
+ },
15109
+
15110
+ multiplicative: function() {
15111
+ var left = this.unary();
15112
+ var token;
15113
+ while ((token = this.expect('*','/','%'))) {
15114
+ left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
15115
+ }
15116
+ return left;
15117
+ },
15118
+
15119
+ unary: function() {
15120
+ var token;
15121
+ if ((token = this.expect('+', '-', '!'))) {
15122
+ return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
15123
+ } else {
15124
+ return this.primary();
15125
+ }
15126
+ },
15127
+
15128
+ primary: function() {
15129
+ var primary;
15130
+ if (this.expect('(')) {
15131
+ primary = this.filterChain();
15132
+ this.consume(')');
15133
+ } else if (this.expect('[')) {
15134
+ primary = this.arrayDeclaration();
15135
+ } else if (this.expect('{')) {
15136
+ primary = this.object();
15137
+ } else if (this.selfReferential.hasOwnProperty(this.peek().text)) {
15138
+ primary = copy(this.selfReferential[this.consume().text]);
15139
+ } else if (this.options.literals.hasOwnProperty(this.peek().text)) {
15140
+ primary = { type: AST.Literal, value: this.options.literals[this.consume().text]};
15141
+ } else if (this.peek().identifier) {
15142
+ primary = this.identifier();
15143
+ } else if (this.peek().constant) {
15144
+ primary = this.constant();
15145
+ } else {
15146
+ this.throwError('not a primary expression', this.peek());
15147
+ }
15148
+
15149
+ var next;
15150
+ while ((next = this.expect('(', '[', '.'))) {
15151
+ if (next.text === '(') {
15152
+ primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
15153
+ this.consume(')');
15154
+ } else if (next.text === '[') {
15155
+ primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
15156
+ this.consume(']');
15157
+ } else if (next.text === '.') {
15158
+ primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
15159
+ } else {
15160
+ this.throwError('IMPOSSIBLE');
15161
+ }
15162
+ }
15163
+ return primary;
15164
+ },
15165
+
15166
+ filter: function(baseExpression) {
15167
+ var args = [baseExpression];
15168
+ var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
15169
+
15170
+ while (this.expect(':')) {
15171
+ args.push(this.expression());
15172
+ }
15173
+
15174
+ return result;
15175
+ },
15176
+
15177
+ parseArguments: function() {
15178
+ var args = [];
15179
+ if (this.peekToken().text !== ')') {
15180
+ do {
15181
+ args.push(this.filterChain());
15182
+ } while (this.expect(','));
15183
+ }
15184
+ return args;
15185
+ },
15186
+
15187
+ identifier: function() {
15188
+ var token = this.consume();
15189
+ if (!token.identifier) {
15190
+ this.throwError('is not a valid identifier', token);
15191
+ }
15192
+ return { type: AST.Identifier, name: token.text };
15193
+ },
15194
+
15195
+ constant: function() {
15196
+ // TODO check that it is a constant
15197
+ return { type: AST.Literal, value: this.consume().value };
15198
+ },
15199
+
15200
+ arrayDeclaration: function() {
15201
+ var elements = [];
15202
+ if (this.peekToken().text !== ']') {
15203
+ do {
15204
+ if (this.peek(']')) {
15205
+ // Support trailing commas per ES5.1.
15206
+ break;
15207
+ }
15208
+ elements.push(this.expression());
15209
+ } while (this.expect(','));
15210
+ }
15211
+ this.consume(']');
15212
+
15213
+ return { type: AST.ArrayExpression, elements: elements };
15214
+ },
15215
+
15216
+ object: function() {
15217
+ var properties = [], property;
15218
+ if (this.peekToken().text !== '}') {
15219
+ do {
15220
+ if (this.peek('}')) {
15221
+ // Support trailing commas per ES5.1.
15222
+ break;
15223
+ }
15224
+ property = {type: AST.Property, kind: 'init'};
15225
+ if (this.peek().constant) {
15226
+ property.key = this.constant();
15227
+ property.computed = false;
15228
+ this.consume(':');
15229
+ property.value = this.expression();
15230
+ } else if (this.peek().identifier) {
15231
+ property.key = this.identifier();
15232
+ property.computed = false;
15233
+ if (this.peek(':')) {
15234
+ this.consume(':');
15235
+ property.value = this.expression();
15236
+ } else {
15237
+ property.value = property.key;
15238
+ }
15239
+ } else if (this.peek('[')) {
15240
+ this.consume('[');
15241
+ property.key = this.expression();
15242
+ this.consume(']');
15243
+ property.computed = true;
15244
+ this.consume(':');
15245
+ property.value = this.expression();
15246
+ } else {
15247
+ this.throwError('invalid key', this.peek());
15248
+ }
15249
+ properties.push(property);
15250
+ } while (this.expect(','));
15251
+ }
15252
+ this.consume('}');
15253
+
15254
+ return {type: AST.ObjectExpression, properties: properties };
15255
+ },
15256
+
15257
+ throwError: function(msg, token) {
15258
+ throw $parseMinErr('syntax',
15259
+ 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
15260
+ token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
15261
+ },
15262
+
15263
+ consume: function(e1) {
15264
+ if (this.tokens.length === 0) {
15265
+ throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
15266
+ }
15267
+
15268
+ var token = this.expect(e1);
15269
+ if (!token) {
15270
+ this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
15271
+ }
15272
+ return token;
15273
+ },
15274
+
15275
+ peekToken: function() {
15276
+ if (this.tokens.length === 0) {
15277
+ throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
15278
+ }
15279
+ return this.tokens[0];
15280
+ },
15281
+
15282
+ peek: function(e1, e2, e3, e4) {
15283
+ return this.peekAhead(0, e1, e2, e3, e4);
15284
+ },
15285
+
15286
+ peekAhead: function(i, e1, e2, e3, e4) {
15287
+ if (this.tokens.length > i) {
15288
+ var token = this.tokens[i];
15289
+ var t = token.text;
15290
+ if (t === e1 || t === e2 || t === e3 || t === e4 ||
15291
+ (!e1 && !e2 && !e3 && !e4)) {
15292
+ return token;
15293
+ }
15294
+ }
15295
+ return false;
15296
+ },
15297
+
15298
+ expect: function(e1, e2, e3, e4) {
15299
+ var token = this.peek(e1, e2, e3, e4);
15300
+ if (token) {
15301
+ this.tokens.shift();
15302
+ return token;
15303
+ }
15304
+ return false;
15305
+ },
15306
+
15307
+ selfReferential: {
15308
+ 'this': {type: AST.ThisExpression },
15309
+ '$locals': {type: AST.LocalsExpression }
15310
+ }
15311
+ };
15312
+
15313
+ function ifDefined(v, d) {
15314
+ return typeof v !== 'undefined' ? v : d;
15315
+ }
15316
+
15317
+ function plusFn(l, r) {
15318
+ if (typeof l === 'undefined') return r;
15319
+ if (typeof r === 'undefined') return l;
15320
+ return l + r;
15321
+ }
15322
+
15323
+ function isStateless($filter, filterName) {
15324
+ var fn = $filter(filterName);
15325
+ return !fn.$stateful;
15326
+ }
15327
+
15328
+ var PURITY_ABSOLUTE = 1;
15329
+ var PURITY_RELATIVE = 2;
15330
+
15331
+ // Detect nodes which could depend on non-shallow state of objects
15332
+ function isPure(node, parentIsPure) {
15333
+ switch (node.type) {
15334
+ // Computed members might invoke a stateful toString()
15335
+ case AST.MemberExpression:
15336
+ if (node.computed) {
15337
+ return false;
15338
+ }
15339
+ break;
15340
+
15341
+ // Unary always convert to primative
15342
+ case AST.UnaryExpression:
15343
+ return PURITY_ABSOLUTE;
15344
+
15345
+ // The binary + operator can invoke a stateful toString().
15346
+ case AST.BinaryExpression:
15347
+ return node.operator !== '+' ? PURITY_ABSOLUTE : false;
15348
+
15349
+ // Functions / filters probably read state from within objects
15350
+ case AST.CallExpression:
15351
+ return false;
15352
+ }
15353
+
15354
+ return (undefined === parentIsPure) ? PURITY_RELATIVE : parentIsPure;
15355
+ }
15356
+
15357
+ function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
15358
+ var allConstants;
15359
+ var argsToWatch;
15360
+ var isStatelessFilter;
15361
+
15362
+ var astIsPure = ast.isPure = isPure(ast, parentIsPure);
15363
+
15364
+ switch (ast.type) {
15365
+ case AST.Program:
15366
+ allConstants = true;
15367
+ forEach(ast.body, function(expr) {
15368
+ findConstantAndWatchExpressions(expr.expression, $filter, astIsPure);
15369
+ allConstants = allConstants && expr.expression.constant;
15370
+ });
15371
+ ast.constant = allConstants;
15372
+ break;
15373
+ case AST.Literal:
15374
+ ast.constant = true;
15375
+ ast.toWatch = [];
15376
+ break;
15377
+ case AST.UnaryExpression:
15378
+ findConstantAndWatchExpressions(ast.argument, $filter, astIsPure);
15379
+ ast.constant = ast.argument.constant;
15380
+ ast.toWatch = ast.argument.toWatch;
15381
+ break;
15382
+ case AST.BinaryExpression:
15383
+ findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
15384
+ findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
15385
+ ast.constant = ast.left.constant && ast.right.constant;
15386
+ ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
15387
+ break;
15388
+ case AST.LogicalExpression:
15389
+ findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
15390
+ findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
15391
+ ast.constant = ast.left.constant && ast.right.constant;
15392
+ ast.toWatch = ast.constant ? [] : [ast];
15393
+ break;
15394
+ case AST.ConditionalExpression:
15395
+ findConstantAndWatchExpressions(ast.test, $filter, astIsPure);
15396
+ findConstantAndWatchExpressions(ast.alternate, $filter, astIsPure);
15397
+ findConstantAndWatchExpressions(ast.consequent, $filter, astIsPure);
15398
+ ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
15399
+ ast.toWatch = ast.constant ? [] : [ast];
15400
+ break;
15401
+ case AST.Identifier:
15402
+ ast.constant = false;
15403
+ ast.toWatch = [ast];
15404
+ break;
15405
+ case AST.MemberExpression:
15406
+ findConstantAndWatchExpressions(ast.object, $filter, astIsPure);
15407
+ if (ast.computed) {
15408
+ findConstantAndWatchExpressions(ast.property, $filter, astIsPure);
15409
+ }
15410
+ ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
15411
+ ast.toWatch = ast.constant ? [] : [ast];
15412
+ break;
15413
+ case AST.CallExpression:
15414
+ isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false;
15415
+ allConstants = isStatelessFilter;
15416
+ argsToWatch = [];
15417
+ forEach(ast.arguments, function(expr) {
15418
+ findConstantAndWatchExpressions(expr, $filter, astIsPure);
15419
+ allConstants = allConstants && expr.constant;
15420
+ argsToWatch.push.apply(argsToWatch, expr.toWatch);
15421
+ });
15422
+ ast.constant = allConstants;
15423
+ ast.toWatch = isStatelessFilter ? argsToWatch : [ast];
15424
+ break;
15425
+ case AST.AssignmentExpression:
15426
+ findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
15427
+ findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
15428
+ ast.constant = ast.left.constant && ast.right.constant;
15429
+ ast.toWatch = [ast];
15430
+ break;
15431
+ case AST.ArrayExpression:
15432
+ allConstants = true;
15433
+ argsToWatch = [];
15434
+ forEach(ast.elements, function(expr) {
15435
+ findConstantAndWatchExpressions(expr, $filter, astIsPure);
15436
+ allConstants = allConstants && expr.constant;
15437
+ argsToWatch.push.apply(argsToWatch, expr.toWatch);
15438
+ });
15439
+ ast.constant = allConstants;
15440
+ ast.toWatch = argsToWatch;
15441
+ break;
15442
+ case AST.ObjectExpression:
15443
+ allConstants = true;
15444
+ argsToWatch = [];
15445
+ forEach(ast.properties, function(property) {
15446
+ findConstantAndWatchExpressions(property.value, $filter, astIsPure);
15447
+ allConstants = allConstants && property.value.constant;
15448
+ argsToWatch.push.apply(argsToWatch, property.value.toWatch);
15449
+ if (property.computed) {
15450
+ //`{[key]: value}` implicitly does `key.toString()` which may be non-pure
15451
+ findConstantAndWatchExpressions(property.key, $filter, /*parentIsPure=*/false);
15452
+ allConstants = allConstants && property.key.constant;
15453
+ argsToWatch.push.apply(argsToWatch, property.key.toWatch);
15454
+ }
15455
+ });
15456
+ ast.constant = allConstants;
15457
+ ast.toWatch = argsToWatch;
15458
+ break;
15459
+ case AST.ThisExpression:
15460
+ ast.constant = false;
15461
+ ast.toWatch = [];
15462
+ break;
15463
+ case AST.LocalsExpression:
15464
+ ast.constant = false;
15465
+ ast.toWatch = [];
15466
+ break;
15467
+ }
15468
+ }
15469
+
15470
+ function getInputs(body) {
15471
+ if (body.length !== 1) return;
15472
+ var lastExpression = body[0].expression;
15473
+ var candidate = lastExpression.toWatch;
15474
+ if (candidate.length !== 1) return candidate;
15475
+ return candidate[0] !== lastExpression ? candidate : undefined;
15476
+ }
15477
+
15478
+ function isAssignable(ast) {
15479
+ return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
15480
+ }
15481
+
15482
+ function assignableAST(ast) {
15483
+ if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
15484
+ return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
15485
+ }
15486
+ }
15487
+
15488
+ function isLiteral(ast) {
15489
+ return ast.body.length === 0 ||
15490
+ ast.body.length === 1 && (
15491
+ ast.body[0].expression.type === AST.Literal ||
15492
+ ast.body[0].expression.type === AST.ArrayExpression ||
15493
+ ast.body[0].expression.type === AST.ObjectExpression);
15494
+ }
15495
+
15496
+ function isConstant(ast) {
15497
+ return ast.constant;
15498
+ }
15499
+
15500
+ function ASTCompiler($filter) {
15501
+ this.$filter = $filter;
15502
+ }
15503
+
15504
+ ASTCompiler.prototype = {
15505
+ compile: function(ast) {
15506
+ var self = this;
15507
+ this.state = {
15508
+ nextId: 0,
15509
+ filters: {},
15510
+ fn: {vars: [], body: [], own: {}},
15511
+ assign: {vars: [], body: [], own: {}},
15512
+ inputs: []
15513
+ };
15514
+ findConstantAndWatchExpressions(ast, self.$filter);
15515
+ var extra = '';
15516
+ var assignable;
15517
+ this.stage = 'assign';
15518
+ if ((assignable = assignableAST(ast))) {
15519
+ this.state.computing = 'assign';
15520
+ var result = this.nextId();
15521
+ this.recurse(assignable, result);
15522
+ this.return_(result);
15523
+ extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
15524
+ }
15525
+ var toWatch = getInputs(ast.body);
15526
+ self.stage = 'inputs';
15527
+ forEach(toWatch, function(watch, key) {
15528
+ var fnKey = 'fn' + key;
15529
+ self.state[fnKey] = {vars: [], body: [], own: {}};
15530
+ self.state.computing = fnKey;
15531
+ var intoId = self.nextId();
15532
+ self.recurse(watch, intoId);
15533
+ self.return_(intoId);
15534
+ self.state.inputs.push({name: fnKey, isPure: watch.isPure});
15535
+ watch.watchId = key;
15536
+ });
15537
+ this.state.computing = 'fn';
15538
+ this.stage = 'main';
15539
+ this.recurse(ast);
15540
+ var fnString =
15541
+ // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
15542
+ // This is a workaround for this until we do a better job at only removing the prefix only when we should.
15543
+ '"' + this.USE + ' ' + this.STRICT + '";\n' +
15544
+ this.filterPrefix() +
15545
+ 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
15546
+ extra +
15547
+ this.watchFns() +
15548
+ 'return fn;';
15549
+
15550
+ // eslint-disable-next-line no-new-func
15551
+ var fn = (new Function('$filter',
15552
+ 'getStringValue',
15553
+ 'ifDefined',
15554
+ 'plus',
15555
+ fnString))(
15556
+ this.$filter,
15557
+ getStringValue,
15558
+ ifDefined,
15559
+ plusFn);
15560
+ this.state = this.stage = undefined;
15561
+ return fn;
15562
+ },
15563
+
15564
+ USE: 'use',
15565
+
15566
+ STRICT: 'strict',
15567
+
15568
+ watchFns: function() {
15569
+ var result = [];
15570
+ var inputs = this.state.inputs;
15571
+ var self = this;
15572
+ forEach(inputs, function(input) {
15573
+ result.push('var ' + input.name + '=' + self.generateFunction(input.name, 's'));
15574
+ if (input.isPure) {
15575
+ result.push(input.name, '.isPure=' + JSON.stringify(input.isPure) + ';');
15576
+ }
15577
+ });
15578
+ if (inputs.length) {
15579
+ result.push('fn.inputs=[' + inputs.map(function(i) { return i.name; }).join(',') + '];');
15580
+ }
15581
+ return result.join('');
15582
+ },
15583
+
15584
+ generateFunction: function(name, params) {
15585
+ return 'function(' + params + '){' +
15586
+ this.varsPrefix(name) +
15587
+ this.body(name) +
15588
+ '};';
15589
+ },
15590
+
15591
+ filterPrefix: function() {
15592
+ var parts = [];
15593
+ var self = this;
15594
+ forEach(this.state.filters, function(id, filter) {
15595
+ parts.push(id + '=$filter(' + self.escape(filter) + ')');
15596
+ });
15597
+ if (parts.length) return 'var ' + parts.join(',') + ';';
15598
+ return '';
15599
+ },
15600
+
15601
+ varsPrefix: function(section) {
15602
+ return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
15603
+ },
15604
+
15605
+ body: function(section) {
15606
+ return this.state[section].body.join('');
15607
+ },
15608
+
15609
+ recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
15610
+ var left, right, self = this, args, expression, computed;
15611
+ recursionFn = recursionFn || noop;
15612
+ if (!skipWatchIdCheck && isDefined(ast.watchId)) {
15613
+ intoId = intoId || this.nextId();
15614
+ this.if_('i',
15615
+ this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
15616
+ this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
15617
+ );
15618
+ return;
15619
+ }
15620
+ switch (ast.type) {
15621
+ case AST.Program:
15622
+ forEach(ast.body, function(expression, pos) {
15623
+ self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
15624
+ if (pos !== ast.body.length - 1) {
15625
+ self.current().body.push(right, ';');
15626
+ } else {
15627
+ self.return_(right);
15628
+ }
15629
+ });
15630
+ break;
15631
+ case AST.Literal:
15632
+ expression = this.escape(ast.value);
15633
+ this.assign(intoId, expression);
15634
+ recursionFn(intoId || expression);
15635
+ break;
15636
+ case AST.UnaryExpression:
15637
+ this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
15638
+ expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
15639
+ this.assign(intoId, expression);
15640
+ recursionFn(expression);
15641
+ break;
15642
+ case AST.BinaryExpression:
15643
+ this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
15644
+ this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
15645
+ if (ast.operator === '+') {
15646
+ expression = this.plus(left, right);
15647
+ } else if (ast.operator === '-') {
15648
+ expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
15649
+ } else {
15650
+ expression = '(' + left + ')' + ast.operator + '(' + right + ')';
15651
+ }
15652
+ this.assign(intoId, expression);
15653
+ recursionFn(expression);
15654
+ break;
15655
+ case AST.LogicalExpression:
15656
+ intoId = intoId || this.nextId();
15657
+ self.recurse(ast.left, intoId);
15658
+ self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
15659
+ recursionFn(intoId);
15660
+ break;
15661
+ case AST.ConditionalExpression:
15662
+ intoId = intoId || this.nextId();
15663
+ self.recurse(ast.test, intoId);
15664
+ self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
15665
+ recursionFn(intoId);
15666
+ break;
15667
+ case AST.Identifier:
15668
+ intoId = intoId || this.nextId();
15669
+ if (nameId) {
15670
+ nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
15671
+ nameId.computed = false;
15672
+ nameId.name = ast.name;
15673
+ }
15674
+ self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
15675
+ function() {
15676
+ self.if_(self.stage === 'inputs' || 's', function() {
15677
+ if (create && create !== 1) {
15678
+ self.if_(
15679
+ self.isNull(self.nonComputedMember('s', ast.name)),
15680
+ self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
15681
+ }
15682
+ self.assign(intoId, self.nonComputedMember('s', ast.name));
15683
+ });
15684
+ }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
15685
+ );
15686
+ recursionFn(intoId);
15687
+ break;
15688
+ case AST.MemberExpression:
15689
+ left = nameId && (nameId.context = this.nextId()) || this.nextId();
15690
+ intoId = intoId || this.nextId();
15691
+ self.recurse(ast.object, left, undefined, function() {
15692
+ self.if_(self.notNull(left), function() {
15693
+ if (ast.computed) {
15694
+ right = self.nextId();
15695
+ self.recurse(ast.property, right);
15696
+ self.getStringValue(right);
15697
+ if (create && create !== 1) {
15698
+ self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
15699
+ }
15700
+ expression = self.computedMember(left, right);
15701
+ self.assign(intoId, expression);
15702
+ if (nameId) {
15703
+ nameId.computed = true;
15704
+ nameId.name = right;
15705
+ }
15706
+ } else {
15707
+ if (create && create !== 1) {
15708
+ self.if_(self.isNull(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
15709
+ }
15710
+ expression = self.nonComputedMember(left, ast.property.name);
15711
+ self.assign(intoId, expression);
15712
+ if (nameId) {
15713
+ nameId.computed = false;
15714
+ nameId.name = ast.property.name;
15715
+ }
15716
+ }
15717
+ }, function() {
15718
+ self.assign(intoId, 'undefined');
15719
+ });
15720
+ recursionFn(intoId);
15721
+ }, !!create);
15722
+ break;
15723
+ case AST.CallExpression:
15724
+ intoId = intoId || this.nextId();
15725
+ if (ast.filter) {
15726
+ right = self.filter(ast.callee.name);
15727
+ args = [];
15728
+ forEach(ast.arguments, function(expr) {
15729
+ var argument = self.nextId();
15730
+ self.recurse(expr, argument);
15731
+ args.push(argument);
15732
+ });
15733
+ expression = right + '(' + args.join(',') + ')';
15734
+ self.assign(intoId, expression);
15735
+ recursionFn(intoId);
15736
+ } else {
15737
+ right = self.nextId();
15738
+ left = {};
15739
+ args = [];
15740
+ self.recurse(ast.callee, right, left, function() {
15741
+ self.if_(self.notNull(right), function() {
15742
+ forEach(ast.arguments, function(expr) {
15743
+ self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
15744
+ args.push(argument);
15745
+ });
15746
+ });
15747
+ if (left.name) {
15748
+ expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
15749
+ } else {
15750
+ expression = right + '(' + args.join(',') + ')';
15751
+ }
15752
+ self.assign(intoId, expression);
15753
+ }, function() {
15754
+ self.assign(intoId, 'undefined');
15755
+ });
15756
+ recursionFn(intoId);
15757
+ });
15758
+ }
15759
+ break;
15760
+ case AST.AssignmentExpression:
15761
+ right = this.nextId();
15762
+ left = {};
15763
+ this.recurse(ast.left, undefined, left, function() {
15764
+ self.if_(self.notNull(left.context), function() {
15765
+ self.recurse(ast.right, right);
15766
+ expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
15767
+ self.assign(intoId, expression);
15768
+ recursionFn(intoId || expression);
15769
+ });
15770
+ }, 1);
15771
+ break;
15772
+ case AST.ArrayExpression:
15773
+ args = [];
15774
+ forEach(ast.elements, function(expr) {
15775
+ self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
15776
+ args.push(argument);
15777
+ });
15778
+ });
15779
+ expression = '[' + args.join(',') + ']';
15780
+ this.assign(intoId, expression);
15781
+ recursionFn(intoId || expression);
15782
+ break;
15783
+ case AST.ObjectExpression:
15784
+ args = [];
15785
+ computed = false;
15786
+ forEach(ast.properties, function(property) {
15787
+ if (property.computed) {
15788
+ computed = true;
15789
+ }
15790
+ });
15791
+ if (computed) {
15792
+ intoId = intoId || this.nextId();
15793
+ this.assign(intoId, '{}');
15794
+ forEach(ast.properties, function(property) {
15795
+ if (property.computed) {
15796
+ left = self.nextId();
15797
+ self.recurse(property.key, left);
15798
+ } else {
15799
+ left = property.key.type === AST.Identifier ?
15800
+ property.key.name :
15801
+ ('' + property.key.value);
15802
+ }
15803
+ right = self.nextId();
15804
+ self.recurse(property.value, right);
15805
+ self.assign(self.member(intoId, left, property.computed), right);
15806
+ });
15807
+ } else {
15808
+ forEach(ast.properties, function(property) {
15809
+ self.recurse(property.value, ast.constant ? undefined : self.nextId(), undefined, function(expr) {
15810
+ args.push(self.escape(
15811
+ property.key.type === AST.Identifier ? property.key.name :
15812
+ ('' + property.key.value)) +
15813
+ ':' + expr);
15814
+ });
15815
+ });
15816
+ expression = '{' + args.join(',') + '}';
15817
+ this.assign(intoId, expression);
15818
+ }
15819
+ recursionFn(intoId || expression);
15820
+ break;
15821
+ case AST.ThisExpression:
15822
+ this.assign(intoId, 's');
15823
+ recursionFn(intoId || 's');
15824
+ break;
15825
+ case AST.LocalsExpression:
15826
+ this.assign(intoId, 'l');
15827
+ recursionFn(intoId || 'l');
15828
+ break;
15829
+ case AST.NGValueParameter:
15830
+ this.assign(intoId, 'v');
15831
+ recursionFn(intoId || 'v');
15832
+ break;
15833
+ }
15834
+ },
15835
+
15836
+ getHasOwnProperty: function(element, property) {
15837
+ var key = element + '.' + property;
15838
+ var own = this.current().own;
15839
+ if (!own.hasOwnProperty(key)) {
15840
+ own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
15841
+ }
15842
+ return own[key];
15843
+ },
15844
+
15845
+ assign: function(id, value) {
15846
+ if (!id) return;
15847
+ this.current().body.push(id, '=', value, ';');
15848
+ return id;
15849
+ },
15850
+
15851
+ filter: function(filterName) {
15852
+ if (!this.state.filters.hasOwnProperty(filterName)) {
15853
+ this.state.filters[filterName] = this.nextId(true);
15854
+ }
15855
+ return this.state.filters[filterName];
15856
+ },
15857
+
15858
+ ifDefined: function(id, defaultValue) {
15859
+ return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
15860
+ },
15861
+
15862
+ plus: function(left, right) {
15863
+ return 'plus(' + left + ',' + right + ')';
15864
+ },
15865
+
15866
+ return_: function(id) {
15867
+ this.current().body.push('return ', id, ';');
15868
+ },
15869
+
15870
+ if_: function(test, alternate, consequent) {
15871
+ if (test === true) {
15872
+ alternate();
15873
+ } else {
15874
+ var body = this.current().body;
15875
+ body.push('if(', test, '){');
15876
+ alternate();
15877
+ body.push('}');
15878
+ if (consequent) {
15879
+ body.push('else{');
15880
+ consequent();
15881
+ body.push('}');
15882
+ }
15883
+ }
15884
+ },
15885
+
15886
+ not: function(expression) {
15887
+ return '!(' + expression + ')';
15888
+ },
15889
+
15890
+ isNull: function(expression) {
15891
+ return expression + '==null';
15892
+ },
15893
+
15894
+ notNull: function(expression) {
15895
+ return expression + '!=null';
15896
+ },
15897
+
15898
+ nonComputedMember: function(left, right) {
15899
+ var SAFE_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
15900
+ var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g;
15901
+ if (SAFE_IDENTIFIER.test(right)) {
15902
+ return left + '.' + right;
15903
+ } else {
15904
+ return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]';
15905
+ }
15906
+ },
15907
+
15908
+ computedMember: function(left, right) {
15909
+ return left + '[' + right + ']';
15910
+ },
15911
+
15912
+ member: function(left, right, computed) {
15913
+ if (computed) return this.computedMember(left, right);
15914
+ return this.nonComputedMember(left, right);
15915
+ },
15916
+
15917
+ getStringValue: function(item) {
15918
+ this.assign(item, 'getStringValue(' + item + ')');
15919
+ },
15920
+
15921
+ lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
15922
+ var self = this;
15923
+ return function() {
15924
+ self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
15925
+ };
15926
+ },
15927
+
15928
+ lazyAssign: function(id, value) {
15929
+ var self = this;
15930
+ return function() {
15931
+ self.assign(id, value);
15932
+ };
15933
+ },
15934
+
15935
+ stringEscapeRegex: /[^ a-zA-Z0-9]/g,
15936
+
15937
+ stringEscapeFn: function(c) {
15938
+ return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
15939
+ },
15940
+
15941
+ escape: function(value) {
15942
+ if (isString(value)) return '\'' + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + '\'';
15943
+ if (isNumber(value)) return value.toString();
15944
+ if (value === true) return 'true';
15945
+ if (value === false) return 'false';
15946
+ if (value === null) return 'null';
15947
+ if (typeof value === 'undefined') return 'undefined';
15948
+
15949
+ throw $parseMinErr('esc', 'IMPOSSIBLE');
15950
+ },
15951
+
15952
+ nextId: function(skip, init) {
15953
+ var id = 'v' + (this.state.nextId++);
15954
+ if (!skip) {
15955
+ this.current().vars.push(id + (init ? '=' + init : ''));
15956
+ }
15957
+ return id;
15958
+ },
15959
+
15960
+ current: function() {
15961
+ return this.state[this.state.computing];
15962
+ }
15963
+ };
15964
+
15965
+
15966
+ function ASTInterpreter($filter) {
15967
+ this.$filter = $filter;
15968
+ }
15969
+
15970
+ ASTInterpreter.prototype = {
15971
+ compile: function(ast) {
15972
+ var self = this;
15973
+ findConstantAndWatchExpressions(ast, self.$filter);
15974
+ var assignable;
15975
+ var assign;
15976
+ if ((assignable = assignableAST(ast))) {
15977
+ assign = this.recurse(assignable);
15978
+ }
15979
+ var toWatch = getInputs(ast.body);
15980
+ var inputs;
15981
+ if (toWatch) {
15982
+ inputs = [];
15983
+ forEach(toWatch, function(watch, key) {
15984
+ var input = self.recurse(watch);
15985
+ input.isPure = watch.isPure;
15986
+ watch.input = input;
15987
+ inputs.push(input);
15988
+ watch.watchId = key;
15989
+ });
15990
+ }
15991
+ var expressions = [];
15992
+ forEach(ast.body, function(expression) {
15993
+ expressions.push(self.recurse(expression.expression));
15994
+ });
15995
+ var fn = ast.body.length === 0 ? noop :
15996
+ ast.body.length === 1 ? expressions[0] :
15997
+ function(scope, locals) {
15998
+ var lastValue;
15999
+ forEach(expressions, function(exp) {
16000
+ lastValue = exp(scope, locals);
16001
+ });
16002
+ return lastValue;
16003
+ };
16004
+ if (assign) {
16005
+ fn.assign = function(scope, value, locals) {
16006
+ return assign(scope, locals, value);
16007
+ };
16008
+ }
16009
+ if (inputs) {
16010
+ fn.inputs = inputs;
16011
+ }
16012
+ return fn;
16013
+ },
16014
+
16015
+ recurse: function(ast, context, create) {
16016
+ var left, right, self = this, args;
16017
+ if (ast.input) {
16018
+ return this.inputs(ast.input, ast.watchId);
16019
+ }
16020
+ switch (ast.type) {
16021
+ case AST.Literal:
16022
+ return this.value(ast.value, context);
16023
+ case AST.UnaryExpression:
16024
+ right = this.recurse(ast.argument);
16025
+ return this['unary' + ast.operator](right, context);
16026
+ case AST.BinaryExpression:
16027
+ left = this.recurse(ast.left);
16028
+ right = this.recurse(ast.right);
16029
+ return this['binary' + ast.operator](left, right, context);
16030
+ case AST.LogicalExpression:
16031
+ left = this.recurse(ast.left);
16032
+ right = this.recurse(ast.right);
16033
+ return this['binary' + ast.operator](left, right, context);
16034
+ case AST.ConditionalExpression:
16035
+ return this['ternary?:'](
16036
+ this.recurse(ast.test),
16037
+ this.recurse(ast.alternate),
16038
+ this.recurse(ast.consequent),
16039
+ context
16040
+ );
16041
+ case AST.Identifier:
16042
+ return self.identifier(ast.name, context, create);
16043
+ case AST.MemberExpression:
16044
+ left = this.recurse(ast.object, false, !!create);
16045
+ if (!ast.computed) {
16046
+ right = ast.property.name;
16047
+ }
16048
+ if (ast.computed) right = this.recurse(ast.property);
16049
+ return ast.computed ?
16050
+ this.computedMember(left, right, context, create) :
16051
+ this.nonComputedMember(left, right, context, create);
16052
+ case AST.CallExpression:
16053
+ args = [];
16054
+ forEach(ast.arguments, function(expr) {
16055
+ args.push(self.recurse(expr));
16056
+ });
16057
+ if (ast.filter) right = this.$filter(ast.callee.name);
16058
+ if (!ast.filter) right = this.recurse(ast.callee, true);
16059
+ return ast.filter ?
16060
+ function(scope, locals, assign, inputs) {
16061
+ var values = [];
16062
+ for (var i = 0; i < args.length; ++i) {
16063
+ values.push(args[i](scope, locals, assign, inputs));
16064
+ }
16065
+ var value = right.apply(undefined, values, inputs);
16066
+ return context ? {context: undefined, name: undefined, value: value} : value;
16067
+ } :
16068
+ function(scope, locals, assign, inputs) {
16069
+ var rhs = right(scope, locals, assign, inputs);
16070
+ var value;
16071
+ if (rhs.value != null) {
16072
+ var values = [];
16073
+ for (var i = 0; i < args.length; ++i) {
16074
+ values.push(args[i](scope, locals, assign, inputs));
16075
+ }
16076
+ value = rhs.value.apply(rhs.context, values);
16077
+ }
16078
+ return context ? {value: value} : value;
16079
+ };
16080
+ case AST.AssignmentExpression:
16081
+ left = this.recurse(ast.left, true, 1);
16082
+ right = this.recurse(ast.right);
16083
+ return function(scope, locals, assign, inputs) {
16084
+ var lhs = left(scope, locals, assign, inputs);
16085
+ var rhs = right(scope, locals, assign, inputs);
16086
+ lhs.context[lhs.name] = rhs;
16087
+ return context ? {value: rhs} : rhs;
16088
+ };
16089
+ case AST.ArrayExpression:
16090
+ args = [];
16091
+ forEach(ast.elements, function(expr) {
16092
+ args.push(self.recurse(expr));
16093
+ });
16094
+ return function(scope, locals, assign, inputs) {
16095
+ var value = [];
16096
+ for (var i = 0; i < args.length; ++i) {
16097
+ value.push(args[i](scope, locals, assign, inputs));
16098
+ }
16099
+ return context ? {value: value} : value;
16100
+ };
16101
+ case AST.ObjectExpression:
16102
+ args = [];
16103
+ forEach(ast.properties, function(property) {
16104
+ if (property.computed) {
16105
+ args.push({key: self.recurse(property.key),
16106
+ computed: true,
16107
+ value: self.recurse(property.value)
16108
+ });
16109
+ } else {
16110
+ args.push({key: property.key.type === AST.Identifier ?
16111
+ property.key.name :
16112
+ ('' + property.key.value),
16113
+ computed: false,
16114
+ value: self.recurse(property.value)
16115
+ });
16116
+ }
16117
+ });
16118
+ return function(scope, locals, assign, inputs) {
16119
+ var value = {};
16120
+ for (var i = 0; i < args.length; ++i) {
16121
+ if (args[i].computed) {
16122
+ value[args[i].key(scope, locals, assign, inputs)] = args[i].value(scope, locals, assign, inputs);
16123
+ } else {
16124
+ value[args[i].key] = args[i].value(scope, locals, assign, inputs);
16125
+ }
16126
+ }
16127
+ return context ? {value: value} : value;
16128
+ };
16129
+ case AST.ThisExpression:
16130
+ return function(scope) {
16131
+ return context ? {value: scope} : scope;
16132
+ };
16133
+ case AST.LocalsExpression:
16134
+ return function(scope, locals) {
16135
+ return context ? {value: locals} : locals;
16136
+ };
16137
+ case AST.NGValueParameter:
16138
+ return function(scope, locals, assign) {
16139
+ return context ? {value: assign} : assign;
16140
+ };
16141
+ }
16142
+ },
16143
+
16144
+ 'unary+': function(argument, context) {
16145
+ return function(scope, locals, assign, inputs) {
16146
+ var arg = argument(scope, locals, assign, inputs);
16147
+ if (isDefined(arg)) {
16148
+ arg = +arg;
16149
+ } else {
16150
+ arg = 0;
16151
+ }
16152
+ return context ? {value: arg} : arg;
16153
+ };
16154
+ },
16155
+ 'unary-': function(argument, context) {
16156
+ return function(scope, locals, assign, inputs) {
16157
+ var arg = argument(scope, locals, assign, inputs);
16158
+ if (isDefined(arg)) {
16159
+ arg = -arg;
16160
+ } else {
16161
+ arg = -0;
16162
+ }
16163
+ return context ? {value: arg} : arg;
16164
+ };
16165
+ },
16166
+ 'unary!': function(argument, context) {
16167
+ return function(scope, locals, assign, inputs) {
16168
+ var arg = !argument(scope, locals, assign, inputs);
16169
+ return context ? {value: arg} : arg;
16170
+ };
16171
+ },
16172
+ 'binary+': function(left, right, context) {
16173
+ return function(scope, locals, assign, inputs) {
16174
+ var lhs = left(scope, locals, assign, inputs);
16175
+ var rhs = right(scope, locals, assign, inputs);
16176
+ var arg = plusFn(lhs, rhs);
16177
+ return context ? {value: arg} : arg;
16178
+ };
16179
+ },
16180
+ 'binary-': function(left, right, context) {
16181
+ return function(scope, locals, assign, inputs) {
16182
+ var lhs = left(scope, locals, assign, inputs);
16183
+ var rhs = right(scope, locals, assign, inputs);
16184
+ var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
16185
+ return context ? {value: arg} : arg;
16186
+ };
16187
+ },
16188
+ 'binary*': function(left, right, context) {
16189
+ return function(scope, locals, assign, inputs) {
16190
+ var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
16191
+ return context ? {value: arg} : arg;
16192
+ };
16193
+ },
16194
+ 'binary/': function(left, right, context) {
16195
+ return function(scope, locals, assign, inputs) {
16196
+ var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
16197
+ return context ? {value: arg} : arg;
16198
+ };
16199
+ },
16200
+ 'binary%': function(left, right, context) {
16201
+ return function(scope, locals, assign, inputs) {
16202
+ var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
16203
+ return context ? {value: arg} : arg;
16204
+ };
16205
+ },
16206
+ 'binary===': function(left, right, context) {
16207
+ return function(scope, locals, assign, inputs) {
16208
+ var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
16209
+ return context ? {value: arg} : arg;
16210
+ };
16211
+ },
16212
+ 'binary!==': function(left, right, context) {
16213
+ return function(scope, locals, assign, inputs) {
16214
+ var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
16215
+ return context ? {value: arg} : arg;
16216
+ };
16217
+ },
16218
+ 'binary==': function(left, right, context) {
16219
+ return function(scope, locals, assign, inputs) {
16220
+ // eslint-disable-next-line eqeqeq
16221
+ var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
16222
+ return context ? {value: arg} : arg;
16223
+ };
16224
+ },
16225
+ 'binary!=': function(left, right, context) {
16226
+ return function(scope, locals, assign, inputs) {
16227
+ // eslint-disable-next-line eqeqeq
16228
+ var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
16229
+ return context ? {value: arg} : arg;
16230
+ };
16231
+ },
16232
+ 'binary<': function(left, right, context) {
16233
+ return function(scope, locals, assign, inputs) {
16234
+ var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
16235
+ return context ? {value: arg} : arg;
16236
+ };
16237
+ },
16238
+ 'binary>': function(left, right, context) {
16239
+ return function(scope, locals, assign, inputs) {
16240
+ var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
16241
+ return context ? {value: arg} : arg;
16242
+ };
16243
+ },
16244
+ 'binary<=': function(left, right, context) {
16245
+ return function(scope, locals, assign, inputs) {
16246
+ var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
16247
+ return context ? {value: arg} : arg;
16248
+ };
16249
+ },
16250
+ 'binary>=': function(left, right, context) {
16251
+ return function(scope, locals, assign, inputs) {
16252
+ var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
16253
+ return context ? {value: arg} : arg;
16254
+ };
16255
+ },
16256
+ 'binary&&': function(left, right, context) {
16257
+ return function(scope, locals, assign, inputs) {
16258
+ var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
16259
+ return context ? {value: arg} : arg;
16260
+ };
16261
+ },
16262
+ 'binary||': function(left, right, context) {
16263
+ return function(scope, locals, assign, inputs) {
16264
+ var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
16265
+ return context ? {value: arg} : arg;
16266
+ };
16267
+ },
16268
+ 'ternary?:': function(test, alternate, consequent, context) {
16269
+ return function(scope, locals, assign, inputs) {
16270
+ var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
16271
+ return context ? {value: arg} : arg;
16272
+ };
16273
+ },
16274
+ value: function(value, context) {
16275
+ return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
16276
+ },
16277
+ identifier: function(name, context, create) {
16278
+ return function(scope, locals, assign, inputs) {
16279
+ var base = locals && (name in locals) ? locals : scope;
16280
+ if (create && create !== 1 && base && base[name] == null) {
16281
+ base[name] = {};
16282
+ }
16283
+ var value = base ? base[name] : undefined;
16284
+ if (context) {
16285
+ return {context: base, name: name, value: value};
16286
+ } else {
16287
+ return value;
16288
+ }
16289
+ };
16290
+ },
16291
+ computedMember: function(left, right, context, create) {
16292
+ return function(scope, locals, assign, inputs) {
16293
+ var lhs = left(scope, locals, assign, inputs);
16294
+ var rhs;
16295
+ var value;
16296
+ if (lhs != null) {
16297
+ rhs = right(scope, locals, assign, inputs);
16298
+ rhs = getStringValue(rhs);
16299
+ if (create && create !== 1) {
16300
+ if (lhs && !(lhs[rhs])) {
16301
+ lhs[rhs] = {};
16302
+ }
16303
+ }
16304
+ value = lhs[rhs];
16305
+ }
16306
+ if (context) {
16307
+ return {context: lhs, name: rhs, value: value};
16308
+ } else {
16309
+ return value;
16310
+ }
16311
+ };
16312
+ },
16313
+ nonComputedMember: function(left, right, context, create) {
16314
+ return function(scope, locals, assign, inputs) {
16315
+ var lhs = left(scope, locals, assign, inputs);
16316
+ if (create && create !== 1) {
16317
+ if (lhs && lhs[right] == null) {
16318
+ lhs[right] = {};
16319
+ }
16320
+ }
16321
+ var value = lhs != null ? lhs[right] : undefined;
16322
+ if (context) {
16323
+ return {context: lhs, name: right, value: value};
16324
+ } else {
16325
+ return value;
16326
+ }
16327
+ };
16328
+ },
16329
+ inputs: function(input, watchId) {
16330
+ return function(scope, value, locals, inputs) {
16331
+ if (inputs) return inputs[watchId];
16332
+ return input(scope, value, locals);
16333
+ };
16334
+ }
16335
+ };
16336
+
16337
+ /**
16338
+ * @constructor
16339
+ */
16340
+ function Parser(lexer, $filter, options) {
16341
+ this.ast = new AST(lexer, options);
16342
+ this.astCompiler = options.csp ? new ASTInterpreter($filter) :
16343
+ new ASTCompiler($filter);
16344
+ }
16345
+
16346
+ Parser.prototype = {
16347
+ constructor: Parser,
16348
+
16349
+ parse: function(text) {
16350
+ var ast = this.ast.ast(text);
16351
+ var fn = this.astCompiler.compile(ast);
16352
+ fn.literal = isLiteral(ast);
16353
+ fn.constant = isConstant(ast);
16354
+ return fn;
16355
+ }
16356
+ };
16357
+
16358
+ function getValueOf(value) {
16359
+ return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
16360
+ }
16361
+
16362
+ ///////////////////////////////////
16363
+
16364
+ /**
16365
+ * @ngdoc service
16366
+ * @name $parse
16367
+ * @kind function
16368
+ *
16369
+ * @description
16370
+ *
16371
+ * Converts Angular {@link guide/expression expression} into a function.
16372
+ *
16373
+ * ```js
16374
+ * var getter = $parse('user.name');
16375
+ * var setter = getter.assign;
16376
+ * var context = {user:{name:'angular'}};
16377
+ * var locals = {user:{name:'local'}};
16378
+ *
16379
+ * expect(getter(context)).toEqual('angular');
16380
+ * setter(context, 'newValue');
16381
+ * expect(context.user.name).toEqual('newValue');
16382
+ * expect(getter(context, locals)).toEqual('local');
16383
+ * ```
16384
+ *
16385
+ *
16386
+ * @param {string} expression String expression to compile.
16387
+ * @returns {function(context, locals)} a function which represents the compiled expression:
16388
+ *
16389
+ * * `context` – `{object}` – an object against which any expressions embedded in the strings
16390
+ * are evaluated against (typically a scope object).
16391
+ * * `locals` – `{object=}` – local variables context object, useful for overriding values in
16392
+ * `context`.
16393
+ *
16394
+ * The returned function also has the following properties:
16395
+ * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
16396
+ * literal.
16397
+ * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
16398
+ * constant literals.
16399
+ * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
16400
+ * set to a function to change its value on the given context.
16401
+ *
16402
+ */
16403
+
16404
+
16405
+ /**
16406
+ * @ngdoc provider
16407
+ * @name $parseProvider
16408
+ * @this
16409
+ *
16410
+ * @description
16411
+ * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
16412
+ * service.
16413
+ */
16414
+ function $ParseProvider() {
16415
+ var cache = createMap();
16416
+ var literals = {
16417
+ 'true': true,
16418
+ 'false': false,
16419
+ 'null': null,
16420
+ 'undefined': undefined
16421
+ };
16422
+ var identStart, identContinue;
16423
+
16424
+ /**
16425
+ * @ngdoc method
16426
+ * @name $parseProvider#addLiteral
16427
+ * @description
16428
+ *
16429
+ * Configure $parse service to add literal values that will be present as literal at expressions.
16430
+ *
16431
+ * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name.
16432
+ * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`.
16433
+ *
16434
+ **/
16435
+ this.addLiteral = function(literalName, literalValue) {
16436
+ literals[literalName] = literalValue;
16437
+ };
16438
+
16439
+ /**
16440
+ * @ngdoc method
16441
+ * @name $parseProvider#setIdentifierFns
16442
+ *
16443
+ * @description
16444
+ *
16445
+ * Allows defining the set of characters that are allowed in Angular expressions. The function
16446
+ * `identifierStart` will get called to know if a given character is a valid character to be the
16447
+ * first character for an identifier. The function `identifierContinue` will get called to know if
16448
+ * a given character is a valid character to be a follow-up identifier character. The functions
16449
+ * `identifierStart` and `identifierContinue` will receive as arguments the single character to be
16450
+ * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in
16451
+ * mind that the `string` parameter can be two characters long depending on the character
16452
+ * representation. It is expected for the function to return `true` or `false`, whether that
16453
+ * character is allowed or not.
16454
+ *
16455
+ * Since this function will be called extensively, keep the implementation of these functions fast,
16456
+ * as the performance of these functions have a direct impact on the expressions parsing speed.
16457
+ *
16458
+ * @param {function=} identifierStart The function that will decide whether the given character is
16459
+ * a valid identifier start character.
16460
+ * @param {function=} identifierContinue The function that will decide whether the given character is
16461
+ * a valid identifier continue character.
16462
+ */
16463
+ this.setIdentifierFns = function(identifierStart, identifierContinue) {
16464
+ identStart = identifierStart;
16465
+ identContinue = identifierContinue;
16466
+ return this;
16467
+ };
16468
+
16469
+ this.$get = ['$filter', function($filter) {
16470
+ var noUnsafeEval = csp().noUnsafeEval;
16471
+ var $parseOptions = {
16472
+ csp: noUnsafeEval,
16473
+ literals: copy(literals),
16474
+ isIdentifierStart: isFunction(identStart) && identStart,
16475
+ isIdentifierContinue: isFunction(identContinue) && identContinue
16476
+ };
16477
+ return $parse;
16478
+
16479
+ function $parse(exp, interceptorFn) {
16480
+ var parsedExpression, oneTime, cacheKey;
16481
+
16482
+ switch (typeof exp) {
16483
+ case 'string':
16484
+ exp = exp.trim();
16485
+ cacheKey = exp;
16486
+
16487
+ parsedExpression = cache[cacheKey];
16488
+
16489
+ if (!parsedExpression) {
16490
+ if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
16491
+ oneTime = true;
16492
+ exp = exp.substring(2);
16493
+ }
16494
+ var lexer = new Lexer($parseOptions);
16495
+ var parser = new Parser(lexer, $filter, $parseOptions);
16496
+ parsedExpression = parser.parse(exp);
16497
+ if (parsedExpression.constant) {
16498
+ parsedExpression.$$watchDelegate = constantWatchDelegate;
16499
+ } else if (oneTime) {
16500
+ parsedExpression.$$watchDelegate = parsedExpression.literal ?
16501
+ oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
16502
+ } else if (parsedExpression.inputs) {
16503
+ parsedExpression.$$watchDelegate = inputsWatchDelegate;
16504
+ }
16505
+ cache[cacheKey] = parsedExpression;
16506
+ }
16507
+ return addInterceptor(parsedExpression, interceptorFn);
16508
+
16509
+ case 'function':
16510
+ return addInterceptor(exp, interceptorFn);
16511
+
16512
+ default:
16513
+ return addInterceptor(noop, interceptorFn);
16514
+ }
16515
+ }
16516
+
16517
+ function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) {
16518
+
16519
+ if (newValue == null || oldValueOfValue == null) { // null/undefined
16520
+ return newValue === oldValueOfValue;
16521
+ }
16522
+
16523
+ if (typeof newValue === 'object') {
16524
+
16525
+ // attempt to convert the value to a primitive type
16526
+ // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
16527
+ // be cheaply dirty-checked
16528
+ newValue = getValueOf(newValue);
16529
+
16530
+ if (typeof newValue === 'object' && !compareObjectIdentity) {
16531
+ // objects/arrays are not supported - deep-watching them would be too expensive
16532
+ return false;
16533
+ }
16534
+
16535
+ // fall-through to the primitive equality check
16536
+ }
16537
+
16538
+ //Primitive or NaN
16539
+ // eslint-disable-next-line no-self-compare
16540
+ return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
16541
+ }
16542
+
16543
+ function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
16544
+ var inputExpressions = parsedExpression.inputs;
16545
+ var lastResult;
16546
+
16547
+ if (inputExpressions.length === 1) {
16548
+ var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
16549
+ inputExpressions = inputExpressions[0];
16550
+ return scope.$watch(function expressionInputWatch(scope) {
16551
+ var newInputValue = inputExpressions(scope);
16552
+ if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, inputExpressions.isPure)) {
16553
+ lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
16554
+ oldInputValueOf = newInputValue && getValueOf(newInputValue);
16555
+ }
16556
+ return lastResult;
16557
+ }, listener, objectEquality, prettyPrintExpression);
16558
+ }
16559
+
16560
+ var oldInputValueOfValues = [];
16561
+ var oldInputValues = [];
16562
+ for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
16563
+ oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
16564
+ oldInputValues[i] = null;
16565
+ }
16566
+
16567
+ return scope.$watch(function expressionInputsWatch(scope) {
16568
+ var changed = false;
16569
+
16570
+ for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
16571
+ var newInputValue = inputExpressions[i](scope);
16572
+ if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], inputExpressions[i].isPure))) {
16573
+ oldInputValues[i] = newInputValue;
16574
+ oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
16575
+ }
16576
+ }
16577
+
16578
+ if (changed) {
16579
+ lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
16580
+ }
16581
+
16582
+ return lastResult;
16583
+ }, listener, objectEquality, prettyPrintExpression);
16584
+ }
16585
+
16586
+ function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
16587
+ var unwatch, lastValue;
16588
+ if (parsedExpression.inputs) {
16589
+ unwatch = inputsWatchDelegate(scope, oneTimeListener, objectEquality, parsedExpression, prettyPrintExpression);
16590
+ } else {
16591
+ unwatch = scope.$watch(oneTimeWatch, oneTimeListener, objectEquality);
16592
+ }
16593
+ return unwatch;
16594
+
16595
+ function oneTimeWatch(scope) {
16596
+ return parsedExpression(scope);
16597
+ }
16598
+ function oneTimeListener(value, old, scope) {
16599
+ lastValue = value;
16600
+ if (isFunction(listener)) {
16601
+ listener(value, old, scope);
16602
+ }
16603
+ if (isDefined(value)) {
16604
+ scope.$$postDigest(function() {
16605
+ if (isDefined(lastValue)) {
16606
+ unwatch();
16607
+ }
16608
+ });
16609
+ }
16610
+ }
16611
+ }
16612
+
16613
+ function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
16614
+ var unwatch, lastValue;
16615
+ unwatch = scope.$watch(function oneTimeWatch(scope) {
16616
+ return parsedExpression(scope);
16617
+ }, function oneTimeListener(value, old, scope) {
16618
+ lastValue = value;
16619
+ if (isFunction(listener)) {
16620
+ listener(value, old, scope);
16621
+ }
16622
+ if (isAllDefined(value)) {
16623
+ scope.$$postDigest(function() {
16624
+ if (isAllDefined(lastValue)) unwatch();
16625
+ });
16626
+ }
16627
+ }, objectEquality);
16628
+
16629
+ return unwatch;
16630
+
16631
+ function isAllDefined(value) {
16632
+ var allDefined = true;
16633
+ forEach(value, function(val) {
16634
+ if (!isDefined(val)) allDefined = false;
16635
+ });
16636
+ return allDefined;
16637
+ }
16638
+ }
16639
+
16640
+ function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
16641
+ var unwatch = scope.$watch(function constantWatch(scope) {
16642
+ unwatch();
16643
+ return parsedExpression(scope);
16644
+ }, listener, objectEquality);
16645
+ return unwatch;
16646
+ }
16647
+
16648
+ function addInterceptor(parsedExpression, interceptorFn) {
16649
+ if (!interceptorFn) return parsedExpression;
16650
+ var watchDelegate = parsedExpression.$$watchDelegate;
16651
+ var useInputs = false;
16652
+
16653
+ var regularWatch =
16654
+ watchDelegate !== oneTimeLiteralWatchDelegate &&
16655
+ watchDelegate !== oneTimeWatchDelegate;
16656
+
16657
+ var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
16658
+ var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
16659
+ return interceptorFn(value, scope, locals);
16660
+ } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
16661
+ var value = parsedExpression(scope, locals, assign, inputs);
16662
+ var result = interceptorFn(value, scope, locals);
16663
+ // we only return the interceptor's result if the
16664
+ // initial value is defined (for bind-once)
16665
+ return isDefined(value) ? result : value;
16666
+ };
16667
+
16668
+ // Propagate $$watchDelegates other then inputsWatchDelegate
16669
+ useInputs = !parsedExpression.inputs;
16670
+ if (watchDelegate && watchDelegate !== inputsWatchDelegate) {
16671
+ fn.$$watchDelegate = watchDelegate;
16672
+ fn.inputs = parsedExpression.inputs;
16673
+ } else if (!interceptorFn.$stateful) {
16674
+ // Treat interceptor like filters - assume non-stateful by default and use the inputsWatchDelegate
16675
+ fn.$$watchDelegate = inputsWatchDelegate;
16676
+ fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
16677
+ }
16678
+
16679
+ if (fn.inputs) {
16680
+ fn.inputs = fn.inputs.map(function(e) {
16681
+ // Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a
16682
+ // potentially non-pure interceptor function.
16683
+ if (e.isPure === PURITY_RELATIVE) {
16684
+ return function depurifier(s) { return e(s); };
16685
+ }
16686
+ return e;
16687
+ });
16688
+ }
16689
+
16690
+ return fn;
16691
+ }
16692
+ }];
16693
+ }
16694
+
16695
+ /**
16696
+ * @ngdoc service
16697
+ * @name $q
16698
+ * @requires $rootScope
16699
+ *
16700
+ * @description
16701
+ * A service that helps you run functions asynchronously, and use their return values (or exceptions)
16702
+ * when they are done processing.
16703
+ *
16704
+ * This is a [Promises/A+](https://promisesaplus.com/)-compliant implementation of promises/deferred
16705
+ * objects inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
16706
+ *
16707
+ * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
16708
+ * implementations, and the other which resembles ES6 (ES2015) promises to some degree.
16709
+ *
16710
+ * # $q constructor
16711
+ *
16712
+ * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
16713
+ * function as the first argument. This is similar to the native Promise implementation from ES6,
16714
+ * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
16715
+ *
16716
+ * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are
16717
+ * available yet.
16718
+ *
16719
+ * It can be used like so:
16720
+ *
16721
+ * ```js
16722
+ * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
16723
+ * // are available in the current lexical scope (they could have been injected or passed in).
16724
+ *
16725
+ * function asyncGreet(name) {
16726
+ * // perform some asynchronous operation, resolve or reject the promise when appropriate.
16727
+ * return $q(function(resolve, reject) {
16728
+ * setTimeout(function() {
16729
+ * if (okToGreet(name)) {
16730
+ * resolve('Hello, ' + name + '!');
16731
+ * } else {
16732
+ * reject('Greeting ' + name + ' is not allowed.');
16733
+ * }
16734
+ * }, 1000);
16735
+ * });
16736
+ * }
16737
+ *
16738
+ * var promise = asyncGreet('Robin Hood');
16739
+ * promise.then(function(greeting) {
16740
+ * alert('Success: ' + greeting);
16741
+ * }, function(reason) {
16742
+ * alert('Failed: ' + reason);
16743
+ * });
16744
+ * ```
16745
+ *
16746
+ * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
16747
+ *
16748
+ * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise.
16749
+ *
16750
+ * However, the more traditional CommonJS-style usage is still available, and documented below.
16751
+ *
16752
+ * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
16753
+ * interface for interacting with an object that represents the result of an action that is
16754
+ * performed asynchronously, and may or may not be finished at any given point in time.
16755
+ *
16756
+ * From the perspective of dealing with error handling, deferred and promise APIs are to
16757
+ * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
16758
+ *
16759
+ * ```js
16760
+ * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
16761
+ * // are available in the current lexical scope (they could have been injected or passed in).
16762
+ *
16763
+ * function asyncGreet(name) {
16764
+ * var deferred = $q.defer();
16765
+ *
16766
+ * setTimeout(function() {
16767
+ * deferred.notify('About to greet ' + name + '.');
16768
+ *
16769
+ * if (okToGreet(name)) {
16770
+ * deferred.resolve('Hello, ' + name + '!');
16771
+ * } else {
16772
+ * deferred.reject('Greeting ' + name + ' is not allowed.');
16773
+ * }
16774
+ * }, 1000);
16775
+ *
16776
+ * return deferred.promise;
16777
+ * }
16778
+ *
16779
+ * var promise = asyncGreet('Robin Hood');
16780
+ * promise.then(function(greeting) {
16781
+ * alert('Success: ' + greeting);
16782
+ * }, function(reason) {
16783
+ * alert('Failed: ' + reason);
16784
+ * }, function(update) {
16785
+ * alert('Got notification: ' + update);
16786
+ * });
16787
+ * ```
16788
+ *
16789
+ * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
16790
+ * comes in the way of guarantees that promise and deferred APIs make, see
16791
+ * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
16792
+ *
16793
+ * Additionally the promise api allows for composition that is very hard to do with the
16794
+ * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
16795
+ * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
16796
+ * section on serial or parallel joining of promises.
16797
+ *
16798
+ * # The Deferred API
16799
+ *
16800
+ * A new instance of deferred is constructed by calling `$q.defer()`.
16801
+ *
16802
+ * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
16803
+ * that can be used for signaling the successful or unsuccessful completion, as well as the status
16804
+ * of the task.
16805
+ *
16806
+ * **Methods**
16807
+ *
16808
+ * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
16809
+ * constructed via `$q.reject`, the promise will be rejected instead.
16810
+ * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
16811
+ * resolving it with a rejection constructed via `$q.reject`.
16812
+ * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
16813
+ * multiple times before the promise is either resolved or rejected.
16814
+ *
16815
+ * **Properties**
16816
+ *
16817
+ * - promise – `{Promise}` – promise object associated with this deferred.
16818
+ *
16819
+ *
16820
+ * # The Promise API
16821
+ *
16822
+ * A new promise instance is created when a deferred instance is created and can be retrieved by
16823
+ * calling `deferred.promise`.
16824
+ *
16825
+ * The purpose of the promise object is to allow for interested parties to get access to the result
16826
+ * of the deferred task when it completes.
16827
+ *
16828
+ * **Methods**
16829
+ *
16830
+ * - `then(successCallback, [errorCallback], [notifyCallback])` – regardless of when the promise was or
16831
+ * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
16832
+ * as soon as the result is available. The callbacks are called with a single argument: the result
16833
+ * or rejection reason. Additionally, the notify callback may be called zero or more times to
16834
+ * provide a progress indication, before the promise is resolved or rejected.
16835
+ *
16836
+ * This method *returns a new promise* which is resolved or rejected via the return value of the
16837
+ * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
16838
+ * with the value which is resolved in that promise using
16839
+ * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
16840
+ * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
16841
+ * resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback
16842
+ * arguments are optional.
16843
+ *
16844
+ * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
16845
+ *
16846
+ * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
16847
+ * but to do so without modifying the final value. This is useful to release resources or do some
16848
+ * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
16849
+ * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
16850
+ * more information.
16851
+ *
16852
+ * # Chaining promises
16853
+ *
16854
+ * Because calling the `then` method of a promise returns a new derived promise, it is easily
16855
+ * possible to create a chain of promises:
16856
+ *
16857
+ * ```js
16858
+ * promiseB = promiseA.then(function(result) {
16859
+ * return result + 1;
16860
+ * });
16861
+ *
16862
+ * // promiseB will be resolved immediately after promiseA is resolved and its value
16863
+ * // will be the result of promiseA incremented by 1
16864
+ * ```
16865
+ *
16866
+ * It is possible to create chains of any length and since a promise can be resolved with another
16867
+ * promise (which will defer its resolution further), it is possible to pause/defer resolution of
16868
+ * the promises at any point in the chain. This makes it possible to implement powerful APIs like
16869
+ * $http's response interceptors.
16870
+ *
16871
+ *
16872
+ * # Differences between Kris Kowal's Q and $q
16873
+ *
16874
+ * There are two main differences:
16875
+ *
16876
+ * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
16877
+ * mechanism in angular, which means faster propagation of resolution or rejection into your
16878
+ * models and avoiding unnecessary browser repaints, which would result in flickering UI.
16879
+ * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
16880
+ * all the important functionality needed for common async tasks.
16881
+ *
16882
+ * # Testing
16883
+ *
16884
+ * ```js
16885
+ * it('should simulate promise', inject(function($q, $rootScope) {
16886
+ * var deferred = $q.defer();
16887
+ * var promise = deferred.promise;
16888
+ * var resolvedValue;
16889
+ *
16890
+ * promise.then(function(value) { resolvedValue = value; });
16891
+ * expect(resolvedValue).toBeUndefined();
16892
+ *
16893
+ * // Simulate resolving of promise
16894
+ * deferred.resolve(123);
16895
+ * // Note that the 'then' function does not get called synchronously.
16896
+ * // This is because we want the promise API to always be async, whether or not
16897
+ * // it got called synchronously or asynchronously.
16898
+ * expect(resolvedValue).toBeUndefined();
16899
+ *
16900
+ * // Propagate promise resolution to 'then' functions using $apply().
16901
+ * $rootScope.$apply();
16902
+ * expect(resolvedValue).toEqual(123);
16903
+ * }));
16904
+ * ```
16905
+ *
16906
+ * @param {function(function, function)} resolver Function which is responsible for resolving or
16907
+ * rejecting the newly created promise. The first parameter is a function which resolves the
16908
+ * promise, the second parameter is a function which rejects the promise.
16909
+ *
16910
+ * @returns {Promise} The newly created promise.
16911
+ */
16912
+ /**
16913
+ * @ngdoc provider
16914
+ * @name $qProvider
16915
+ * @this
16916
+ *
16917
+ * @description
16918
+ */
16919
+ function $QProvider() {
16920
+ var errorOnUnhandledRejections = true;
16921
+ this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
16922
+ return qFactory(function(callback) {
16923
+ $rootScope.$evalAsync(callback);
16924
+ }, $exceptionHandler, errorOnUnhandledRejections);
16925
+ }];
16926
+
16927
+ /**
16928
+ * @ngdoc method
16929
+ * @name $qProvider#errorOnUnhandledRejections
16930
+ * @kind function
16931
+ *
16932
+ * @description
16933
+ * Retrieves or overrides whether to generate an error when a rejected promise is not handled.
16934
+ * This feature is enabled by default.
16935
+ *
16936
+ * @param {boolean=} value Whether to generate an error when a rejected promise is not handled.
16937
+ * @returns {boolean|ng.$qProvider} Current value when called without a new value or self for
16938
+ * chaining otherwise.
16939
+ */
16940
+ this.errorOnUnhandledRejections = function(value) {
16941
+ if (isDefined(value)) {
16942
+ errorOnUnhandledRejections = value;
16943
+ return this;
16944
+ } else {
16945
+ return errorOnUnhandledRejections;
16946
+ }
16947
+ };
16948
+ }
16949
+
16950
+ /** @this */
16951
+ function $$QProvider() {
16952
+ var errorOnUnhandledRejections = true;
16953
+ this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
16954
+ return qFactory(function(callback) {
16955
+ $browser.defer(callback);
16956
+ }, $exceptionHandler, errorOnUnhandledRejections);
16957
+ }];
16958
+
16959
+ this.errorOnUnhandledRejections = function(value) {
16960
+ if (isDefined(value)) {
16961
+ errorOnUnhandledRejections = value;
16962
+ return this;
16963
+ } else {
16964
+ return errorOnUnhandledRejections;
16965
+ }
16966
+ };
16967
+ }
16968
+
16969
+ /**
16970
+ * Constructs a promise manager.
16971
+ *
16972
+ * @param {function(function)} nextTick Function for executing functions in the next turn.
16973
+ * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
16974
+ * debugging purposes.
16975
+ * @param {boolean=} errorOnUnhandledRejections Whether an error should be generated on unhandled
16976
+ * promises rejections.
16977
+ * @returns {object} Promise manager.
16978
+ */
16979
+ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
16980
+ var $qMinErr = minErr('$q', TypeError);
16981
+ var queueSize = 0;
16982
+ var checkQueue = [];
16983
+
16984
+ /**
16985
+ * @ngdoc method
16986
+ * @name ng.$q#defer
16987
+ * @kind function
16988
+ *
16989
+ * @description
16990
+ * Creates a `Deferred` object which represents a task which will finish in the future.
16991
+ *
16992
+ * @returns {Deferred} Returns a new instance of deferred.
16993
+ */
16994
+ function defer() {
16995
+ return new Deferred();
16996
+ }
16997
+
16998
+ function Deferred() {
16999
+ var promise = this.promise = new Promise();
17000
+ //Non prototype methods necessary to support unbound execution :/
17001
+ this.resolve = function(val) { resolvePromise(promise, val); };
17002
+ this.reject = function(reason) { rejectPromise(promise, reason); };
17003
+ this.notify = function(progress) { notifyPromise(promise, progress); };
17004
+ }
17005
+
17006
+
17007
+ function Promise() {
17008
+ this.$$state = { status: 0 };
17009
+ }
17010
+
17011
+ extend(Promise.prototype, {
17012
+ then: function(onFulfilled, onRejected, progressBack) {
17013
+ if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
17014
+ return this;
17015
+ }
17016
+ var result = new Promise();
17017
+
17018
+ this.$$state.pending = this.$$state.pending || [];
17019
+ this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
17020
+ if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
17021
+
17022
+ return result;
17023
+ },
17024
+
17025
+ 'catch': function(callback) {
17026
+ return this.then(null, callback);
17027
+ },
17028
+
17029
+ 'finally': function(callback, progressBack) {
17030
+ return this.then(function(value) {
17031
+ return handleCallback(value, resolve, callback);
17032
+ }, function(error) {
17033
+ return handleCallback(error, reject, callback);
17034
+ }, progressBack);
17035
+ }
17036
+ });
17037
+
17038
+ function processQueue(state) {
17039
+ var fn, promise, pending;
17040
+
17041
+ pending = state.pending;
17042
+ state.processScheduled = false;
17043
+ state.pending = undefined;
17044
+ try {
17045
+ for (var i = 0, ii = pending.length; i < ii; ++i) {
17046
+ markQStateExceptionHandled(state);
17047
+ promise = pending[i][0];
17048
+ fn = pending[i][state.status];
17049
+ try {
17050
+ if (isFunction(fn)) {
17051
+ resolvePromise(promise, fn(state.value));
17052
+ } else if (state.status === 1) {
17053
+ resolvePromise(promise, state.value);
17054
+ } else {
17055
+ rejectPromise(promise, state.value);
17056
+ }
17057
+ } catch (e) {
17058
+ rejectPromise(promise, e);
17059
+ }
17060
+ }
17061
+ } finally {
17062
+ --queueSize;
17063
+ if (errorOnUnhandledRejections && queueSize === 0) {
17064
+ nextTick(processChecks);
17065
+ }
17066
+ }
17067
+ }
17068
+
17069
+ function processChecks() {
17070
+ // eslint-disable-next-line no-unmodified-loop-condition
17071
+ while (!queueSize && checkQueue.length) {
17072
+ var toCheck = checkQueue.shift();
17073
+ if (!isStateExceptionHandled(toCheck)) {
17074
+ markQStateExceptionHandled(toCheck);
17075
+ var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value);
17076
+ if (isError(toCheck.value)) {
17077
+ exceptionHandler(toCheck.value, errorMessage);
17078
+ } else {
17079
+ exceptionHandler(errorMessage);
17080
+ }
17081
+ }
17082
+ }
17083
+ }
17084
+
17085
+ function scheduleProcessQueue(state) {
17086
+ if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !isStateExceptionHandled(state)) {
17087
+ if (queueSize === 0 && checkQueue.length === 0) {
17088
+ nextTick(processChecks);
17089
+ }
17090
+ checkQueue.push(state);
17091
+ }
17092
+ if (state.processScheduled || !state.pending) return;
17093
+ state.processScheduled = true;
17094
+ ++queueSize;
17095
+ nextTick(function() { processQueue(state); });
17096
+ }
17097
+
17098
+ function resolvePromise(promise, val) {
17099
+ if (promise.$$state.status) return;
17100
+ if (val === promise) {
17101
+ $$reject(promise, $qMinErr(
17102
+ 'qcycle',
17103
+ 'Expected promise to be resolved with value other than itself \'{0}\'',
17104
+ val));
17105
+ } else {
17106
+ $$resolve(promise, val);
17107
+ }
17108
+
17109
+ }
17110
+
17111
+ function $$resolve(promise, val) {
17112
+ var then;
17113
+ var done = false;
17114
+ try {
17115
+ if (isObject(val) || isFunction(val)) then = val.then;
17116
+ if (isFunction(then)) {
17117
+ promise.$$state.status = -1;
17118
+ then.call(val, doResolve, doReject, doNotify);
17119
+ } else {
17120
+ promise.$$state.value = val;
17121
+ promise.$$state.status = 1;
17122
+ scheduleProcessQueue(promise.$$state);
17123
+ }
17124
+ } catch (e) {
17125
+ doReject(e);
17126
+ }
17127
+
17128
+ function doResolve(val) {
17129
+ if (done) return;
17130
+ done = true;
17131
+ $$resolve(promise, val);
17132
+ }
17133
+ function doReject(val) {
17134
+ if (done) return;
17135
+ done = true;
17136
+ $$reject(promise, val);
17137
+ }
17138
+ function doNotify(progress) {
17139
+ notifyPromise(promise, progress);
17140
+ }
17141
+ }
17142
+
17143
+ function rejectPromise(promise, reason) {
17144
+ if (promise.$$state.status) return;
17145
+ $$reject(promise, reason);
17146
+ }
17147
+
17148
+ function $$reject(promise, reason) {
17149
+ promise.$$state.value = reason;
17150
+ promise.$$state.status = 2;
17151
+ scheduleProcessQueue(promise.$$state);
17152
+ }
17153
+
17154
+ function notifyPromise(promise, progress) {
17155
+ var callbacks = promise.$$state.pending;
17156
+
17157
+ if ((promise.$$state.status <= 0) && callbacks && callbacks.length) {
17158
+ nextTick(function() {
17159
+ var callback, result;
17160
+ for (var i = 0, ii = callbacks.length; i < ii; i++) {
17161
+ result = callbacks[i][0];
17162
+ callback = callbacks[i][3];
17163
+ try {
17164
+ notifyPromise(result, isFunction(callback) ? callback(progress) : progress);
17165
+ } catch (e) {
17166
+ exceptionHandler(e);
17167
+ }
17168
+ }
17169
+ });
17170
+ }
17171
+ }
17172
+
17173
+ /**
17174
+ * @ngdoc method
17175
+ * @name $q#reject
17176
+ * @kind function
17177
+ *
17178
+ * @description
17179
+ * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
17180
+ * used to forward rejection in a chain of promises. If you are dealing with the last promise in
17181
+ * a promise chain, you don't need to worry about it.
17182
+ *
17183
+ * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
17184
+ * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
17185
+ * a promise error callback and you want to forward the error to the promise derived from the
17186
+ * current promise, you have to "rethrow" the error by returning a rejection constructed via
17187
+ * `reject`.
17188
+ *
17189
+ * ```js
17190
+ * promiseB = promiseA.then(function(result) {
17191
+ * // success: do something and resolve promiseB
17192
+ * // with the old or a new result
17193
+ * return result;
17194
+ * }, function(reason) {
17195
+ * // error: handle the error if possible and
17196
+ * // resolve promiseB with newPromiseOrValue,
17197
+ * // otherwise forward the rejection to promiseB
17198
+ * if (canHandle(reason)) {
17199
+ * // handle the error and recover
17200
+ * return newPromiseOrValue;
17201
+ * }
17202
+ * return $q.reject(reason);
17203
+ * });
17204
+ * ```
17205
+ *
17206
+ * @param {*} reason Constant, message, exception or an object representing the rejection reason.
17207
+ * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
17208
+ */
17209
+ function reject(reason) {
17210
+ var result = new Promise();
17211
+ rejectPromise(result, reason);
17212
+ return result;
17213
+ }
17214
+
17215
+ function handleCallback(value, resolver, callback) {
17216
+ var callbackOutput = null;
17217
+ try {
17218
+ if (isFunction(callback)) callbackOutput = callback();
17219
+ } catch (e) {
17220
+ return reject(e);
17221
+ }
17222
+ if (isPromiseLike(callbackOutput)) {
17223
+ return callbackOutput.then(function() {
17224
+ return resolver(value);
17225
+ }, reject);
17226
+ } else {
17227
+ return resolver(value);
17228
+ }
17229
+ }
17230
+
17231
+ /**
17232
+ * @ngdoc method
17233
+ * @name $q#when
17234
+ * @kind function
17235
+ *
17236
+ * @description
17237
+ * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
17238
+ * This is useful when you are dealing with an object that might or might not be a promise, or if
17239
+ * the promise comes from a source that can't be trusted.
17240
+ *
17241
+ * @param {*} value Value or a promise
17242
+ * @param {Function=} successCallback
17243
+ * @param {Function=} errorCallback
17244
+ * @param {Function=} progressCallback
17245
+ * @returns {Promise} Returns a promise of the passed value or promise
17246
+ */
17247
+
17248
+
17249
+ function when(value, callback, errback, progressBack) {
17250
+ var result = new Promise();
17251
+ resolvePromise(result, value);
17252
+ return result.then(callback, errback, progressBack);
17253
+ }
17254
+
17255
+ /**
17256
+ * @ngdoc method
17257
+ * @name $q#resolve
17258
+ * @kind function
17259
+ *
17260
+ * @description
17261
+ * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
17262
+ *
17263
+ * @param {*} value Value or a promise
17264
+ * @param {Function=} successCallback
17265
+ * @param {Function=} errorCallback
17266
+ * @param {Function=} progressCallback
17267
+ * @returns {Promise} Returns a promise of the passed value or promise
17268
+ */
17269
+ var resolve = when;
17270
+
17271
+ /**
17272
+ * @ngdoc method
17273
+ * @name $q#all
17274
+ * @kind function
17275
+ *
17276
+ * @description
17277
+ * Combines multiple promises into a single promise that is resolved when all of the input
17278
+ * promises are resolved.
17279
+ *
17280
+ * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
17281
+ * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
17282
+ * each value corresponding to the promise at the same index/key in the `promises` array/hash.
17283
+ * If any of the promises is resolved with a rejection, this resulting promise will be rejected
17284
+ * with the same rejection value.
17285
+ */
17286
+
17287
+ function all(promises) {
17288
+ var result = new Promise(),
17289
+ counter = 0,
17290
+ results = isArray(promises) ? [] : {};
17291
+
17292
+ forEach(promises, function(promise, key) {
17293
+ counter++;
17294
+ when(promise).then(function(value) {
17295
+ results[key] = value;
17296
+ if (!(--counter)) resolvePromise(result, results);
17297
+ }, function(reason) {
17298
+ rejectPromise(result, reason);
17299
+ });
17300
+ });
17301
+
17302
+ if (counter === 0) {
17303
+ resolvePromise(result, results);
17304
+ }
17305
+
17306
+ return result;
17307
+ }
17308
+
17309
+ /**
17310
+ * @ngdoc method
17311
+ * @name $q#race
17312
+ * @kind function
17313
+ *
17314
+ * @description
17315
+ * Returns a promise that resolves or rejects as soon as one of those promises
17316
+ * resolves or rejects, with the value or reason from that promise.
17317
+ *
17318
+ * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
17319
+ * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises`
17320
+ * resolves or rejects, with the value or reason from that promise.
17321
+ */
17322
+
17323
+ function race(promises) {
17324
+ var deferred = defer();
17325
+
17326
+ forEach(promises, function(promise) {
17327
+ when(promise).then(deferred.resolve, deferred.reject);
17328
+ });
17329
+
17330
+ return deferred.promise;
17331
+ }
17332
+
17333
+ function $Q(resolver) {
17334
+ if (!isFunction(resolver)) {
17335
+ throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver);
17336
+ }
17337
+
17338
+ var promise = new Promise();
17339
+
17340
+ function resolveFn(value) {
17341
+ resolvePromise(promise, value);
17342
+ }
17343
+
17344
+ function rejectFn(reason) {
17345
+ rejectPromise(promise, reason);
17346
+ }
17347
+
17348
+ resolver(resolveFn, rejectFn);
17349
+
17350
+ return promise;
17351
+ }
17352
+
17353
+ // Let's make the instanceof operator work for promises, so that
17354
+ // `new $q(fn) instanceof $q` would evaluate to true.
17355
+ $Q.prototype = Promise.prototype;
17356
+
17357
+ $Q.defer = defer;
17358
+ $Q.reject = reject;
17359
+ $Q.when = when;
17360
+ $Q.resolve = resolve;
17361
+ $Q.all = all;
17362
+ $Q.race = race;
17363
+
17364
+ return $Q;
17365
+ }
17366
+
17367
+ function isStateExceptionHandled(state) {
17368
+ return !!state.pur;
17369
+ }
17370
+ function markQStateExceptionHandled(state) {
17371
+ state.pur = true;
17372
+ }
17373
+ function markQExceptionHandled(q) {
17374
+ markQStateExceptionHandled(q.$$state);
17375
+ }
17376
+
17377
+ /** @this */
17378
+ function $$RAFProvider() { //rAF
17379
+ this.$get = ['$window', '$timeout', function($window, $timeout) {
17380
+ var requestAnimationFrame = $window.requestAnimationFrame ||
17381
+ $window.webkitRequestAnimationFrame;
17382
+
17383
+ var cancelAnimationFrame = $window.cancelAnimationFrame ||
17384
+ $window.webkitCancelAnimationFrame ||
17385
+ $window.webkitCancelRequestAnimationFrame;
17386
+
17387
+ var rafSupported = !!requestAnimationFrame;
17388
+ var raf = rafSupported
17389
+ ? function(fn) {
17390
+ var id = requestAnimationFrame(fn);
17391
+ return function() {
17392
+ cancelAnimationFrame(id);
17393
+ };
17394
+ }
17395
+ : function(fn) {
17396
+ var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
17397
+ return function() {
17398
+ $timeout.cancel(timer);
17399
+ };
17400
+ };
17401
+
17402
+ raf.supported = rafSupported;
17403
+
17404
+ return raf;
17405
+ }];
17406
+ }
17407
+
17408
+ /**
17409
+ * DESIGN NOTES
17410
+ *
17411
+ * The design decisions behind the scope are heavily favored for speed and memory consumption.
17412
+ *
17413
+ * The typical use of scope is to watch the expressions, which most of the time return the same
17414
+ * value as last time so we optimize the operation.
17415
+ *
17416
+ * Closures construction is expensive in terms of speed as well as memory:
17417
+ * - No closures, instead use prototypical inheritance for API
17418
+ * - Internal state needs to be stored on scope directly, which means that private state is
17419
+ * exposed as $$____ properties
17420
+ *
17421
+ * Loop operations are optimized by using while(count--) { ... }
17422
+ * - This means that in order to keep the same order of execution as addition we have to add
17423
+ * items to the array at the beginning (unshift) instead of at the end (push)
17424
+ *
17425
+ * Child scopes are created and removed often
17426
+ * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
17427
+ *
17428
+ * There are fewer watches than observers. This is why you don't want the observer to be implemented
17429
+ * in the same way as watch. Watch requires return of the initialization function which is expensive
17430
+ * to construct.
17431
+ */
17432
+
17433
+
17434
+ /**
17435
+ * @ngdoc provider
17436
+ * @name $rootScopeProvider
17437
+ * @description
17438
+ *
17439
+ * Provider for the $rootScope service.
17440
+ */
17441
+
17442
+ /**
17443
+ * @ngdoc method
17444
+ * @name $rootScopeProvider#digestTtl
17445
+ * @description
17446
+ *
17447
+ * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
17448
+ * assuming that the model is unstable.
17449
+ *
17450
+ * The current default is 10 iterations.
17451
+ *
17452
+ * In complex applications it's possible that the dependencies between `$watch`s will result in
17453
+ * several digest iterations. However if an application needs more than the default 10 digest
17454
+ * iterations for its model to stabilize then you should investigate what is causing the model to
17455
+ * continuously change during the digest.
17456
+ *
17457
+ * Increasing the TTL could have performance implications, so you should not change it without
17458
+ * proper justification.
17459
+ *
17460
+ * @param {number} limit The number of digest iterations.
17461
+ */
17462
+
17463
+
17464
+ /**
17465
+ * @ngdoc service
17466
+ * @name $rootScope
17467
+ * @this
17468
+ *
17469
+ * @description
17470
+ *
17471
+ * Every application has a single root {@link ng.$rootScope.Scope scope}.
17472
+ * All other scopes are descendant scopes of the root scope. Scopes provide separation
17473
+ * between the model and the view, via a mechanism for watching the model for changes.
17474
+ * They also provide event emission/broadcast and subscription facility. See the
17475
+ * {@link guide/scope developer guide on scopes}.
17476
+ */
17477
+ function $RootScopeProvider() {
17478
+ var TTL = 10;
17479
+ var $rootScopeMinErr = minErr('$rootScope');
17480
+ var lastDirtyWatch = null;
17481
+ var applyAsyncId = null;
17482
+
17483
+ this.digestTtl = function(value) {
17484
+ if (arguments.length) {
17485
+ TTL = value;
17486
+ }
17487
+ return TTL;
17488
+ };
17489
+
17490
+ function createChildScopeClass(parent) {
17491
+ function ChildScope() {
17492
+ this.$$watchers = this.$$nextSibling =
17493
+ this.$$childHead = this.$$childTail = null;
17494
+ this.$$listeners = {};
17495
+ this.$$listenerCount = {};
17496
+ this.$$watchersCount = 0;
17497
+ this.$id = nextUid();
17498
+ this.$$ChildScope = null;
17499
+ }
17500
+ ChildScope.prototype = parent;
17501
+ return ChildScope;
17502
+ }
17503
+
17504
+ this.$get = ['$exceptionHandler', '$parse', '$browser',
17505
+ function($exceptionHandler, $parse, $browser) {
17506
+
17507
+ function destroyChildScope($event) {
17508
+ $event.currentScope.$$destroyed = true;
17509
+ }
17510
+
17511
+ function cleanUpScope($scope) {
17512
+
17513
+ // Support: IE 9 only
17514
+ if (msie === 9) {
17515
+ // There is a memory leak in IE9 if all child scopes are not disconnected
17516
+ // completely when a scope is destroyed. So this code will recurse up through
17517
+ // all this scopes children
17518
+ //
17519
+ // See issue https://github.com/angular/angular.js/issues/10706
17520
+ if ($scope.$$childHead) {
17521
+ cleanUpScope($scope.$$childHead);
17522
+ }
17523
+ if ($scope.$$nextSibling) {
17524
+ cleanUpScope($scope.$$nextSibling);
17525
+ }
17526
+ }
17527
+
17528
+ // The code below works around IE9 and V8's memory leaks
17529
+ //
17530
+ // See:
17531
+ // - https://code.google.com/p/v8/issues/detail?id=2073#c26
17532
+ // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
17533
+ // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
17534
+
17535
+ $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
17536
+ $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
17537
+ }
17538
+
17539
+ /**
17540
+ * @ngdoc type
17541
+ * @name $rootScope.Scope
17542
+ *
17543
+ * @description
17544
+ * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
17545
+ * {@link auto.$injector $injector}. Child scopes are created using the
17546
+ * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
17547
+ * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
17548
+ * an in-depth introduction and usage examples.
17549
+ *
17550
+ *
17551
+ * # Inheritance
17552
+ * A scope can inherit from a parent scope, as in this example:
17553
+ * ```js
17554
+ var parent = $rootScope;
17555
+ var child = parent.$new();
17556
+
17557
+ parent.salutation = "Hello";
17558
+ expect(child.salutation).toEqual('Hello');
17559
+
17560
+ child.salutation = "Welcome";
17561
+ expect(child.salutation).toEqual('Welcome');
17562
+ expect(parent.salutation).toEqual('Hello');
17563
+ * ```
17564
+ *
17565
+ * When interacting with `Scope` in tests, additional helper methods are available on the
17566
+ * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
17567
+ * details.
17568
+ *
17569
+ *
17570
+ * @param {Object.<string, function()>=} providers Map of service factory which need to be
17571
+ * provided for the current scope. Defaults to {@link ng}.
17572
+ * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
17573
+ * append/override services provided by `providers`. This is handy
17574
+ * when unit-testing and having the need to override a default
17575
+ * service.
17576
+ * @returns {Object} Newly created scope.
17577
+ *
17578
+ */
17579
+ function Scope() {
17580
+ this.$id = nextUid();
17581
+ this.$$phase = this.$parent = this.$$watchers =
17582
+ this.$$nextSibling = this.$$prevSibling =
17583
+ this.$$childHead = this.$$childTail = null;
17584
+ this.$root = this;
17585
+ this.$$destroyed = false;
17586
+ this.$$listeners = {};
17587
+ this.$$listenerCount = {};
17588
+ this.$$watchersCount = 0;
17589
+ this.$$isolateBindings = null;
17590
+ }
17591
+
17592
+ /**
17593
+ * @ngdoc property
17594
+ * @name $rootScope.Scope#$id
17595
+ *
17596
+ * @description
17597
+ * Unique scope ID (monotonically increasing) useful for debugging.
17598
+ */
17599
+
17600
+ /**
17601
+ * @ngdoc property
17602
+ * @name $rootScope.Scope#$parent
17603
+ *
17604
+ * @description
17605
+ * Reference to the parent scope.
17606
+ */
17607
+
17608
+ /**
17609
+ * @ngdoc property
17610
+ * @name $rootScope.Scope#$root
17611
+ *
17612
+ * @description
17613
+ * Reference to the root scope.
17614
+ */
17615
+
17616
+ Scope.prototype = {
17617
+ constructor: Scope,
17618
+ /**
17619
+ * @ngdoc method
17620
+ * @name $rootScope.Scope#$new
17621
+ * @kind function
17622
+ *
17623
+ * @description
17624
+ * Creates a new child {@link ng.$rootScope.Scope scope}.
17625
+ *
17626
+ * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
17627
+ * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
17628
+ *
17629
+ * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
17630
+ * desired for the scope and its child scopes to be permanently detached from the parent and
17631
+ * thus stop participating in model change detection and listener notification by invoking.
17632
+ *
17633
+ * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
17634
+ * parent scope. The scope is isolated, as it can not see parent scope properties.
17635
+ * When creating widgets, it is useful for the widget to not accidentally read parent
17636
+ * state.
17637
+ *
17638
+ * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
17639
+ * of the newly created scope. Defaults to `this` scope if not provided.
17640
+ * This is used when creating a transclude scope to correctly place it
17641
+ * in the scope hierarchy while maintaining the correct prototypical
17642
+ * inheritance.
17643
+ *
17644
+ * @returns {Object} The newly created child scope.
17645
+ *
17646
+ */
17647
+ $new: function(isolate, parent) {
17648
+ var child;
17649
+
17650
+ parent = parent || this;
17651
+
17652
+ if (isolate) {
17653
+ child = new Scope();
17654
+ child.$root = this.$root;
17655
+ } else {
17656
+ // Only create a child scope class if somebody asks for one,
17657
+ // but cache it to allow the VM to optimize lookups.
17658
+ if (!this.$$ChildScope) {
17659
+ this.$$ChildScope = createChildScopeClass(this);
17660
+ }
17661
+ child = new this.$$ChildScope();
17662
+ }
17663
+ child.$parent = parent;
17664
+ child.$$prevSibling = parent.$$childTail;
17665
+ if (parent.$$childHead) {
17666
+ parent.$$childTail.$$nextSibling = child;
17667
+ parent.$$childTail = child;
17668
+ } else {
17669
+ parent.$$childHead = parent.$$childTail = child;
17670
+ }
17671
+
17672
+ // When the new scope is not isolated or we inherit from `this`, and
17673
+ // the parent scope is destroyed, the property `$$destroyed` is inherited
17674
+ // prototypically. In all other cases, this property needs to be set
17675
+ // when the parent scope is destroyed.
17676
+ // The listener needs to be added after the parent is set
17677
+ if (isolate || parent !== this) child.$on('$destroy', destroyChildScope);
17678
+
17679
+ return child;
17680
+ },
17681
+
17682
+ /**
17683
+ * @ngdoc method
17684
+ * @name $rootScope.Scope#$watch
17685
+ * @kind function
17686
+ *
17687
+ * @description
17688
+ * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
17689
+ *
17690
+ * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
17691
+ * $digest()} and should return the value that will be watched. (`watchExpression` should not change
17692
+ * its value when executed multiple times with the same input because it may be executed multiple
17693
+ * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
17694
+ * [idempotent](http://en.wikipedia.org/wiki/Idempotence).)
17695
+ * - The `listener` is called only when the value from the current `watchExpression` and the
17696
+ * previous call to `watchExpression` are not equal (with the exception of the initial run,
17697
+ * see below). Inequality is determined according to reference inequality,
17698
+ * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
17699
+ * via the `!==` Javascript operator, unless `objectEquality == true`
17700
+ * (see next point)
17701
+ * - When `objectEquality == true`, inequality of the `watchExpression` is determined
17702
+ * according to the {@link angular.equals} function. To save the value of the object for
17703
+ * later comparison, the {@link angular.copy} function is used. This therefore means that
17704
+ * watching complex objects will have adverse memory and performance implications.
17705
+ * - This should not be used to watch for changes in objects that are
17706
+ * or contain [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}.
17707
+ * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
17708
+ * This is achieved by rerunning the watchers until no changes are detected. The rerun
17709
+ * iteration limit is 10 to prevent an infinite loop deadlock.
17710
+ *
17711
+ *
17712
+ * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
17713
+ * you can register a `watchExpression` function with no `listener`. (Be prepared for
17714
+ * multiple calls to your `watchExpression` because it will execute multiple times in a
17715
+ * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
17716
+ *
17717
+ * After a watcher is registered with the scope, the `listener` fn is called asynchronously
17718
+ * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
17719
+ * watcher. In rare cases, this is undesirable because the listener is called when the result
17720
+ * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
17721
+ * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
17722
+ * listener was called due to initialization.
17723
+ *
17724
+ *
17725
+ *
17726
+ * # Example
17727
+ * ```js
17728
+ // let's assume that scope was dependency injected as the $rootScope
17729
+ var scope = $rootScope;
17730
+ scope.name = 'misko';
17731
+ scope.counter = 0;
17732
+
17733
+ expect(scope.counter).toEqual(0);
17734
+ scope.$watch('name', function(newValue, oldValue) {
17735
+ scope.counter = scope.counter + 1;
17736
+ });
17737
+ expect(scope.counter).toEqual(0);
17738
+
17739
+ scope.$digest();
17740
+ // the listener is always called during the first $digest loop after it was registered
17741
+ expect(scope.counter).toEqual(1);
17742
+
17743
+ scope.$digest();
17744
+ // but now it will not be called unless the value changes
17745
+ expect(scope.counter).toEqual(1);
17746
+
17747
+ scope.name = 'adam';
17748
+ scope.$digest();
17749
+ expect(scope.counter).toEqual(2);
17750
+
17751
+
17752
+
17753
+ // Using a function as a watchExpression
17754
+ var food;
17755
+ scope.foodCounter = 0;
17756
+ expect(scope.foodCounter).toEqual(0);
17757
+ scope.$watch(
17758
+ // This function returns the value being watched. It is called for each turn of the $digest loop
17759
+ function() { return food; },
17760
+ // This is the change listener, called when the value returned from the above function changes
17761
+ function(newValue, oldValue) {
17762
+ if ( newValue !== oldValue ) {
17763
+ // Only increment the counter if the value changed
17764
+ scope.foodCounter = scope.foodCounter + 1;
17765
+ }
17766
+ }
17767
+ );
17768
+ // No digest has been run so the counter will be zero
17769
+ expect(scope.foodCounter).toEqual(0);
17770
+
17771
+ // Run the digest but since food has not changed count will still be zero
17772
+ scope.$digest();
17773
+ expect(scope.foodCounter).toEqual(0);
17774
+
17775
+ // Update food and run digest. Now the counter will increment
17776
+ food = 'cheeseburger';
17777
+ scope.$digest();
17778
+ expect(scope.foodCounter).toEqual(1);
17779
+
17780
+ * ```
17781
+ *
17782
+ *
17783
+ *
17784
+ * @param {(function()|string)} watchExpression Expression that is evaluated on each
17785
+ * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
17786
+ * a call to the `listener`.
17787
+ *
17788
+ * - `string`: Evaluated as {@link guide/expression expression}
17789
+ * - `function(scope)`: called with current `scope` as a parameter.
17790
+ * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
17791
+ * of `watchExpression` changes.
17792
+ *
17793
+ * - `newVal` contains the current value of the `watchExpression`
17794
+ * - `oldVal` contains the previous value of the `watchExpression`
17795
+ * - `scope` refers to the current scope
17796
+ * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of
17797
+ * comparing for reference equality.
17798
+ * @returns {function()} Returns a deregistration function for this listener.
17799
+ */
17800
+ $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
17801
+ var get = $parse(watchExp);
17802
+
17803
+ if (get.$$watchDelegate) {
17804
+ return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
17805
+ }
17806
+ var scope = this,
17807
+ array = scope.$$watchers,
17808
+ watcher = {
17809
+ fn: listener,
17810
+ last: initWatchVal,
17811
+ get: get,
17812
+ exp: prettyPrintExpression || watchExp,
17813
+ eq: !!objectEquality
17814
+ };
17815
+
17816
+ lastDirtyWatch = null;
17817
+
17818
+ if (!isFunction(listener)) {
17819
+ watcher.fn = noop;
17820
+ }
17821
+
17822
+ if (!array) {
17823
+ array = scope.$$watchers = [];
17824
+ array.$$digestWatchIndex = -1;
17825
+ }
17826
+ // we use unshift since we use a while loop in $digest for speed.
17827
+ // the while loop reads in reverse order.
17828
+ array.unshift(watcher);
17829
+ array.$$digestWatchIndex++;
17830
+ incrementWatchersCount(this, 1);
17831
+
17832
+ return function deregisterWatch() {
17833
+ var index = arrayRemove(array, watcher);
17834
+ if (index >= 0) {
17835
+ incrementWatchersCount(scope, -1);
17836
+ if (index < array.$$digestWatchIndex) {
17837
+ array.$$digestWatchIndex--;
17838
+ }
17839
+ }
17840
+ lastDirtyWatch = null;
17841
+ };
17842
+ },
17843
+
17844
+ /**
17845
+ * @ngdoc method
17846
+ * @name $rootScope.Scope#$watchGroup
17847
+ * @kind function
17848
+ *
17849
+ * @description
17850
+ * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
17851
+ * If any one expression in the collection changes the `listener` is executed.
17852
+ *
17853
+ * - The items in the `watchExpressions` array are observed via the standard `$watch` operation. Their return
17854
+ * values are examined for changes on every call to `$digest`.
17855
+ * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
17856
+ *
17857
+ * `$watchGroup` is more performant than watching each expression individually, and should be
17858
+ * used when the listener does not need to know which expression has changed.
17859
+ * If the listener needs to know which expression has changed,
17860
+ * {@link ng.$rootScope.Scope#$watch $watch()} or
17861
+ * {@link ng.$rootScope.Scope#$watchCollection $watchCollection()} should be used.
17862
+ *
17863
+ * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
17864
+ * watched using {@link ng.$rootScope.Scope#$watch $watch()}
17865
+ *
17866
+ * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
17867
+ * expression in `watchExpressions` changes
17868
+ * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
17869
+ * those of `watchExpression`
17870
+ * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
17871
+ * those of `watchExpression`.
17872
+ *
17873
+ * Note that `newValues` and `oldValues` reflect the differences in each **individual**
17874
+ * expression, and not the difference of the values between each call of the listener.
17875
+ * That means the difference between `newValues` and `oldValues` cannot be used to determine
17876
+ * which expression has changed / remained stable:
17877
+ *
17878
+ * ```js
17879
+ *
17880
+ * $scope.$watchGroup(['v1', 'v2'], function(newValues, oldValues) {
17881
+ * console.log(newValues, oldValues);
17882
+ * });
17883
+ *
17884
+ * // newValues, oldValues initially
17885
+ * // [undefined, undefined], [undefined, undefined]
17886
+ *
17887
+ * $scope.v1 = 'a';
17888
+ * $scope.v2 = 'a';
17889
+ *
17890
+ * // ['a', 'a'], [undefined, undefined]
17891
+ *
17892
+ * $scope.v2 = 'b'
17893
+ *
17894
+ * // v1 hasn't changed since it became `'a'`, therefore its oldValue is still `undefined`
17895
+ * // ['a', 'b'], [undefined, 'a']
17896
+ *
17897
+ * ```
17898
+ *
17899
+ * The `scope` refers to the current scope.
17900
+ * @returns {function()} Returns a de-registration function for all listeners.
17901
+ */
17902
+ $watchGroup: function(watchExpressions, listener) {
17903
+ var oldValues = new Array(watchExpressions.length);
17904
+ var newValues = new Array(watchExpressions.length);
17905
+ var deregisterFns = [];
17906
+ var self = this;
17907
+ var changeReactionScheduled = false;
17908
+ var firstRun = true;
17909
+
17910
+ if (!watchExpressions.length) {
17911
+ // No expressions means we call the listener ASAP
17912
+ var shouldCall = true;
17913
+ self.$evalAsync(function() {
17914
+ if (shouldCall) listener(newValues, newValues, self);
17915
+ });
17916
+ return function deregisterWatchGroup() {
17917
+ shouldCall = false;
17918
+ };
17919
+ }
17920
+
17921
+ if (watchExpressions.length === 1) {
17922
+ // Special case size of one
17923
+ return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
17924
+ newValues[0] = value;
17925
+ oldValues[0] = oldValue;
17926
+ listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
17927
+ });
17928
+ }
17929
+
17930
+ forEach(watchExpressions, function(expr, i) {
17931
+ var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
17932
+ newValues[i] = value;
17933
+ oldValues[i] = oldValue;
17934
+ if (!changeReactionScheduled) {
17935
+ changeReactionScheduled = true;
17936
+ self.$evalAsync(watchGroupAction);
17937
+ }
17938
+ });
17939
+ deregisterFns.push(unwatchFn);
17940
+ });
17941
+
17942
+ function watchGroupAction() {
17943
+ changeReactionScheduled = false;
17944
+
17945
+ if (firstRun) {
17946
+ firstRun = false;
17947
+ listener(newValues, newValues, self);
17948
+ } else {
17949
+ listener(newValues, oldValues, self);
17950
+ }
17951
+ }
17952
+
17953
+ return function deregisterWatchGroup() {
17954
+ while (deregisterFns.length) {
17955
+ deregisterFns.shift()();
17956
+ }
17957
+ };
17958
+ },
17959
+
17960
+
17961
+ /**
17962
+ * @ngdoc method
17963
+ * @name $rootScope.Scope#$watchCollection
17964
+ * @kind function
17965
+ *
17966
+ * @description
17967
+ * Shallow watches the properties of an object and fires whenever any of the properties change
17968
+ * (for arrays, this implies watching the array items; for object maps, this implies watching
17969
+ * the properties). If a change is detected, the `listener` callback is fired.
17970
+ *
17971
+ * - The `obj` collection is observed via standard $watch operation and is examined on every
17972
+ * call to $digest() to see if any items have been added, removed, or moved.
17973
+ * - The `listener` is called whenever anything within the `obj` has changed. Examples include
17974
+ * adding, removing, and moving items belonging to an object or array.
17975
+ *
17976
+ *
17977
+ * # Example
17978
+ * ```js
17979
+ $scope.names = ['igor', 'matias', 'misko', 'james'];
17980
+ $scope.dataCount = 4;
17981
+
17982
+ $scope.$watchCollection('names', function(newNames, oldNames) {
17983
+ $scope.dataCount = newNames.length;
17984
+ });
17985
+
17986
+ expect($scope.dataCount).toEqual(4);
17987
+ $scope.$digest();
17988
+
17989
+ //still at 4 ... no changes
17990
+ expect($scope.dataCount).toEqual(4);
17991
+
17992
+ $scope.names.pop();
17993
+ $scope.$digest();
17994
+
17995
+ //now there's been a change
17996
+ expect($scope.dataCount).toEqual(3);
17997
+ * ```
17998
+ *
17999
+ *
18000
+ * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
18001
+ * expression value should evaluate to an object or an array which is observed on each
18002
+ * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
18003
+ * collection will trigger a call to the `listener`.
18004
+ *
18005
+ * @param {function(newCollection, oldCollection, scope)} listener a callback function called
18006
+ * when a change is detected.
18007
+ * - The `newCollection` object is the newly modified data obtained from the `obj` expression
18008
+ * - The `oldCollection` object is a copy of the former collection data.
18009
+ * Due to performance considerations, the`oldCollection` value is computed only if the
18010
+ * `listener` function declares two or more arguments.
18011
+ * - The `scope` argument refers to the current scope.
18012
+ *
18013
+ * @returns {function()} Returns a de-registration function for this listener. When the
18014
+ * de-registration function is executed, the internal watch operation is terminated.
18015
+ */
18016
+ $watchCollection: function(obj, listener) {
18017
+ $watchCollectionInterceptor.$stateful = true;
18018
+
18019
+ var self = this;
18020
+ // the current value, updated on each dirty-check run
18021
+ var newValue;
18022
+ // a shallow copy of the newValue from the last dirty-check run,
18023
+ // updated to match newValue during dirty-check run
18024
+ var oldValue;
18025
+ // a shallow copy of the newValue from when the last change happened
18026
+ var veryOldValue;
18027
+ // only track veryOldValue if the listener is asking for it
18028
+ var trackVeryOldValue = (listener.length > 1);
18029
+ var changeDetected = 0;
18030
+ var changeDetector = $parse(obj, $watchCollectionInterceptor);
18031
+ var internalArray = [];
18032
+ var internalObject = {};
18033
+ var initRun = true;
18034
+ var oldLength = 0;
18035
+
18036
+ function $watchCollectionInterceptor(_value) {
18037
+ newValue = _value;
18038
+ var newLength, key, bothNaN, newItem, oldItem;
18039
+
18040
+ // If the new value is undefined, then return undefined as the watch may be a one-time watch
18041
+ if (isUndefined(newValue)) return;
18042
+
18043
+ if (!isObject(newValue)) { // if primitive
18044
+ if (oldValue !== newValue) {
18045
+ oldValue = newValue;
18046
+ changeDetected++;
18047
+ }
18048
+ } else if (isArrayLike(newValue)) {
18049
+ if (oldValue !== internalArray) {
18050
+ // we are transitioning from something which was not an array into array.
18051
+ oldValue = internalArray;
18052
+ oldLength = oldValue.length = 0;
18053
+ changeDetected++;
18054
+ }
18055
+
18056
+ newLength = newValue.length;
18057
+
18058
+ if (oldLength !== newLength) {
18059
+ // if lengths do not match we need to trigger change notification
18060
+ changeDetected++;
18061
+ oldValue.length = oldLength = newLength;
18062
+ }
18063
+ // copy the items to oldValue and look for changes.
18064
+ for (var i = 0; i < newLength; i++) {
18065
+ oldItem = oldValue[i];
18066
+ newItem = newValue[i];
18067
+
18068
+ // eslint-disable-next-line no-self-compare
18069
+ bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
18070
+ if (!bothNaN && (oldItem !== newItem)) {
18071
+ changeDetected++;
18072
+ oldValue[i] = newItem;
18073
+ }
18074
+ }
18075
+ } else {
18076
+ if (oldValue !== internalObject) {
18077
+ // we are transitioning from something which was not an object into object.
18078
+ oldValue = internalObject = {};
18079
+ oldLength = 0;
18080
+ changeDetected++;
18081
+ }
18082
+ // copy the items to oldValue and look for changes.
18083
+ newLength = 0;
18084
+ for (key in newValue) {
18085
+ if (hasOwnProperty.call(newValue, key)) {
18086
+ newLength++;
18087
+ newItem = newValue[key];
18088
+ oldItem = oldValue[key];
18089
+
18090
+ if (key in oldValue) {
18091
+ // eslint-disable-next-line no-self-compare
18092
+ bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
18093
+ if (!bothNaN && (oldItem !== newItem)) {
18094
+ changeDetected++;
18095
+ oldValue[key] = newItem;
18096
+ }
18097
+ } else {
18098
+ oldLength++;
18099
+ oldValue[key] = newItem;
18100
+ changeDetected++;
18101
+ }
18102
+ }
18103
+ }
18104
+ if (oldLength > newLength) {
18105
+ // we used to have more keys, need to find them and destroy them.
18106
+ changeDetected++;
18107
+ for (key in oldValue) {
18108
+ if (!hasOwnProperty.call(newValue, key)) {
18109
+ oldLength--;
18110
+ delete oldValue[key];
18111
+ }
18112
+ }
18113
+ }
18114
+ }
18115
+ return changeDetected;
18116
+ }
18117
+
18118
+ function $watchCollectionAction() {
18119
+ if (initRun) {
18120
+ initRun = false;
18121
+ listener(newValue, newValue, self);
18122
+ } else {
18123
+ listener(newValue, veryOldValue, self);
18124
+ }
18125
+
18126
+ // make a copy for the next time a collection is changed
18127
+ if (trackVeryOldValue) {
18128
+ if (!isObject(newValue)) {
18129
+ //primitive
18130
+ veryOldValue = newValue;
18131
+ } else if (isArrayLike(newValue)) {
18132
+ veryOldValue = new Array(newValue.length);
18133
+ for (var i = 0; i < newValue.length; i++) {
18134
+ veryOldValue[i] = newValue[i];
18135
+ }
18136
+ } else { // if object
18137
+ veryOldValue = {};
18138
+ for (var key in newValue) {
18139
+ if (hasOwnProperty.call(newValue, key)) {
18140
+ veryOldValue[key] = newValue[key];
18141
+ }
18142
+ }
18143
+ }
18144
+ }
18145
+ }
18146
+
18147
+ return this.$watch(changeDetector, $watchCollectionAction);
18148
+ },
18149
+
18150
+ /**
18151
+ * @ngdoc method
18152
+ * @name $rootScope.Scope#$digest
18153
+ * @kind function
18154
+ *
18155
+ * @description
18156
+ * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
18157
+ * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
18158
+ * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
18159
+ * until no more listeners are firing. This means that it is possible to get into an infinite
18160
+ * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
18161
+ * iterations exceeds 10.
18162
+ *
18163
+ * Usually, you don't call `$digest()` directly in
18164
+ * {@link ng.directive:ngController controllers} or in
18165
+ * {@link ng.$compileProvider#directive directives}.
18166
+ * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
18167
+ * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
18168
+ *
18169
+ * If you want to be notified whenever `$digest()` is called,
18170
+ * you can register a `watchExpression` function with
18171
+ * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
18172
+ *
18173
+ * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
18174
+ *
18175
+ * # Example
18176
+ * ```js
18177
+ var scope = ...;
18178
+ scope.name = 'misko';
18179
+ scope.counter = 0;
18180
+
18181
+ expect(scope.counter).toEqual(0);
18182
+ scope.$watch('name', function(newValue, oldValue) {
18183
+ scope.counter = scope.counter + 1;
18184
+ });
18185
+ expect(scope.counter).toEqual(0);
18186
+
18187
+ scope.$digest();
18188
+ // the listener is always called during the first $digest loop after it was registered
18189
+ expect(scope.counter).toEqual(1);
18190
+
18191
+ scope.$digest();
18192
+ // but now it will not be called unless the value changes
18193
+ expect(scope.counter).toEqual(1);
18194
+
18195
+ scope.name = 'adam';
18196
+ scope.$digest();
18197
+ expect(scope.counter).toEqual(2);
18198
+ * ```
18199
+ *
18200
+ */
18201
+ $digest: function() {
18202
+ var watch, value, last, fn, get,
18203
+ watchers,
18204
+ dirty, ttl = TTL,
18205
+ next, current, target = this,
18206
+ watchLog = [],
18207
+ logIdx, asyncTask;
18208
+
18209
+ beginPhase('$digest');
18210
+ // Check for changes to browser url that happened in sync before the call to $digest
18211
+ $browser.$$checkUrlChange();
18212
+
18213
+ if (this === $rootScope && applyAsyncId !== null) {
18214
+ // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
18215
+ // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
18216
+ $browser.defer.cancel(applyAsyncId);
18217
+ flushApplyAsync();
18218
+ }
18219
+
18220
+ lastDirtyWatch = null;
18221
+
18222
+ do { // "while dirty" loop
18223
+ dirty = false;
18224
+ current = target;
18225
+
18226
+ // It's safe for asyncQueuePosition to be a local variable here because this loop can't
18227
+ // be reentered recursively. Calling $digest from a function passed to $evalAsync would
18228
+ // lead to a '$digest already in progress' error.
18229
+ for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) {
18230
+ try {
18231
+ asyncTask = asyncQueue[asyncQueuePosition];
18232
+ fn = asyncTask.fn;
18233
+ fn(asyncTask.scope, asyncTask.locals);
18234
+ } catch (e) {
18235
+ $exceptionHandler(e);
18236
+ }
18237
+ lastDirtyWatch = null;
18238
+ }
18239
+ asyncQueue.length = 0;
18240
+
18241
+ traverseScopesLoop:
18242
+ do { // "traverse the scopes" loop
18243
+ if ((watchers = current.$$watchers)) {
18244
+ // process our watches
18245
+ watchers.$$digestWatchIndex = watchers.length;
18246
+ while (watchers.$$digestWatchIndex--) {
18247
+ try {
18248
+ watch = watchers[watchers.$$digestWatchIndex];
18249
+ // Most common watches are on primitives, in which case we can short
18250
+ // circuit it with === operator, only when === fails do we use .equals
18251
+ if (watch) {
18252
+ get = watch.get;
18253
+ if ((value = get(current)) !== (last = watch.last) &&
18254
+ !(watch.eq
18255
+ ? equals(value, last)
18256
+ : (isNumberNaN(value) && isNumberNaN(last)))) {
18257
+ dirty = true;
18258
+ lastDirtyWatch = watch;
18259
+ watch.last = watch.eq ? copy(value, null) : value;
18260
+ fn = watch.fn;
18261
+ fn(value, ((last === initWatchVal) ? value : last), current);
18262
+ if (ttl < 5) {
18263
+ logIdx = 4 - ttl;
18264
+ if (!watchLog[logIdx]) watchLog[logIdx] = [];
18265
+ watchLog[logIdx].push({
18266
+ msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
18267
+ newVal: value,
18268
+ oldVal: last
18269
+ });
18270
+ }
18271
+ } else if (watch === lastDirtyWatch) {
18272
+ // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
18273
+ // have already been tested.
18274
+ dirty = false;
18275
+ break traverseScopesLoop;
18276
+ }
18277
+ }
18278
+ } catch (e) {
18279
+ $exceptionHandler(e);
18280
+ }
18281
+ }
18282
+ }
18283
+
18284
+ // Insanity Warning: scope depth-first traversal
18285
+ // yes, this code is a bit crazy, but it works and we have tests to prove it!
18286
+ // this piece should be kept in sync with the traversal in $broadcast
18287
+ if (!(next = ((current.$$watchersCount && current.$$childHead) ||
18288
+ (current !== target && current.$$nextSibling)))) {
18289
+ while (current !== target && !(next = current.$$nextSibling)) {
18290
+ current = current.$parent;
18291
+ }
18292
+ }
18293
+ } while ((current = next));
18294
+
18295
+ // `break traverseScopesLoop;` takes us to here
18296
+
18297
+ if ((dirty || asyncQueue.length) && !(ttl--)) {
18298
+ clearPhase();
18299
+ throw $rootScopeMinErr('infdig',
18300
+ '{0} $digest() iterations reached. Aborting!\n' +
18301
+ 'Watchers fired in the last 5 iterations: {1}',
18302
+ TTL, watchLog);
18303
+ }
18304
+
18305
+ } while (dirty || asyncQueue.length);
18306
+
18307
+ clearPhase();
18308
+
18309
+ // postDigestQueuePosition isn't local here because this loop can be reentered recursively.
18310
+ while (postDigestQueuePosition < postDigestQueue.length) {
18311
+ try {
18312
+ postDigestQueue[postDigestQueuePosition++]();
18313
+ } catch (e) {
18314
+ $exceptionHandler(e);
18315
+ }
18316
+ }
18317
+ postDigestQueue.length = postDigestQueuePosition = 0;
18318
+
18319
+ // Check for changes to browser url that happened during the $digest
18320
+ // (for which no event is fired; e.g. via `history.pushState()`)
18321
+ $browser.$$checkUrlChange();
18322
+ },
18323
+
18324
+
18325
+ /**
18326
+ * @ngdoc event
18327
+ * @name $rootScope.Scope#$destroy
18328
+ * @eventType broadcast on scope being destroyed
18329
+ *
18330
+ * @description
18331
+ * Broadcasted when a scope and its children are being destroyed.
18332
+ *
18333
+ * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
18334
+ * clean up DOM bindings before an element is removed from the DOM.
18335
+ */
18336
+
18337
+ /**
18338
+ * @ngdoc method
18339
+ * @name $rootScope.Scope#$destroy
18340
+ * @kind function
18341
+ *
18342
+ * @description
18343
+ * Removes the current scope (and all of its children) from the parent scope. Removal implies
18344
+ * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
18345
+ * propagate to the current scope and its children. Removal also implies that the current
18346
+ * scope is eligible for garbage collection.
18347
+ *
18348
+ * The `$destroy()` is usually used by directives such as
18349
+ * {@link ng.directive:ngRepeat ngRepeat} for managing the
18350
+ * unrolling of the loop.
18351
+ *
18352
+ * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
18353
+ * Application code can register a `$destroy` event handler that will give it a chance to
18354
+ * perform any necessary cleanup.
18355
+ *
18356
+ * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
18357
+ * clean up DOM bindings before an element is removed from the DOM.
18358
+ */
18359
+ $destroy: function() {
18360
+ // We can't destroy a scope that has been already destroyed.
18361
+ if (this.$$destroyed) return;
18362
+ var parent = this.$parent;
18363
+
18364
+ this.$broadcast('$destroy');
18365
+ this.$$destroyed = true;
18366
+
18367
+ if (this === $rootScope) {
18368
+ //Remove handlers attached to window when $rootScope is removed
18369
+ $browser.$$applicationDestroyed();
18370
+ }
18371
+
18372
+ incrementWatchersCount(this, -this.$$watchersCount);
18373
+ for (var eventName in this.$$listenerCount) {
18374
+ decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
18375
+ }
18376
+
18377
+ // sever all the references to parent scopes (after this cleanup, the current scope should
18378
+ // not be retained by any of our references and should be eligible for garbage collection)
18379
+ if (parent && parent.$$childHead === this) parent.$$childHead = this.$$nextSibling;
18380
+ if (parent && parent.$$childTail === this) parent.$$childTail = this.$$prevSibling;
18381
+ if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
18382
+ if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
18383
+
18384
+ // Disable listeners, watchers and apply/digest methods
18385
+ this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
18386
+ this.$on = this.$watch = this.$watchGroup = function() { return noop; };
18387
+ this.$$listeners = {};
18388
+
18389
+ // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
18390
+ this.$$nextSibling = null;
18391
+ cleanUpScope(this);
18392
+ },
18393
+
18394
+ /**
18395
+ * @ngdoc method
18396
+ * @name $rootScope.Scope#$eval
18397
+ * @kind function
18398
+ *
18399
+ * @description
18400
+ * Executes the `expression` on the current scope and returns the result. Any exceptions in
18401
+ * the expression are propagated (uncaught). This is useful when evaluating Angular
18402
+ * expressions.
18403
+ *
18404
+ * # Example
18405
+ * ```js
18406
+ var scope = ng.$rootScope.Scope();
18407
+ scope.a = 1;
18408
+ scope.b = 2;
18409
+
18410
+ expect(scope.$eval('a+b')).toEqual(3);
18411
+ expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
18412
+ * ```
18413
+ *
18414
+ * @param {(string|function())=} expression An angular expression to be executed.
18415
+ *
18416
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18417
+ * - `function(scope)`: execute the function with the current `scope` parameter.
18418
+ *
18419
+ * @param {(object)=} locals Local variables object, useful for overriding values in scope.
18420
+ * @returns {*} The result of evaluating the expression.
18421
+ */
18422
+ $eval: function(expr, locals) {
18423
+ return $parse(expr)(this, locals);
18424
+ },
18425
+
18426
+ /**
18427
+ * @ngdoc method
18428
+ * @name $rootScope.Scope#$evalAsync
18429
+ * @kind function
18430
+ *
18431
+ * @description
18432
+ * Executes the expression on the current scope at a later point in time.
18433
+ *
18434
+ * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
18435
+ * that:
18436
+ *
18437
+ * - it will execute after the function that scheduled the evaluation (preferably before DOM
18438
+ * rendering).
18439
+ * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
18440
+ * `expression` execution.
18441
+ *
18442
+ * Any exceptions from the execution of the expression are forwarded to the
18443
+ * {@link ng.$exceptionHandler $exceptionHandler} service.
18444
+ *
18445
+ * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
18446
+ * will be scheduled. However, it is encouraged to always call code that changes the model
18447
+ * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
18448
+ *
18449
+ * @param {(string|function())=} expression An angular expression to be executed.
18450
+ *
18451
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18452
+ * - `function(scope)`: execute the function with the current `scope` parameter.
18453
+ *
18454
+ * @param {(object)=} locals Local variables object, useful for overriding values in scope.
18455
+ */
18456
+ $evalAsync: function(expr, locals) {
18457
+ // if we are outside of an $digest loop and this is the first time we are scheduling async
18458
+ // task also schedule async auto-flush
18459
+ if (!$rootScope.$$phase && !asyncQueue.length) {
18460
+ $browser.defer(function() {
18461
+ if (asyncQueue.length) {
18462
+ $rootScope.$digest();
18463
+ }
18464
+ });
18465
+ }
18466
+
18467
+ asyncQueue.push({scope: this, fn: $parse(expr), locals: locals});
18468
+ },
18469
+
18470
+ $$postDigest: function(fn) {
18471
+ postDigestQueue.push(fn);
18472
+ },
18473
+
18474
+ /**
18475
+ * @ngdoc method
18476
+ * @name $rootScope.Scope#$apply
18477
+ * @kind function
18478
+ *
18479
+ * @description
18480
+ * `$apply()` is used to execute an expression in angular from outside of the angular
18481
+ * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
18482
+ * Because we are calling into the angular framework we need to perform proper scope life
18483
+ * cycle of {@link ng.$exceptionHandler exception handling},
18484
+ * {@link ng.$rootScope.Scope#$digest executing watches}.
18485
+ *
18486
+ * ## Life cycle
18487
+ *
18488
+ * # Pseudo-Code of `$apply()`
18489
+ * ```js
18490
+ function $apply(expr) {
18491
+ try {
18492
+ return $eval(expr);
18493
+ } catch (e) {
18494
+ $exceptionHandler(e);
18495
+ } finally {
18496
+ $root.$digest();
18497
+ }
18498
+ }
18499
+ * ```
18500
+ *
18501
+ *
18502
+ * Scope's `$apply()` method transitions through the following stages:
18503
+ *
18504
+ * 1. The {@link guide/expression expression} is executed using the
18505
+ * {@link ng.$rootScope.Scope#$eval $eval()} method.
18506
+ * 2. Any exceptions from the execution of the expression are forwarded to the
18507
+ * {@link ng.$exceptionHandler $exceptionHandler} service.
18508
+ * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
18509
+ * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
18510
+ *
18511
+ *
18512
+ * @param {(string|function())=} exp An angular expression to be executed.
18513
+ *
18514
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18515
+ * - `function(scope)`: execute the function with current `scope` parameter.
18516
+ *
18517
+ * @returns {*} The result of evaluating the expression.
18518
+ */
18519
+ $apply: function(expr) {
18520
+ try {
18521
+ beginPhase('$apply');
18522
+ try {
18523
+ return this.$eval(expr);
18524
+ } finally {
18525
+ clearPhase();
18526
+ }
18527
+ } catch (e) {
18528
+ $exceptionHandler(e);
18529
+ } finally {
18530
+ try {
18531
+ $rootScope.$digest();
18532
+ } catch (e) {
18533
+ $exceptionHandler(e);
18534
+ // eslint-disable-next-line no-unsafe-finally
18535
+ throw e;
18536
+ }
18537
+ }
18538
+ },
18539
+
18540
+ /**
18541
+ * @ngdoc method
18542
+ * @name $rootScope.Scope#$applyAsync
18543
+ * @kind function
18544
+ *
18545
+ * @description
18546
+ * Schedule the invocation of $apply to occur at a later time. The actual time difference
18547
+ * varies across browsers, but is typically around ~10 milliseconds.
18548
+ *
18549
+ * This can be used to queue up multiple expressions which need to be evaluated in the same
18550
+ * digest.
18551
+ *
18552
+ * @param {(string|function())=} exp An angular expression to be executed.
18553
+ *
18554
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18555
+ * - `function(scope)`: execute the function with current `scope` parameter.
18556
+ */
18557
+ $applyAsync: function(expr) {
18558
+ var scope = this;
18559
+ if (expr) {
18560
+ applyAsyncQueue.push($applyAsyncExpression);
18561
+ }
18562
+ expr = $parse(expr);
18563
+ scheduleApplyAsync();
18564
+
18565
+ function $applyAsyncExpression() {
18566
+ scope.$eval(expr);
18567
+ }
18568
+ },
18569
+
18570
+ /**
18571
+ * @ngdoc method
18572
+ * @name $rootScope.Scope#$on
18573
+ * @kind function
18574
+ *
18575
+ * @description
18576
+ * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
18577
+ * discussion of event life cycle.
18578
+ *
18579
+ * The event listener function format is: `function(event, args...)`. The `event` object
18580
+ * passed into the listener has the following attributes:
18581
+ *
18582
+ * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
18583
+ * `$broadcast`-ed.
18584
+ * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
18585
+ * event propagates through the scope hierarchy, this property is set to null.
18586
+ * - `name` - `{string}`: name of the event.
18587
+ * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
18588
+ * further event propagation (available only for events that were `$emit`-ed).
18589
+ * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
18590
+ * to true.
18591
+ * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
18592
+ *
18593
+ * @param {string} name Event name to listen on.
18594
+ * @param {function(event, ...args)} listener Function to call when the event is emitted.
18595
+ * @returns {function()} Returns a deregistration function for this listener.
18596
+ */
18597
+ $on: function(name, listener) {
18598
+ var namedListeners = this.$$listeners[name];
18599
+ if (!namedListeners) {
18600
+ this.$$listeners[name] = namedListeners = [];
18601
+ }
18602
+ namedListeners.push(listener);
18603
+
18604
+ var current = this;
18605
+ do {
18606
+ if (!current.$$listenerCount[name]) {
18607
+ current.$$listenerCount[name] = 0;
18608
+ }
18609
+ current.$$listenerCount[name]++;
18610
+ } while ((current = current.$parent));
18611
+
18612
+ var self = this;
18613
+ return function() {
18614
+ var indexOfListener = namedListeners.indexOf(listener);
18615
+ if (indexOfListener !== -1) {
18616
+ namedListeners[indexOfListener] = null;
18617
+ decrementListenerCount(self, 1, name);
18618
+ }
18619
+ };
18620
+ },
18621
+
18622
+
18623
+ /**
18624
+ * @ngdoc method
18625
+ * @name $rootScope.Scope#$emit
18626
+ * @kind function
18627
+ *
18628
+ * @description
18629
+ * Dispatches an event `name` upwards through the scope hierarchy notifying the
18630
+ * registered {@link ng.$rootScope.Scope#$on} listeners.
18631
+ *
18632
+ * The event life cycle starts at the scope on which `$emit` was called. All
18633
+ * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
18634
+ * notified. Afterwards, the event traverses upwards toward the root scope and calls all
18635
+ * registered listeners along the way. The event will stop propagating if one of the listeners
18636
+ * cancels it.
18637
+ *
18638
+ * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
18639
+ * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
18640
+ *
18641
+ * @param {string} name Event name to emit.
18642
+ * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
18643
+ * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
18644
+ */
18645
+ $emit: function(name, args) {
18646
+ var empty = [],
18647
+ namedListeners,
18648
+ scope = this,
18649
+ stopPropagation = false,
18650
+ event = {
18651
+ name: name,
18652
+ targetScope: scope,
18653
+ stopPropagation: function() {stopPropagation = true;},
18654
+ preventDefault: function() {
18655
+ event.defaultPrevented = true;
18656
+ },
18657
+ defaultPrevented: false
18658
+ },
18659
+ listenerArgs = concat([event], arguments, 1),
18660
+ i, length;
18661
+
18662
+ do {
18663
+ namedListeners = scope.$$listeners[name] || empty;
18664
+ event.currentScope = scope;
18665
+ for (i = 0, length = namedListeners.length; i < length; i++) {
18666
+
18667
+ // if listeners were deregistered, defragment the array
18668
+ if (!namedListeners[i]) {
18669
+ namedListeners.splice(i, 1);
18670
+ i--;
18671
+ length--;
18672
+ continue;
18673
+ }
18674
+ try {
18675
+ //allow all listeners attached to the current scope to run
18676
+ namedListeners[i].apply(null, listenerArgs);
18677
+ } catch (e) {
18678
+ $exceptionHandler(e);
18679
+ }
18680
+ }
18681
+ //if any listener on the current scope stops propagation, prevent bubbling
18682
+ if (stopPropagation) {
18683
+ event.currentScope = null;
18684
+ return event;
18685
+ }
18686
+ //traverse upwards
18687
+ scope = scope.$parent;
18688
+ } while (scope);
18689
+
18690
+ event.currentScope = null;
18691
+
18692
+ return event;
18693
+ },
18694
+
18695
+
18696
+ /**
18697
+ * @ngdoc method
18698
+ * @name $rootScope.Scope#$broadcast
18699
+ * @kind function
18700
+ *
18701
+ * @description
18702
+ * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
18703
+ * registered {@link ng.$rootScope.Scope#$on} listeners.
18704
+ *
18705
+ * The event life cycle starts at the scope on which `$broadcast` was called. All
18706
+ * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
18707
+ * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
18708
+ * scope and calls all registered listeners along the way. The event cannot be canceled.
18709
+ *
18710
+ * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
18711
+ * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
18712
+ *
18713
+ * @param {string} name Event name to broadcast.
18714
+ * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
18715
+ * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
18716
+ */
18717
+ $broadcast: function(name, args) {
18718
+ var target = this,
18719
+ current = target,
18720
+ next = target,
18721
+ event = {
18722
+ name: name,
18723
+ targetScope: target,
18724
+ preventDefault: function() {
18725
+ event.defaultPrevented = true;
18726
+ },
18727
+ defaultPrevented: false
18728
+ };
18729
+
18730
+ if (!target.$$listenerCount[name]) return event;
18731
+
18732
+ var listenerArgs = concat([event], arguments, 1),
18733
+ listeners, i, length;
18734
+
18735
+ //down while you can, then up and next sibling or up and next sibling until back at root
18736
+ while ((current = next)) {
18737
+ event.currentScope = current;
18738
+ listeners = current.$$listeners[name] || [];
18739
+ for (i = 0, length = listeners.length; i < length; i++) {
18740
+ // if listeners were deregistered, defragment the array
18741
+ if (!listeners[i]) {
18742
+ listeners.splice(i, 1);
18743
+ i--;
18744
+ length--;
18745
+ continue;
18746
+ }
18747
+
18748
+ try {
18749
+ listeners[i].apply(null, listenerArgs);
18750
+ } catch (e) {
18751
+ $exceptionHandler(e);
18752
+ }
18753
+ }
18754
+
18755
+ // Insanity Warning: scope depth-first traversal
18756
+ // yes, this code is a bit crazy, but it works and we have tests to prove it!
18757
+ // this piece should be kept in sync with the traversal in $digest
18758
+ // (though it differs due to having the extra check for $$listenerCount)
18759
+ if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
18760
+ (current !== target && current.$$nextSibling)))) {
18761
+ while (current !== target && !(next = current.$$nextSibling)) {
18762
+ current = current.$parent;
18763
+ }
18764
+ }
18765
+ }
18766
+
18767
+ event.currentScope = null;
18768
+ return event;
18769
+ }
18770
+ };
18771
+
18772
+ var $rootScope = new Scope();
18773
+
18774
+ //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
18775
+ var asyncQueue = $rootScope.$$asyncQueue = [];
18776
+ var postDigestQueue = $rootScope.$$postDigestQueue = [];
18777
+ var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
18778
+
18779
+ var postDigestQueuePosition = 0;
18780
+
18781
+ return $rootScope;
18782
+
18783
+
18784
+ function beginPhase(phase) {
18785
+ if ($rootScope.$$phase) {
18786
+ throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
18787
+ }
18788
+
18789
+ $rootScope.$$phase = phase;
18790
+ }
18791
+
18792
+ function clearPhase() {
18793
+ $rootScope.$$phase = null;
18794
+ }
18795
+
18796
+ function incrementWatchersCount(current, count) {
18797
+ do {
18798
+ current.$$watchersCount += count;
18799
+ } while ((current = current.$parent));
18800
+ }
18801
+
18802
+ function decrementListenerCount(current, count, name) {
18803
+ do {
18804
+ current.$$listenerCount[name] -= count;
18805
+
18806
+ if (current.$$listenerCount[name] === 0) {
18807
+ delete current.$$listenerCount[name];
18808
+ }
18809
+ } while ((current = current.$parent));
18810
+ }
18811
+
18812
+ /**
18813
+ * function used as an initial value for watchers.
18814
+ * because it's unique we can easily tell it apart from other values
18815
+ */
18816
+ function initWatchVal() {}
18817
+
18818
+ function flushApplyAsync() {
18819
+ while (applyAsyncQueue.length) {
18820
+ try {
18821
+ applyAsyncQueue.shift()();
18822
+ } catch (e) {
18823
+ $exceptionHandler(e);
18824
+ }
18825
+ }
18826
+ applyAsyncId = null;
18827
+ }
18828
+
18829
+ function scheduleApplyAsync() {
18830
+ if (applyAsyncId === null) {
18831
+ applyAsyncId = $browser.defer(function() {
18832
+ $rootScope.$apply(flushApplyAsync);
18833
+ });
18834
+ }
18835
+ }
18836
+ }];
18837
+ }
18838
+
18839
+ /**
18840
+ * @ngdoc service
18841
+ * @name $rootElement
18842
+ *
18843
+ * @description
18844
+ * The root element of Angular application. This is either the element where {@link
18845
+ * ng.directive:ngApp ngApp} was declared or the element passed into
18846
+ * {@link angular.bootstrap}. The element represents the root element of application. It is also the
18847
+ * location where the application's {@link auto.$injector $injector} service gets
18848
+ * published, and can be retrieved using `$rootElement.injector()`.
18849
+ */
18850
+
18851
+
18852
+ // the implementation is in angular.bootstrap
18853
+
18854
+ /**
18855
+ * @this
18856
+ * @description
18857
+ * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
18858
+ */
18859
+ function $$SanitizeUriProvider() {
18860
+ var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
18861
+ imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
18862
+
18863
+ /**
18864
+ * @description
18865
+ * Retrieves or overrides the default regular expression that is used for whitelisting of safe
18866
+ * urls during a[href] sanitization.
18867
+ *
18868
+ * The sanitization is a security measure aimed at prevent XSS attacks via html links.
18869
+ *
18870
+ * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
18871
+ * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
18872
+ * regular expression. If a match is found, the original url is written into the dom. Otherwise,
18873
+ * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
18874
+ *
18875
+ * @param {RegExp=} regexp New regexp to whitelist urls with.
18876
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
18877
+ * chaining otherwise.
18878
+ */
18879
+ this.aHrefSanitizationWhitelist = function(regexp) {
18880
+ if (isDefined(regexp)) {
18881
+ aHrefSanitizationWhitelist = regexp;
18882
+ return this;
18883
+ }
18884
+ return aHrefSanitizationWhitelist;
18885
+ };
18886
+
18887
+
18888
+ /**
18889
+ * @description
18890
+ * Retrieves or overrides the default regular expression that is used for whitelisting of safe
18891
+ * urls during img[src] sanitization.
18892
+ *
18893
+ * The sanitization is a security measure aimed at prevent XSS attacks via html links.
18894
+ *
18895
+ * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
18896
+ * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
18897
+ * regular expression. If a match is found, the original url is written into the dom. Otherwise,
18898
+ * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
18899
+ *
18900
+ * @param {RegExp=} regexp New regexp to whitelist urls with.
18901
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
18902
+ * chaining otherwise.
18903
+ */
18904
+ this.imgSrcSanitizationWhitelist = function(regexp) {
18905
+ if (isDefined(regexp)) {
18906
+ imgSrcSanitizationWhitelist = regexp;
18907
+ return this;
18908
+ }
18909
+ return imgSrcSanitizationWhitelist;
18910
+ };
18911
+
18912
+ this.$get = function() {
18913
+ return function sanitizeUri(uri, isImage) {
18914
+ var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
18915
+ var normalizedVal;
18916
+ normalizedVal = urlResolve(uri).href;
18917
+ if (normalizedVal !== '' && !normalizedVal.match(regex)) {
18918
+ return 'unsafe:' + normalizedVal;
18919
+ }
18920
+ return uri;
18921
+ };
18922
+ };
18923
+ }
18924
+
18925
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
18926
+ * Any commits to this file should be reviewed with security in mind. *
18927
+ * Changes to this file can potentially create security vulnerabilities. *
18928
+ * An approval from 2 Core members with history of modifying *
18929
+ * this file is required. *
18930
+ * *
18931
+ * Does the change somehow allow for arbitrary javascript to be executed? *
18932
+ * Or allows for someone to change the prototype of built-in objects? *
18933
+ * Or gives undesired access to variables likes document or window? *
18934
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
18935
+
18936
+ /* exported $SceProvider, $SceDelegateProvider */
18937
+
18938
+ var $sceMinErr = minErr('$sce');
18939
+
18940
+ var SCE_CONTEXTS = {
18941
+ // HTML is used when there's HTML rendered (e.g. ng-bind-html, iframe srcdoc binding).
18942
+ HTML: 'html',
18943
+
18944
+ // Style statements or stylesheets. Currently unused in AngularJS.
18945
+ CSS: 'css',
18946
+
18947
+ // An URL used in a context where it does not refer to a resource that loads code. Currently
18948
+ // unused in AngularJS.
18949
+ URL: 'url',
18950
+
18951
+ // RESOURCE_URL is a subtype of URL used where the referred-to resource could be interpreted as
18952
+ // code. (e.g. ng-include, script src binding, templateUrl)
18953
+ RESOURCE_URL: 'resourceUrl',
18954
+
18955
+ // Script. Currently unused in AngularJS.
18956
+ JS: 'js'
18957
+ };
18958
+
18959
+ // Helper functions follow.
18960
+
18961
+ var UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g;
18962
+
18963
+ function snakeToCamel(name) {
18964
+ return name
18965
+ .replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace);
18966
+ }
18967
+
18968
+ function adjustMatcher(matcher) {
18969
+ if (matcher === 'self') {
18970
+ return matcher;
18971
+ } else if (isString(matcher)) {
18972
+ // Strings match exactly except for 2 wildcards - '*' and '**'.
18973
+ // '*' matches any character except those from the set ':/.?&'.
18974
+ // '**' matches any character (like .* in a RegExp).
18975
+ // More than 2 *'s raises an error as it's ill defined.
18976
+ if (matcher.indexOf('***') > -1) {
18977
+ throw $sceMinErr('iwcard',
18978
+ 'Illegal sequence *** in string matcher. String: {0}', matcher);
18979
+ }
18980
+ matcher = escapeForRegexp(matcher).
18981
+ replace(/\\\*\\\*/g, '.*').
18982
+ replace(/\\\*/g, '[^:/.?&;]*');
18983
+ return new RegExp('^' + matcher + '$');
18984
+ } else if (isRegExp(matcher)) {
18985
+ // The only other type of matcher allowed is a Regexp.
18986
+ // Match entire URL / disallow partial matches.
18987
+ // Flags are reset (i.e. no global, ignoreCase or multiline)
18988
+ return new RegExp('^' + matcher.source + '$');
18989
+ } else {
18990
+ throw $sceMinErr('imatcher',
18991
+ 'Matchers may only be "self", string patterns or RegExp objects');
18992
+ }
18993
+ }
18994
+
18995
+
18996
+ function adjustMatchers(matchers) {
18997
+ var adjustedMatchers = [];
18998
+ if (isDefined(matchers)) {
18999
+ forEach(matchers, function(matcher) {
19000
+ adjustedMatchers.push(adjustMatcher(matcher));
19001
+ });
19002
+ }
19003
+ return adjustedMatchers;
19004
+ }
19005
+
19006
+
19007
+ /**
19008
+ * @ngdoc service
19009
+ * @name $sceDelegate
19010
+ * @kind function
19011
+ *
19012
+ * @description
19013
+ *
19014
+ * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
19015
+ * Contextual Escaping (SCE)} services to AngularJS.
19016
+ *
19017
+ * For an overview of this service and the functionnality it provides in AngularJS, see the main
19018
+ * page for {@link ng.$sce SCE}. The current page is targeted for developers who need to alter how
19019
+ * SCE works in their application, which shouldn't be needed in most cases.
19020
+ *
19021
+ * <div class="alert alert-danger">
19022
+ * AngularJS strongly relies on contextual escaping for the security of bindings: disabling or
19023
+ * modifying this might cause cross site scripting (XSS) vulnerabilities. For libraries owners,
19024
+ * changes to this service will also influence users, so be extra careful and document your changes.
19025
+ * </div>
19026
+ *
19027
+ * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
19028
+ * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
19029
+ * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
19030
+ * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
19031
+ * work because `$sce` delegates to `$sceDelegate` for these operations.
19032
+ *
19033
+ * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
19034
+ *
19035
+ * The default instance of `$sceDelegate` should work out of the box with little pain. While you
19036
+ * can override it completely to change the behavior of `$sce`, the common case would
19037
+ * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
19038
+ * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
19039
+ * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
19040
+ * $sceDelegateProvider.resourceUrlWhitelist} and {@link
19041
+ * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
19042
+ */
19043
+
19044
+ /**
19045
+ * @ngdoc provider
19046
+ * @name $sceDelegateProvider
19047
+ * @this
19048
+ *
19049
+ * @description
19050
+ *
19051
+ * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
19052
+ * $sceDelegate service}, used as a delegate for {@link ng.$sce Strict Contextual Escaping (SCE)}.
19053
+ *
19054
+ * The `$sceDelegateProvider` allows one to get/set the whitelists and blacklists used to ensure
19055
+ * that the URLs used for sourcing AngularJS templates and other script-running URLs are safe (all
19056
+ * places that use the `$sce.RESOURCE_URL` context). See
19057
+ * {@link ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist}
19058
+ * and
19059
+ * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist},
19060
+ *
19061
+ * For the general details about this service in Angular, read the main page for {@link ng.$sce
19062
+ * Strict Contextual Escaping (SCE)}.
19063
+ *
19064
+ * **Example**: Consider the following case. <a name="example"></a>
19065
+ *
19066
+ * - your app is hosted at url `http://myapp.example.com/`
19067
+ * - but some of your templates are hosted on other domains you control such as
19068
+ * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc.
19069
+ * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
19070
+ *
19071
+ * Here is what a secure configuration for this scenario might look like:
19072
+ *
19073
+ * ```
19074
+ * angular.module('myApp', []).config(function($sceDelegateProvider) {
19075
+ * $sceDelegateProvider.resourceUrlWhitelist([
19076
+ * // Allow same origin resource loads.
19077
+ * 'self',
19078
+ * // Allow loading from our assets domain. Notice the difference between * and **.
19079
+ * 'http://srv*.assets.example.com/**'
19080
+ * ]);
19081
+ *
19082
+ * // The blacklist overrides the whitelist so the open redirect here is blocked.
19083
+ * $sceDelegateProvider.resourceUrlBlacklist([
19084
+ * 'http://myapp.example.com/clickThru**'
19085
+ * ]);
19086
+ * });
19087
+ * ```
19088
+ * Note that an empty whitelist will block every resource URL from being loaded, and will require
19089
+ * you to manually mark each one as trusted with `$sce.trustAsResourceUrl`. However, templates
19090
+ * requested by {@link ng.$templateRequest $templateRequest} that are present in
19091
+ * {@link ng.$templateCache $templateCache} will not go through this check. If you have a mechanism
19092
+ * to populate your templates in that cache at config time, then it is a good idea to remove 'self'
19093
+ * from that whitelist. This helps to mitigate the security impact of certain types of issues, like
19094
+ * for instance attacker-controlled `ng-includes`.
19095
+ */
19096
+
19097
+ function $SceDelegateProvider() {
19098
+ this.SCE_CONTEXTS = SCE_CONTEXTS;
19099
+
19100
+ // Resource URLs can also be trusted by policy.
19101
+ var resourceUrlWhitelist = ['self'],
19102
+ resourceUrlBlacklist = [];
19103
+
19104
+ /**
19105
+ * @ngdoc method
19106
+ * @name $sceDelegateProvider#resourceUrlWhitelist
19107
+ * @kind function
19108
+ *
19109
+ * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
19110
+ * provided. This must be an array or null. A snapshot of this array is used so further
19111
+ * changes to the array are ignored.
19112
+ * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
19113
+ * allowed in this array.
19114
+ *
19115
+ * @return {Array} The currently set whitelist array.
19116
+ *
19117
+ * @description
19118
+ * Sets/Gets the whitelist of trusted resource URLs.
19119
+ *
19120
+ * The **default value** when no whitelist has been explicitly set is `['self']` allowing only
19121
+ * same origin resource requests.
19122
+ *
19123
+ * <div class="alert alert-warning">
19124
+ * **Note:** the default whitelist of 'self' is not recommended if your app shares its origin
19125
+ * with other apps! It is a good idea to limit it to only your application's directory.
19126
+ * </div>
19127
+ */
19128
+ this.resourceUrlWhitelist = function(value) {
19129
+ if (arguments.length) {
19130
+ resourceUrlWhitelist = adjustMatchers(value);
19131
+ }
19132
+ return resourceUrlWhitelist;
19133
+ };
19134
+
19135
+ /**
19136
+ * @ngdoc method
19137
+ * @name $sceDelegateProvider#resourceUrlBlacklist
19138
+ * @kind function
19139
+ *
19140
+ * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
19141
+ * provided. This must be an array or null. A snapshot of this array is used so further
19142
+ * changes to the array are ignored.</p><p>
19143
+ * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
19144
+ * allowed in this array.</p><p>
19145
+ * The typical usage for the blacklist is to **block
19146
+ * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
19147
+ * these would otherwise be trusted but actually return content from the redirected domain.
19148
+ * </p><p>
19149
+ * Finally, **the blacklist overrides the whitelist** and has the final say.
19150
+ *
19151
+ * @return {Array} The currently set blacklist array.
19152
+ *
19153
+ * @description
19154
+ * Sets/Gets the blacklist of trusted resource URLs.
19155
+ *
19156
+ * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
19157
+ * is no blacklist.)
19158
+ */
19159
+
19160
+ this.resourceUrlBlacklist = function(value) {
19161
+ if (arguments.length) {
19162
+ resourceUrlBlacklist = adjustMatchers(value);
19163
+ }
19164
+ return resourceUrlBlacklist;
19165
+ };
19166
+
19167
+ this.$get = ['$injector', function($injector) {
19168
+
19169
+ var htmlSanitizer = function htmlSanitizer(html) {
19170
+ throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
19171
+ };
19172
+
19173
+ if ($injector.has('$sanitize')) {
19174
+ htmlSanitizer = $injector.get('$sanitize');
19175
+ }
19176
+
19177
+
19178
+ function matchUrl(matcher, parsedUrl) {
19179
+ if (matcher === 'self') {
19180
+ return urlIsSameOrigin(parsedUrl);
19181
+ } else {
19182
+ // definitely a regex. See adjustMatchers()
19183
+ return !!matcher.exec(parsedUrl.href);
19184
+ }
19185
+ }
19186
+
19187
+ function isResourceUrlAllowedByPolicy(url) {
19188
+ var parsedUrl = urlResolve(url.toString());
19189
+ var i, n, allowed = false;
19190
+ // Ensure that at least one item from the whitelist allows this url.
19191
+ for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) {
19192
+ if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) {
19193
+ allowed = true;
19194
+ break;
19195
+ }
19196
+ }
19197
+ if (allowed) {
19198
+ // Ensure that no item from the blacklist blocked this url.
19199
+ for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) {
19200
+ if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) {
19201
+ allowed = false;
19202
+ break;
19203
+ }
19204
+ }
19205
+ }
19206
+ return allowed;
19207
+ }
19208
+
19209
+ function generateHolderType(Base) {
19210
+ var holderType = function TrustedValueHolderType(trustedValue) {
19211
+ this.$$unwrapTrustedValue = function() {
19212
+ return trustedValue;
19213
+ };
19214
+ };
19215
+ if (Base) {
19216
+ holderType.prototype = new Base();
19217
+ }
19218
+ holderType.prototype.valueOf = function sceValueOf() {
19219
+ return this.$$unwrapTrustedValue();
19220
+ };
19221
+ holderType.prototype.toString = function sceToString() {
19222
+ return this.$$unwrapTrustedValue().toString();
19223
+ };
19224
+ return holderType;
19225
+ }
19226
+
19227
+ var trustedValueHolderBase = generateHolderType(),
19228
+ byType = {};
19229
+
19230
+ byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
19231
+ byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
19232
+ byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
19233
+ byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
19234
+ byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
19235
+
19236
+ /**
19237
+ * @ngdoc method
19238
+ * @name $sceDelegate#trustAs
19239
+ *
19240
+ * @description
19241
+ * Returns a trusted representation of the parameter for the specified context. This trusted
19242
+ * object will later on be used as-is, without any security check, by bindings or directives
19243
+ * that require this security context.
19244
+ * For instance, marking a string as trusted for the `$sce.HTML` context will entirely bypass
19245
+ * the potential `$sanitize` call in corresponding `$sce.HTML` bindings or directives, such as
19246
+ * `ng-bind-html`. Note that in most cases you won't need to call this function: if you have the
19247
+ * sanitizer loaded, passing the value itself will render all the HTML that does not pose a
19248
+ * security risk.
19249
+ *
19250
+ * See {@link ng.$sceDelegate#getTrusted getTrusted} for the function that will consume those
19251
+ * trusted values, and {@link ng.$sce $sce} for general documentation about strict contextual
19252
+ * escaping.
19253
+ *
19254
+ * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`,
19255
+ * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`.
19256
+ *
19257
+ * @param {*} value The value that should be considered trusted.
19258
+ * @return {*} A trusted representation of value, that can be used in the given context.
19259
+ */
19260
+ function trustAs(type, trustedValue) {
19261
+ var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
19262
+ if (!Constructor) {
19263
+ throw $sceMinErr('icontext',
19264
+ 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
19265
+ type, trustedValue);
19266
+ }
19267
+ if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
19268
+ return trustedValue;
19269
+ }
19270
+ // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
19271
+ // mutable objects, we ensure here that the value passed in is actually a string.
19272
+ if (typeof trustedValue !== 'string') {
19273
+ throw $sceMinErr('itype',
19274
+ 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
19275
+ type);
19276
+ }
19277
+ return new Constructor(trustedValue);
19278
+ }
19279
+
19280
+ /**
19281
+ * @ngdoc method
19282
+ * @name $sceDelegate#valueOf
19283
+ *
19284
+ * @description
19285
+ * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
19286
+ * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
19287
+ * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
19288
+ *
19289
+ * If the passed parameter is not a value that had been returned by {@link
19290
+ * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, it must be returned as-is.
19291
+ *
19292
+ * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
19293
+ * call or anything else.
19294
+ * @return {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
19295
+ * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
19296
+ * `value` unchanged.
19297
+ */
19298
+ function valueOf(maybeTrusted) {
19299
+ if (maybeTrusted instanceof trustedValueHolderBase) {
19300
+ return maybeTrusted.$$unwrapTrustedValue();
19301
+ } else {
19302
+ return maybeTrusted;
19303
+ }
19304
+ }
19305
+
19306
+ /**
19307
+ * @ngdoc method
19308
+ * @name $sceDelegate#getTrusted
19309
+ *
19310
+ * @description
19311
+ * Takes any input, and either returns a value that's safe to use in the specified context, or
19312
+ * throws an exception.
19313
+ *
19314
+ * In practice, there are several cases. When given a string, this function runs checks
19315
+ * and sanitization to make it safe without prior assumptions. When given the result of a {@link
19316
+ * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call, it returns the originally supplied
19317
+ * value if that value's context is valid for this call's context. Finally, this function can
19318
+ * also throw when there is no way to turn `maybeTrusted` in a safe value (e.g., no sanitization
19319
+ * is available or possible.)
19320
+ *
19321
+ * @param {string} type The context in which this value is to be used (such as `$sce.HTML`).
19322
+ * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
19323
+ * `$sceDelegate.trustAs`} call, or anything else (which will not be considered trusted.)
19324
+ * @return {*} A version of the value that's safe to use in the given context, or throws an
19325
+ * exception if this is impossible.
19326
+ */
19327
+ function getTrusted(type, maybeTrusted) {
19328
+ if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
19329
+ return maybeTrusted;
19330
+ }
19331
+ var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
19332
+ // If maybeTrusted is a trusted class instance or subclass instance, then unwrap and return
19333
+ // as-is.
19334
+ if (constructor && maybeTrusted instanceof constructor) {
19335
+ return maybeTrusted.$$unwrapTrustedValue();
19336
+ }
19337
+ // Otherwise, if we get here, then we may either make it safe, or throw an exception. This
19338
+ // depends on the context: some are sanitizatible (HTML), some use whitelists (RESOURCE_URL),
19339
+ // some are impossible to do (JS). This step isn't implemented for CSS and URL, as AngularJS
19340
+ // has no corresponding sinks.
19341
+ if (type === SCE_CONTEXTS.RESOURCE_URL) {
19342
+ // RESOURCE_URL uses a whitelist.
19343
+ if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
19344
+ return maybeTrusted;
19345
+ } else {
19346
+ throw $sceMinErr('insecurl',
19347
+ 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
19348
+ maybeTrusted.toString());
19349
+ }
19350
+ } else if (type === SCE_CONTEXTS.HTML) {
19351
+ // htmlSanitizer throws its own error when no sanitizer is available.
19352
+ return htmlSanitizer(maybeTrusted);
19353
+ }
19354
+ // Default error when the $sce service has no way to make the input safe.
19355
+ throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
19356
+ }
19357
+
19358
+ return { trustAs: trustAs,
19359
+ getTrusted: getTrusted,
19360
+ valueOf: valueOf };
19361
+ }];
19362
+ }
19363
+
19364
+
19365
+ /**
19366
+ * @ngdoc provider
19367
+ * @name $sceProvider
19368
+ * @this
19369
+ *
19370
+ * @description
19371
+ *
19372
+ * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
19373
+ * - enable/disable Strict Contextual Escaping (SCE) in a module
19374
+ * - override the default implementation with a custom delegate
19375
+ *
19376
+ * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
19377
+ */
19378
+
19379
+ /**
19380
+ * @ngdoc service
19381
+ * @name $sce
19382
+ * @kind function
19383
+ *
19384
+ * @description
19385
+ *
19386
+ * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
19387
+ *
19388
+ * # Strict Contextual Escaping
19389
+ *
19390
+ * Strict Contextual Escaping (SCE) is a mode in which AngularJS constrains bindings to only render
19391
+ * trusted values. Its goal is to assist in writing code in a way that (a) is secure by default, and
19392
+ * (b) makes auditing for security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
19393
+ *
19394
+ * ## Overview
19395
+ *
19396
+ * To systematically block XSS security bugs, AngularJS treats all values as untrusted by default in
19397
+ * HTML or sensitive URL bindings. When binding untrusted values, AngularJS will automatically
19398
+ * run security checks on them (sanitizations, whitelists, depending on context), or throw when it
19399
+ * cannot guarantee the security of the result. That behavior depends strongly on contexts: HTML
19400
+ * can be sanitized, but template URLs cannot, for instance.
19401
+ *
19402
+ * To illustrate this, consider the `ng-bind-html` directive. It renders its value directly as HTML:
19403
+ * we call that the *context*. When given an untrusted input, AngularJS will attempt to sanitize it
19404
+ * before rendering if a sanitizer is available, and throw otherwise. To bypass sanitization and
19405
+ * render the input as-is, you will need to mark it as trusted for that context before attempting
19406
+ * to bind it.
19407
+ *
19408
+ * As of version 1.2, AngularJS ships with SCE enabled by default.
19409
+ *
19410
+ * ## In practice
19411
+ *
19412
+ * Here's an example of a binding in a privileged context:
19413
+ *
19414
+ * ```
19415
+ * <input ng-model="userHtml" aria-label="User input">
19416
+ * <div ng-bind-html="userHtml"></div>
19417
+ * ```
19418
+ *
19419
+ * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
19420
+ * disabled, this application allows the user to render arbitrary HTML into the DIV, which would
19421
+ * be an XSS security bug. In a more realistic example, one may be rendering user comments, blog
19422
+ * articles, etc. via bindings. (HTML is just one example of a context where rendering user
19423
+ * controlled input creates security vulnerabilities.)
19424
+ *
19425
+ * For the case of HTML, you might use a library, either on the client side, or on the server side,
19426
+ * to sanitize unsafe HTML before binding to the value and rendering it in the document.
19427
+ *
19428
+ * How would you ensure that every place that used these types of bindings was bound to a value that
19429
+ * was sanitized by your library (or returned as safe for rendering by your server?) How can you
19430
+ * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
19431
+ * properties/fields and forgot to update the binding to the sanitized value?
19432
+ *
19433
+ * To be secure by default, AngularJS makes sure bindings go through that sanitization, or
19434
+ * any similar validation process, unless there's a good reason to trust the given value in this
19435
+ * context. That trust is formalized with a function call. This means that as a developer, you
19436
+ * can assume all untrusted bindings are safe. Then, to audit your code for binding security issues,
19437
+ * you just need to ensure the values you mark as trusted indeed are safe - because they were
19438
+ * received from your server, sanitized by your library, etc. You can organize your codebase to
19439
+ * help with this - perhaps allowing only the files in a specific directory to do this.
19440
+ * Ensuring that the internal API exposed by that code doesn't markup arbitrary values as safe then
19441
+ * becomes a more manageable task.
19442
+ *
19443
+ * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
19444
+ * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
19445
+ * build the trusted versions of your values.
19446
+ *
19447
+ * ## How does it work?
19448
+ *
19449
+ * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
19450
+ * $sce.getTrusted(context, value)} rather than to the value directly. Think of this function as
19451
+ * a way to enforce the required security context in your data sink. Directives use {@link
19452
+ * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs
19453
+ * the {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. Also,
19454
+ * when binding without directives, AngularJS will understand the context of your bindings
19455
+ * automatically.
19456
+ *
19457
+ * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
19458
+ * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
19459
+ * simplified):
19460
+ *
19461
+ * ```
19462
+ * var ngBindHtmlDirective = ['$sce', function($sce) {
19463
+ * return function(scope, element, attr) {
19464
+ * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
19465
+ * element.html(value || '');
19466
+ * });
19467
+ * };
19468
+ * }];
19469
+ * ```
19470
+ *
19471
+ * ## Impact on loading templates
19472
+ *
19473
+ * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
19474
+ * `templateUrl`'s specified by {@link guide/directive directives}.
19475
+ *
19476
+ * By default, Angular only loads templates from the same domain and protocol as the application
19477
+ * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
19478
+ * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
19479
+ * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
19480
+ * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
19481
+ *
19482
+ * *Please note*:
19483
+ * The browser's
19484
+ * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
19485
+ * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
19486
+ * policy apply in addition to this and may further restrict whether the template is successfully
19487
+ * loaded. This means that without the right CORS policy, loading templates from a different domain
19488
+ * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
19489
+ * browsers.
19490
+ *
19491
+ * ## This feels like too much overhead
19492
+ *
19493
+ * It's important to remember that SCE only applies to interpolation expressions.
19494
+ *
19495
+ * If your expressions are constant literals, they're automatically trusted and you don't need to
19496
+ * call `$sce.trustAs` on them (e.g.
19497
+ * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. The `$sceDelegate` will
19498
+ * also use the `$sanitize` service if it is available when binding untrusted values to
19499
+ * `$sce.HTML` context. AngularJS provides an implementation in `angular-sanitize.js`, and if you
19500
+ * wish to use it, you will also need to depend on the {@link ngSanitize `ngSanitize`} module in
19501
+ * your application.
19502
+ *
19503
+ * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
19504
+ * templates in `ng-include` from your application's domain without having to even know about SCE.
19505
+ * It blocks loading templates from other domains or loading templates over http from an https
19506
+ * served document. You can change these by setting your own custom {@link
19507
+ * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
19508
+ * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
19509
+ *
19510
+ * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
19511
+ * application that's secure and can be audited to verify that with much more ease than bolting
19512
+ * security onto an application later.
19513
+ *
19514
+ * <a name="contexts"></a>
19515
+ * ## What trusted context types are supported?
19516
+ *
19517
+ * | Context | Notes |
19518
+ * |---------------------|----------------|
19519
+ * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered, and the {@link ngSanitize.$sanitize $sanitize} service is available (implemented by the {@link ngSanitize ngSanitize} module) this will sanitize the value instead of throwing an error. |
19520
+ * | `$sce.CSS` | For CSS that's safe to source into the application. Currently, no bindings require this context. Feel free to use it in your own directives. |
19521
+ * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=`, `<img src=`, and some others sanitize their urls and don't constitute an SCE context.) |
19522
+ * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG`, `VIDEO`, `AUDIO`, `SOURCE`, and `TRACK` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does (it's not just the URL that matters, but also what is at the end of it), and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. |
19523
+ * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently, no bindings require this context. Feel free to use it in your own directives. |
19524
+ *
19525
+ *
19526
+ * Be aware that `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
19527
+ * through {@link ng.$sce#getTrusted $sce.getTrusted}. There's no CSS-, URL-, or JS-context bindings
19528
+ * in AngularJS currently, so their corresponding `$sce.trustAs` functions aren't useful yet. This
19529
+ * might evolve.
19530
+ *
19531
+ * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
19532
+ *
19533
+ * Each element in these arrays must be one of the following:
19534
+ *
19535
+ * - **'self'**
19536
+ * - The special **string**, `'self'`, can be used to match against all URLs of the **same
19537
+ * domain** as the application document using the **same protocol**.
19538
+ * - **String** (except the special value `'self'`)
19539
+ * - The string is matched against the full *normalized / absolute URL* of the resource
19540
+ * being tested (substring matches are not good enough.)
19541
+ * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
19542
+ * match themselves.
19543
+ * - `*`: matches zero or more occurrences of any character other than one of the following 6
19544
+ * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
19545
+ * in a whitelist.
19546
+ * - `**`: matches zero or more occurrences of *any* character. As such, it's not
19547
+ * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
19548
+ * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
19549
+ * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
19550
+ * http://foo.example.com/templates/**).
19551
+ * - **RegExp** (*see caveat below*)
19552
+ * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
19553
+ * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
19554
+ * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
19555
+ * have good test coverage). For instance, the use of `.` in the regex is correct only in a
19556
+ * small number of cases. A `.` character in the regex used when matching the scheme or a
19557
+ * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
19558
+ * is highly recommended to use the string patterns and only fall back to regular expressions
19559
+ * as a last resort.
19560
+ * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
19561
+ * matched against the **entire** *normalized / absolute URL* of the resource being tested
19562
+ * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
19563
+ * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
19564
+ * - If you are generating your JavaScript from some other templating engine (not
19565
+ * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
19566
+ * remember to escape your regular expression (and be aware that you might need more than
19567
+ * one level of escaping depending on your templating engine and the way you interpolated
19568
+ * the value.) Do make use of your platform's escaping mechanism as it might be good
19569
+ * enough before coding your own. E.g. Ruby has
19570
+ * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
19571
+ * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
19572
+ * Javascript lacks a similar built in function for escaping. Take a look at Google
19573
+ * Closure library's [goog.string.regExpEscape(s)](
19574
+ * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
19575
+ *
19576
+ * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
19577
+ *
19578
+ * ## Show me an example using SCE.
19579
+ *
19580
+ * <example module="mySceApp" deps="angular-sanitize.js" name="sce-service">
19581
+ * <file name="index.html">
19582
+ * <div ng-controller="AppController as myCtrl">
19583
+ * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
19584
+ * <b>User comments</b><br>
19585
+ * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
19586
+ * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
19587
+ * exploit.
19588
+ * <div class="well">
19589
+ * <div ng-repeat="userComment in myCtrl.userComments">
19590
+ * <b>{{userComment.name}}</b>:
19591
+ * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
19592
+ * <br>
19593
+ * </div>
19594
+ * </div>
19595
+ * </div>
19596
+ * </file>
19597
+ *
19598
+ * <file name="script.js">
19599
+ * angular.module('mySceApp', ['ngSanitize'])
19600
+ * .controller('AppController', ['$http', '$templateCache', '$sce',
19601
+ * function AppController($http, $templateCache, $sce) {
19602
+ * var self = this;
19603
+ * $http.get('test_data.json', {cache: $templateCache}).then(function(response) {
19604
+ * self.userComments = response.data;
19605
+ * });
19606
+ * self.explicitlyTrustedHtml = $sce.trustAsHtml(
19607
+ * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
19608
+ * 'sanitization.&quot;">Hover over this text.</span>');
19609
+ * }]);
19610
+ * </file>
19611
+ *
19612
+ * <file name="test_data.json">
19613
+ * [
19614
+ * { "name": "Alice",
19615
+ * "htmlComment":
19616
+ * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
19617
+ * },
19618
+ * { "name": "Bob",
19619
+ * "htmlComment": "<i>Yes!</i> Am I the only other one?"
19620
+ * }
19621
+ * ]
19622
+ * </file>
19623
+ *
19624
+ * <file name="protractor.js" type="protractor">
19625
+ * describe('SCE doc demo', function() {
19626
+ * it('should sanitize untrusted values', function() {
19627
+ * expect(element.all(by.css('.htmlComment')).first().getAttribute('innerHTML'))
19628
+ * .toBe('<span>Is <i>anyone</i> reading this?</span>');
19629
+ * });
19630
+ *
19631
+ * it('should NOT sanitize explicitly trusted values', function() {
19632
+ * expect(element(by.id('explicitlyTrustedHtml')).getAttribute('innerHTML')).toBe(
19633
+ * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
19634
+ * 'sanitization.&quot;">Hover over this text.</span>');
19635
+ * });
19636
+ * });
19637
+ * </file>
19638
+ * </example>
19639
+ *
19640
+ *
19641
+ *
19642
+ * ## Can I disable SCE completely?
19643
+ *
19644
+ * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
19645
+ * for little coding overhead. It will be much harder to take an SCE disabled application and
19646
+ * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
19647
+ * for cases where you have a lot of existing code that was written before SCE was introduced and
19648
+ * you're migrating them a module at a time. Also do note that this is an app-wide setting, so if
19649
+ * you are writing a library, you will cause security bugs applications using it.
19650
+ *
19651
+ * That said, here's how you can completely disable SCE:
19652
+ *
19653
+ * ```
19654
+ * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
19655
+ * // Completely disable SCE. For demonstration purposes only!
19656
+ * // Do not use in new projects or libraries.
19657
+ * $sceProvider.enabled(false);
19658
+ * });
19659
+ * ```
19660
+ *
19661
+ */
19662
+
19663
+ function $SceProvider() {
19664
+ var enabled = true;
19665
+
19666
+ /**
19667
+ * @ngdoc method
19668
+ * @name $sceProvider#enabled
19669
+ * @kind function
19670
+ *
19671
+ * @param {boolean=} value If provided, then enables/disables SCE application-wide.
19672
+ * @return {boolean} True if SCE is enabled, false otherwise.
19673
+ *
19674
+ * @description
19675
+ * Enables/disables SCE and returns the current value.
19676
+ */
19677
+ this.enabled = function(value) {
19678
+ if (arguments.length) {
19679
+ enabled = !!value;
19680
+ }
19681
+ return enabled;
19682
+ };
19683
+
19684
+
19685
+ /* Design notes on the default implementation for SCE.
19686
+ *
19687
+ * The API contract for the SCE delegate
19688
+ * -------------------------------------
19689
+ * The SCE delegate object must provide the following 3 methods:
19690
+ *
19691
+ * - trustAs(contextEnum, value)
19692
+ * This method is used to tell the SCE service that the provided value is OK to use in the
19693
+ * contexts specified by contextEnum. It must return an object that will be accepted by
19694
+ * getTrusted() for a compatible contextEnum and return this value.
19695
+ *
19696
+ * - valueOf(value)
19697
+ * For values that were not produced by trustAs(), return them as is. For values that were
19698
+ * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
19699
+ * trustAs is wrapping the given values into some type, this operation unwraps it when given
19700
+ * such a value.
19701
+ *
19702
+ * - getTrusted(contextEnum, value)
19703
+ * This function should return the a value that is safe to use in the context specified by
19704
+ * contextEnum or throw and exception otherwise.
19705
+ *
19706
+ * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
19707
+ * opaque or wrapped in some holder object. That happens to be an implementation detail. For
19708
+ * instance, an implementation could maintain a registry of all trusted objects by context. In
19709
+ * such a case, trustAs() would return the same object that was passed in. getTrusted() would
19710
+ * return the same object passed in if it was found in the registry under a compatible context or
19711
+ * throw an exception otherwise. An implementation might only wrap values some of the time based
19712
+ * on some criteria. getTrusted() might return a value and not throw an exception for special
19713
+ * constants or objects even if not wrapped. All such implementations fulfill this contract.
19714
+ *
19715
+ *
19716
+ * A note on the inheritance model for SCE contexts
19717
+ * ------------------------------------------------
19718
+ * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
19719
+ * is purely an implementation details.
19720
+ *
19721
+ * The contract is simply this:
19722
+ *
19723
+ * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
19724
+ * will also succeed.
19725
+ *
19726
+ * Inheritance happens to capture this in a natural way. In some future, we may not use
19727
+ * inheritance anymore. That is OK because no code outside of sce.js and sceSpecs.js would need to
19728
+ * be aware of this detail.
19729
+ */
19730
+
19731
+ this.$get = ['$parse', '$sceDelegate', function(
19732
+ $parse, $sceDelegate) {
19733
+ // Support: IE 9-11 only
19734
+ // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
19735
+ // the "expression(javascript expression)" syntax which is insecure.
19736
+ if (enabled && msie < 8) {
19737
+ throw $sceMinErr('iequirks',
19738
+ 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
19739
+ 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
19740
+ 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
19741
+ }
19742
+
19743
+ var sce = shallowCopy(SCE_CONTEXTS);
19744
+
19745
+ /**
19746
+ * @ngdoc method
19747
+ * @name $sce#isEnabled
19748
+ * @kind function
19749
+ *
19750
+ * @return {Boolean} True if SCE is enabled, false otherwise. If you want to set the value, you
19751
+ * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
19752
+ *
19753
+ * @description
19754
+ * Returns a boolean indicating if SCE is enabled.
19755
+ */
19756
+ sce.isEnabled = function() {
19757
+ return enabled;
19758
+ };
19759
+ sce.trustAs = $sceDelegate.trustAs;
19760
+ sce.getTrusted = $sceDelegate.getTrusted;
19761
+ sce.valueOf = $sceDelegate.valueOf;
19762
+
19763
+ if (!enabled) {
19764
+ sce.trustAs = sce.getTrusted = function(type, value) { return value; };
19765
+ sce.valueOf = identity;
19766
+ }
19767
+
19768
+ /**
19769
+ * @ngdoc method
19770
+ * @name $sce#parseAs
19771
+ *
19772
+ * @description
19773
+ * Converts Angular {@link guide/expression expression} into a function. This is like {@link
19774
+ * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
19775
+ * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
19776
+ * *result*)}
19777
+ *
19778
+ * @param {string} type The SCE context in which this result will be used.
19779
+ * @param {string} expression String expression to compile.
19780
+ * @return {function(context, locals)} A function which represents the compiled expression:
19781
+ *
19782
+ * * `context` – `{object}` – an object against which any expressions embedded in the
19783
+ * strings are evaluated against (typically a scope object).
19784
+ * * `locals` – `{object=}` – local variables context object, useful for overriding values
19785
+ * in `context`.
19786
+ */
19787
+ sce.parseAs = function sceParseAs(type, expr) {
19788
+ var parsed = $parse(expr);
19789
+ if (parsed.literal && parsed.constant) {
19790
+ return parsed;
19791
+ } else {
19792
+ return $parse(expr, function(value) {
19793
+ return sce.getTrusted(type, value);
19794
+ });
19795
+ }
19796
+ };
19797
+
19798
+ /**
19799
+ * @ngdoc method
19800
+ * @name $sce#trustAs
19801
+ *
19802
+ * @description
19803
+ * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, returns a
19804
+ * wrapped object that represents your value, and the trust you have in its safety for the given
19805
+ * context. AngularJS can then use that value as-is in bindings of the specified secure context.
19806
+ * This is used in bindings for `ng-bind-html`, `ng-include`, and most `src` attribute
19807
+ * interpolations. See {@link ng.$sce $sce} for strict contextual escaping.
19808
+ *
19809
+ * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`,
19810
+ * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`.
19811
+ *
19812
+ * @param {*} value The value that that should be considered trusted.
19813
+ * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19814
+ * in the context you specified.
19815
+ */
19816
+
19817
+ /**
19818
+ * @ngdoc method
19819
+ * @name $sce#trustAsHtml
19820
+ *
19821
+ * @description
19822
+ * Shorthand method. `$sce.trustAsHtml(value)` →
19823
+ * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
19824
+ *
19825
+ * @param {*} value The value to mark as trusted for `$sce.HTML` context.
19826
+ * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19827
+ * in `$sce.HTML` context (like `ng-bind-html`).
19828
+ */
19829
+
19830
+ /**
19831
+ * @ngdoc method
19832
+ * @name $sce#trustAsCss
19833
+ *
19834
+ * @description
19835
+ * Shorthand method. `$sce.trustAsCss(value)` →
19836
+ * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.CSS, value)`}
19837
+ *
19838
+ * @param {*} value The value to mark as trusted for `$sce.CSS` context.
19839
+ * @return {*} A wrapped version of value that can be used as a trusted variant
19840
+ * of your `value` in `$sce.CSS` context. This context is currently unused, so there are
19841
+ * almost no reasons to use this function so far.
19842
+ */
19843
+
19844
+ /**
19845
+ * @ngdoc method
19846
+ * @name $sce#trustAsUrl
19847
+ *
19848
+ * @description
19849
+ * Shorthand method. `$sce.trustAsUrl(value)` →
19850
+ * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
19851
+ *
19852
+ * @param {*} value The value to mark as trusted for `$sce.URL` context.
19853
+ * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19854
+ * in `$sce.URL` context. That context is currently unused, so there are almost no reasons
19855
+ * to use this function so far.
19856
+ */
19857
+
19858
+ /**
19859
+ * @ngdoc method
19860
+ * @name $sce#trustAsResourceUrl
19861
+ *
19862
+ * @description
19863
+ * Shorthand method. `$sce.trustAsResourceUrl(value)` →
19864
+ * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
19865
+ *
19866
+ * @param {*} value The value to mark as trusted for `$sce.RESOURCE_URL` context.
19867
+ * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19868
+ * in `$sce.RESOURCE_URL` context (template URLs in `ng-include`, most `src` attribute
19869
+ * bindings, ...)
19870
+ */
19871
+
19872
+ /**
19873
+ * @ngdoc method
19874
+ * @name $sce#trustAsJs
19875
+ *
19876
+ * @description
19877
+ * Shorthand method. `$sce.trustAsJs(value)` →
19878
+ * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
19879
+ *
19880
+ * @param {*} value The value to mark as trusted for `$sce.JS` context.
19881
+ * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19882
+ * in `$sce.JS` context. That context is currently unused, so there are almost no reasons to
19883
+ * use this function so far.
19884
+ */
19885
+
19886
+ /**
19887
+ * @ngdoc method
19888
+ * @name $sce#getTrusted
19889
+ *
19890
+ * @description
19891
+ * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
19892
+ * takes any input, and either returns a value that's safe to use in the specified context,
19893
+ * or throws an exception. This function is aware of trusted values created by the `trustAs`
19894
+ * function and its shorthands, and when contexts are appropriate, returns the unwrapped value
19895
+ * as-is. Finally, this function can also throw when there is no way to turn `maybeTrusted` in a
19896
+ * safe value (e.g., no sanitization is available or possible.)
19897
+ *
19898
+ * @param {string} type The context in which this value is to be used.
19899
+ * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs
19900
+ * `$sce.trustAs`} call, or anything else (which will not be considered trusted.)
19901
+ * @return {*} A version of the value that's safe to use in the given context, or throws an
19902
+ * exception if this is impossible.
19903
+ */
19904
+
19905
+ /**
19906
+ * @ngdoc method
19907
+ * @name $sce#getTrustedHtml
19908
+ *
19909
+ * @description
19910
+ * Shorthand method. `$sce.getTrustedHtml(value)` →
19911
+ * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
19912
+ *
19913
+ * @param {*} value The value to pass to `$sce.getTrusted`.
19914
+ * @return {*} The return value of `$sce.getTrusted($sce.HTML, value)`
19915
+ */
19916
+
19917
+ /**
19918
+ * @ngdoc method
19919
+ * @name $sce#getTrustedCss
19920
+ *
19921
+ * @description
19922
+ * Shorthand method. `$sce.getTrustedCss(value)` →
19923
+ * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
19924
+ *
19925
+ * @param {*} value The value to pass to `$sce.getTrusted`.
19926
+ * @return {*} The return value of `$sce.getTrusted($sce.CSS, value)`
19927
+ */
19928
+
19929
+ /**
19930
+ * @ngdoc method
19931
+ * @name $sce#getTrustedUrl
19932
+ *
19933
+ * @description
19934
+ * Shorthand method. `$sce.getTrustedUrl(value)` →
19935
+ * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
19936
+ *
19937
+ * @param {*} value The value to pass to `$sce.getTrusted`.
19938
+ * @return {*} The return value of `$sce.getTrusted($sce.URL, value)`
19939
+ */
19940
+
19941
+ /**
19942
+ * @ngdoc method
19943
+ * @name $sce#getTrustedResourceUrl
19944
+ *
19945
+ * @description
19946
+ * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
19947
+ * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
19948
+ *
19949
+ * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
19950
+ * @return {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
19951
+ */
19952
+
19953
+ /**
19954
+ * @ngdoc method
19955
+ * @name $sce#getTrustedJs
19956
+ *
19957
+ * @description
19958
+ * Shorthand method. `$sce.getTrustedJs(value)` →
19959
+ * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
19960
+ *
19961
+ * @param {*} value The value to pass to `$sce.getTrusted`.
19962
+ * @return {*} The return value of `$sce.getTrusted($sce.JS, value)`
19963
+ */
19964
+
19965
+ /**
19966
+ * @ngdoc method
19967
+ * @name $sce#parseAsHtml
19968
+ *
19969
+ * @description
19970
+ * Shorthand method. `$sce.parseAsHtml(expression string)` →
19971
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
19972
+ *
19973
+ * @param {string} expression String expression to compile.
19974
+ * @return {function(context, locals)} A function which represents the compiled expression:
19975
+ *
19976
+ * * `context` – `{object}` – an object against which any expressions embedded in the
19977
+ * strings are evaluated against (typically a scope object).
19978
+ * * `locals` – `{object=}` – local variables context object, useful for overriding values
19979
+ * in `context`.
19980
+ */
19981
+
19982
+ /**
19983
+ * @ngdoc method
19984
+ * @name $sce#parseAsCss
19985
+ *
19986
+ * @description
19987
+ * Shorthand method. `$sce.parseAsCss(value)` →
19988
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
19989
+ *
19990
+ * @param {string} expression String expression to compile.
19991
+ * @return {function(context, locals)} A function which represents the compiled expression:
19992
+ *
19993
+ * * `context` – `{object}` – an object against which any expressions embedded in the
19994
+ * strings are evaluated against (typically a scope object).
19995
+ * * `locals` – `{object=}` – local variables context object, useful for overriding values
19996
+ * in `context`.
19997
+ */
19998
+
19999
+ /**
20000
+ * @ngdoc method
20001
+ * @name $sce#parseAsUrl
20002
+ *
20003
+ * @description
20004
+ * Shorthand method. `$sce.parseAsUrl(value)` →
20005
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
20006
+ *
20007
+ * @param {string} expression String expression to compile.
20008
+ * @return {function(context, locals)} A function which represents the compiled expression:
20009
+ *
20010
+ * * `context` – `{object}` – an object against which any expressions embedded in the
20011
+ * strings are evaluated against (typically a scope object).
20012
+ * * `locals` – `{object=}` – local variables context object, useful for overriding values
20013
+ * in `context`.
20014
+ */
20015
+
20016
+ /**
20017
+ * @ngdoc method
20018
+ * @name $sce#parseAsResourceUrl
20019
+ *
20020
+ * @description
20021
+ * Shorthand method. `$sce.parseAsResourceUrl(value)` →
20022
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
20023
+ *
20024
+ * @param {string} expression String expression to compile.
20025
+ * @return {function(context, locals)} A function which represents the compiled expression:
20026
+ *
20027
+ * * `context` – `{object}` – an object against which any expressions embedded in the
20028
+ * strings are evaluated against (typically a scope object).
20029
+ * * `locals` – `{object=}` – local variables context object, useful for overriding values
20030
+ * in `context`.
20031
+ */
20032
+
20033
+ /**
20034
+ * @ngdoc method
20035
+ * @name $sce#parseAsJs
20036
+ *
20037
+ * @description
20038
+ * Shorthand method. `$sce.parseAsJs(value)` →
20039
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
20040
+ *
20041
+ * @param {string} expression String expression to compile.
20042
+ * @return {function(context, locals)} A function which represents the compiled expression:
20043
+ *