Ad Inserter – WordPress Ads Management with AdSense Header Integration - Version 2.3.1

Version Description

  • Added support for server-side insertion before/after any HTML element
  • Few minor bug fixes
Download this release

Release Info

Developer spacetime
Plugin Icon 128x128 Ad Inserter – WordPress Ads Management with AdSense Header Integration
Version 2.3.1
Comparing to
See all releases

Code changes from version 2.3.0 to 2.3.1

ad-inserter.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  /*
4
  Plugin Name: Ad Inserter
5
- Version: 2.3.0
6
  Description: Ad management plugin with advanced advertising options to automatically insert ad codes on your website
7
  Author: Igor Funa
8
  Author URI: http://igorfuna.com/
@@ -13,7 +13,11 @@ Plugin URI: http://adinserter.pro/documentation
13
 
14
  Change Log
15
 
16
- Ad Inserter 2.3.0 - 2018-01-20
 
 
 
 
17
  - Added support for client-side insertion before/after any HTML element
18
  - Inplemented AdSense integration
19
  - Added option to define close button position
@@ -600,13 +604,19 @@ ai_load_settings ();
600
  $ai_wp_data [AI_BACKEND_JS_DEBUGGING] = get_backend_javascript_debugging ();
601
  $ai_wp_data [AI_FRONTEND_JS_DEBUGGING] = get_frontend_javascript_debugging ();
602
 
603
- if (isset ($_GET [AI_URL_DEBUG_PHP]) && $_GET [AI_URL_DEBUG_PHP] != '') {
604
  if (get_remote_debugging ()) {
605
  ini_set ('display_errors', 1);
606
  error_reporting (E_ALL);
607
  }
608
  }
609
 
 
 
 
 
 
 
610
  if (defined ('AI_ADBLOCKING_DETECTION') && AI_ADBLOCKING_DETECTION) {
611
  $ai_wp_data [AI_ADB_DETECTION] = $block_object [AI_ADB_MESSAGE_OPTION_NAME]->get_enable_manual ();
612
 
@@ -1091,7 +1101,7 @@ function ai_buffering_start () {
1091
  global $ai_wp_data;
1092
 
1093
  if (($ai_wp_data [AI_WP_DEBUGGING] & AI_DEBUG_PROCESSING) != 0) {
1094
- ai_log ("BUFFERING START");
1095
  }
1096
 
1097
  ob_start ();
@@ -1099,28 +1109,124 @@ function ai_buffering_start () {
1099
  }
1100
 
1101
  function ai_buffering_end () {
1102
- global $ai_wp_data, $ai_total_plugin_time;
1103
 
1104
  if (!defined ('AI_BUFFERING_START')) return;
1105
 
 
 
1106
  if (($ai_wp_data [AI_WP_DEBUGGING] & AI_DEBUG_PROCESSING) != 0) {
1107
- ai_log ("BUFFERING END");
1108
  $start_time = microtime (true);
1109
  }
1110
 
1111
- $page = ob_get_clean();
1112
-
1113
-
1114
  // TEST
1115
  // $page = ai_content_hook ($page);
1116
 
1117
  $matches = preg_split ('/(<body.*?'.'>)/i', $page, - 1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
1118
- $after_body = $matches [2];
1119
 
1120
- echo $matches [0], $matches [1];
1121
- ai_custom_hook ('above_header', 'Above Header');
1122
- echo $after_body;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1123
 
 
 
 
1124
 
1125
  // echo ai_content_hook ($matches [2]);
1126
 
@@ -1519,11 +1625,17 @@ function ai_wp_enqueue_scripts_hook () {
1519
  $ai_wp_data [AI_STICKY_WIDGETS] ||
1520
  (defined ('AI_ADBLOCKING_DETECTION') && AI_ADBLOCKING_DETECTION && $ai_wp_data [AI_ADB_DETECTION]);
1521
 
1522
- if (($footer_inline_scripts || $ai_wp_data [AI_CLIENT_SIDE_INSERTION])) {
1523
  // force loading of jquery on frontend
1524
- wp_enqueue_script ('ai-dummy-js', plugins_url ('includes/js/dummy.js', __FILE__ ), array (
1525
  'jquery',
1526
  ));
 
 
 
 
 
 
1527
  }
1528
  }
1529
 
@@ -1538,18 +1650,6 @@ function ai_clean_url ( $url, $original_url){
1538
  function add_head_inline_styles_and_scripts () {
1539
  global $ai_wp_data;
1540
 
1541
- if ($ai_wp_data [AI_FRONTEND_JS_DEBUGGING]) {
1542
- echo "<script type='text/javascript'>\n";
1543
- echo " ai_debugging = true;";
1544
- echo "</script>\n";
1545
- }
1546
-
1547
- if ($ai_wp_data [AI_CLIENT_SIDE_INSERTION]) {
1548
- echo "<script type='text/javascript'>\n";
1549
- echo ai_get_js ('ai-insert', false);
1550
- echo "</script>\n";
1551
- }
1552
-
1553
  if ($ai_wp_data [AI_CLIENT_SIDE_DETECTION] ||
1554
  get_dynamic_blocks () == AI_DYNAMIC_BLOCKS_CLIENT_SIDE ||
1555
  get_admin_toolbar_debugging () && (get_remote_debugging () || ($ai_wp_data [AI_WP_USER] & AI_USER_LOGGED_IN) != 0) ||
@@ -2166,7 +2266,7 @@ function get_adb_status_debug_info () {
2166
 
2167
  if (defined ('AI_ADBLOCKING_DETECTION') && AI_ADBLOCKING_DETECTION) {
2168
  if ($ai_wp_data [AI_ADB_DETECTION]) {
2169
- $page_type = "<section id='ai-adb-bar' class='".AI_DEBUG_ADB_CLASS."' title='Click to delete ad blocking detection cokies'>AD BLOCKING <span id='ai-adb-status'>STATUS UNKNOWN</span></section>";
2170
  }
2171
  }
2172
 
@@ -2642,6 +2742,7 @@ function ai_write_debug_info ($write_processing_log = false) {
2642
  break;
2643
  }
2644
  echo "\n";
 
2645
  if (defined ('AI_BUFFERING')) {
2646
  echo 'OUTPUT BUFFERING: ';
2647
  switch (get_output_buffering()) {
@@ -2873,6 +2974,8 @@ function ai_write_debug_info ($write_processing_log = false) {
2873
  if (count ($ai_db_options_extract [$custom_hook ['action'] . CUSTOM_HOOK_BLOCKS][AI_PT_ANY]))
2874
  echo substr (strtoupper (str_replace (array ('&lt;', '&gt;'), array ('<', '>'), get_hook_name ($custom_hook ['index']))) . " HOOK: ", 0, 25), implode (", ", $ai_db_options_extract [$custom_hook ['action'] . CUSTOM_HOOK_BLOCKS][AI_PT_ANY]), "\n";
2875
  }
 
 
2876
 
2877
  echo "\nBLOCKS FOR THIS PAGE TYPE\n";
2878
  if (isset ($ai_db_options_extract [ABOVE_HEADER_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]) && count ($ai_db_options_extract [ABOVE_HEADER_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]))
@@ -2911,6 +3014,8 @@ function ai_write_debug_info ($write_processing_log = false) {
2911
  if (isset ($ai_db_options_extract [$custom_hook ['action'] . CUSTOM_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]) && count ($ai_db_options_extract [$custom_hook ['action'] . CUSTOM_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]))
2912
  echo substr (strtoupper (str_replace (array ('&lt;', '&gt;'), array ('<', '>'), get_hook_name ($custom_hook ['index']))) . " HOOK: ", 0, 25), implode (", ", $ai_db_options_extract [$custom_hook ['action'] . CUSTOM_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]), "\n";
2913
  }
 
 
2914
 
2915
  if ($write_processing_log) {
2916
  echo "\nTIME EVENT\n";
@@ -2937,6 +3042,7 @@ function ai_write_debug_info ($write_processing_log = false) {
2937
  echo "Display Errors: ", ini_get ('display_errors'), "\n";
2938
  echo "cURL: ", function_exists ('curl_version') ? 'ENABLED' : 'DISABLED', "\n";
2939
  echo "fsockopen: ", function_exists ('fsockopen') ? 'ENABLED' : 'DISABLED', "\n";
 
2940
  echo "\n\n";
2941
 
2942
  global $wp_version;
@@ -2953,8 +3059,14 @@ function ai_write_debug_info ($write_processing_log = false) {
2953
  }
2954
  $all_plugins = get_plugins();
2955
  $active_plugins = get_option ('active_plugins');
 
 
2956
  foreach ($all_plugins as $plugin_path => $plugin) {
2957
- echo in_array ($plugin_path, $active_plugins) ? '* ' : ' ', html_entity_decode ($plugin ["Name"]), ' ', $plugin ["Version"], "\n";
 
 
 
 
2958
  }
2959
  }
2960
 
@@ -3871,6 +3983,7 @@ function ai_generate_extract (&$settings) {
3871
 
3872
  if (defined ('AI_BUFFERING')) {
3873
  $above_header_hook_blocks = array (AI_PT_ANY => array (), AI_PT_HOMEPAGE => array(), AI_PT_CATEGORY => array(), AI_PT_SEARCH => array(), AI_PT_ARCHIVE => array(), AI_PT_STATIC => array(), AI_PT_POST => array(), AI_PT_404 => array(), AI_PT_FEED => array(), AI_PT_AJAX => array());
 
3874
  }
3875
 
3876
  $content_hook_blocks = array (AI_PT_ANY => array (), AI_PT_HOMEPAGE => array(), AI_PT_CATEGORY => array(), AI_PT_SEARCH => array(), AI_PT_ARCHIVE => array(), AI_PT_STATIC => array(), AI_PT_POST => array(), AI_PT_404 => array(), AI_PT_FEED => array(), AI_PT_AJAX => array());
@@ -3940,7 +4053,14 @@ function ai_generate_extract (&$settings) {
3940
  switch ($automatic_insertion) {
3941
  case AI_AUTOMATIC_INSERTION_BEFORE_HTML_ELEMENT:
3942
  case AI_AUTOMATIC_INSERTION_AFTER_HTML_ELEMENT:
3943
- $automatic_insertion = $obj->get_server_side_insertion ();
 
 
 
 
 
 
 
3944
  break;
3945
  }
3946
 
@@ -3951,6 +4071,12 @@ function ai_generate_extract (&$settings) {
3951
  $above_header_hook_blocks [AI_PT_ANY][]= $block;
3952
  }
3953
  break;
 
 
 
 
 
 
3954
  case AI_AUTOMATIC_INSERTION_BEFORE_PARAGRAPH:
3955
  case AI_AUTOMATIC_INSERTION_AFTER_PARAGRAPH:
3956
  case AI_AUTOMATIC_INSERTION_BEFORE_CONTENT:
@@ -4015,6 +4141,7 @@ function ai_generate_extract (&$settings) {
4015
 
4016
  if (defined ('AI_BUFFERING')) {
4017
  $extract [ABOVE_HEADER_HOOK_BLOCKS] = $above_header_hook_blocks;
 
4018
  }
4019
  $extract [CONTENT_HOOK_BLOCKS] = $content_hook_blocks;
4020
  $extract [EXCERPT_HOOK_BLOCKS] = $excerpt_hook_blocks;
@@ -4099,7 +4226,7 @@ function ai_load_settings () {
4099
  switch ($obj->get_automatic_insertion()) {
4100
  case AI_AUTOMATIC_INSERTION_BEFORE_HTML_ELEMENT:
4101
  case AI_AUTOMATIC_INSERTION_AFTER_HTML_ELEMENT:
4102
- $ai_wp_data [AI_CLIENT_SIDE_INSERTION] = true;
4103
  break;
4104
  }
4105
  }
@@ -4238,7 +4365,8 @@ function generate_debug_css () {
4238
  .ai-debug-tags {font-weight: bold; color: white; padding: 2px;}
4239
  .ai-debug-positions {clear: both; text-align: center; padding: 10px 0; font-weight: bold; border: 1px solid blue; color: blue; background: #eef;}
4240
  .ai-debug-page-type {text-align: center; padding: 10px 0; font-weight: bold; border: 1px solid green; color: green; background: #efe;}
4241
- .ai-debug-adb {text-align: center; padding: 10px 0; font-weight: bold; border: 1px solid red; color: red; background: #fee; opacity: 0.85; cursor: pointer;}
 
4242
  .ai-debug-widget {margin: 0; padding: 0 5px; font-size: 10px; white-space: pre; overflow-x: auto; overflow-y: hidden;}
4243
  a.ai-debug-left {float: left; font-size: 10px; text-decoration: none; color: transparent; padding: 0px 10px 0 0; box-shadow: none;}
4244
  a.ai-debug-right {float: right; font-size: 10px; text-decoration: none; color: #88f; padding: 0px 10px 0 0; box-shadow: none;}
2
 
3
  /*
4
  Plugin Name: Ad Inserter
5
+ Version: 2.3.1
6
  Description: Ad management plugin with advanced advertising options to automatically insert ad codes on your website
7
  Author: Igor Funa
8
  Author URI: http://igorfuna.com/
13
 
14
  Change Log
15
 
16
+ Ad Inserter 2.3.1 - 2018-01-25
17
+ - Added support for server-side insertion before/after any HTML element
18
+ - Few minor bug fixes
19
+
20
+ Ad Inserter 2.3.0 - 2018-01-21
21
  - Added support for client-side insertion before/after any HTML element
22
  - Inplemented AdSense integration
23
  - Added option to define close button position
604
  $ai_wp_data [AI_BACKEND_JS_DEBUGGING] = get_backend_javascript_debugging ();
605
  $ai_wp_data [AI_FRONTEND_JS_DEBUGGING] = get_frontend_javascript_debugging ();
606
 
607
+ if (isset ($_GET [AI_URL_DEBUG_PHP]) && $_GET [AI_URL_DEBUG_PHP] == '1') {
608
  if (get_remote_debugging ()) {
609
  ini_set ('display_errors', 1);
610
  error_reporting (E_ALL);
611
  }
612
  }
613
 
614
+ if (isset ($_GET [AI_URL_DEBUG_JAVASCRIPT]) && $_GET [AI_URL_DEBUG_JAVASCRIPT] == '1') {
615
+ if (get_remote_debugging ()) {
616
+ $ai_wp_data [AI_FRONTEND_JS_DEBUGGING] = true;
617
+ }
618
+ }
619
+
620
  if (defined ('AI_ADBLOCKING_DETECTION') && AI_ADBLOCKING_DETECTION) {
621
  $ai_wp_data [AI_ADB_DETECTION] = $block_object [AI_ADB_MESSAGE_OPTION_NAME]->get_enable_manual ();
622
 
1101
  global $ai_wp_data;
1102
 
1103
  if (($ai_wp_data [AI_WP_DEBUGGING] & AI_DEBUG_PROCESSING) != 0) {
1104
+ ai_log ("BUFFERING START: level " . ob_get_level () );
1105
  }
1106
 
1107
  ob_start ();
1109
  }
1110
 
1111
  function ai_buffering_end () {
1112
+ global $ai_wp_data, $ai_total_plugin_time, $ai_db_options_extract, $block_object;
1113
 
1114
  if (!defined ('AI_BUFFERING_START')) return;
1115
 
1116
+ $page = ob_get_clean();
1117
+
1118
  if (($ai_wp_data [AI_WP_DEBUGGING] & AI_DEBUG_PROCESSING) != 0) {
1119
+ ai_log ("BUFFERING END: level " . ob_get_level ());
1120
  $start_time = microtime (true);
1121
  }
1122
 
 
 
 
1123
  // TEST
1124
  // $page = ai_content_hook ($page);
1125
 
1126
  $matches = preg_split ('/(<body.*?'.'>)/i', $page, - 1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
 
1127
 
1128
+ if (($ai_wp_data [AI_WP_DEBUGGING] & AI_DEBUG_PROCESSING) != 0) {
1129
+ ai_log ("BUFFER body matches: " . count ($matches));
1130
+ }
1131
+
1132
+ if (count ($matches) == 3) {
1133
+ if (($ai_wp_data [AI_WP_DEBUGGING] & AI_DEBUG_PROCESSING) != 0) {
1134
+ ai_log ("BUFFER PROCESSING");
1135
+ }
1136
+
1137
+ $body = $matches [2];
1138
+
1139
+ if (isset ($ai_db_options_extract [HTML_ELEMENT_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]) && class_exists ('DOMDocument')) {
1140
+ require_once ('includes/phpQuery.php');
1141
+
1142
+ $no_closing_tag = array ('img', 'hr', 'br');
1143
+ $multibyte = get_paragraph_counting_functions() == AI_MULTIBYTE_PARAGRAPH_COUNTING_FUNCTIONS;
1144
+
1145
+ foreach ($ai_db_options_extract [HTML_ELEMENT_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]] as $block) {
1146
+
1147
+ $obj = $block_object [$block];
1148
+ $obj->clear_code_cache ();
1149
+
1150
+ $insert_after = $obj->get_automatic_insertion () == AI_AUTOMATIC_INSERTION_AFTER_HTML_ELEMENT;
1151
+ $selector = $obj->get_html_selector ();
1152
+
1153
+ libxml_use_internal_errors(true);
1154
+ $content = phpQuery::newDocumentHTML ($body);
1155
+ libxml_use_internal_errors(false);
1156
+ foreach (pq ($selector) as $element){
1157
+ if ($insert_after)
1158
+ pq ($element)->after (AI_MARKER_START.$element->tagName.AI_MARKER_END); else
1159
+ pq ($element)->before (AI_MARKER_START.$element->tagName.AI_MARKER_END);
1160
+ }
1161
+
1162
+ $markers = preg_split ('/('.AI_MARKER_START.'.*?'.AI_MARKER_END.')/', $content->htmlOuter (), - 1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
1163
+
1164
+ $content_before = '';
1165
+ $insertions = array ();
1166
+ foreach ($markers as $marker) {
1167
+ if (strpos ($marker, AI_MARKER_START) === 0) {
1168
+ $tag = str_replace (array (AI_MARKER_START, AI_MARKER_END), '', $marker);
1169
+
1170
+ if ($insert_after && in_array ($tag, $no_closing_tag)) $tag_string = '>'; else
1171
+ $tag_string = $insert_after ? "</{$tag}>" : "<{$tag}";
1172
+
1173
+ preg_match_all ("#{$tag_string}#i", $content_before, $tag_matches);
1174
+ $insertions []= array ($tag_string, $insert_after ? count ($tag_matches [0]) : count ($tag_matches [0]) + 1);
1175
+ continue;
1176
+ }
1177
+ $content_before .= $marker;
1178
+ }
1179
+
1180
+ $insertion_offsets = array ();
1181
+ foreach ($insertions as $insertion) {
1182
+ $tag = $insertion [0];
1183
+ $tag_counter = $insertion [1];
1184
+ preg_match_all ("#$tag#i", $body, $org_tag_matches, PREG_OFFSET_CAPTURE);
1185
+ if (isset ($org_tag_matches [0][$tag_counter - 1])) {
1186
+ if ($insert_after)
1187
+ $insertion_offsets []= $org_tag_matches [0][$tag_counter - 1][1] + strlen ($tag); else
1188
+ $insertion_offsets []= $org_tag_matches [0][$tag_counter - 1][1];
1189
+ }
1190
+ }
1191
+
1192
+ sort ($insertion_offsets);
1193
+
1194
+ $new_content = '';
1195
+ $current_offset = 0;
1196
+ foreach ($insertion_offsets as $insertion_offset) {
1197
+ if ($multibyte)
1198
+ $new_content .= mb_substr ($body, $current_offset, $insertion_offset - $current_offset);
1199
+ $new_content .= substr ($body, $current_offset, $insertion_offset - $current_offset);
1200
+
1201
+ $current_offset = $insertion_offset;
1202
+
1203
+ ob_start ();
1204
+
1205
+ $action = ($insert_after ? 'after' : 'before') . '_html_element';
1206
+ $action_name = ($insert_after ? 'After ' : 'Before ') . $selector;
1207
+
1208
+ $ai_db_options_extract [$action . CUSTOM_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]] = array ($block);
1209
+ ai_custom_hook ($action, $action_name);
1210
+ unset ($ai_db_options_extract [$action . CUSTOM_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]);
1211
+
1212
+ $new_content .= ob_get_clean();
1213
+ }
1214
+ $new_content .= substr ($body, $current_offset);
1215
+
1216
+ $body = $new_content;
1217
+ }
1218
+ }
1219
+
1220
+ echo $matches [0], $matches [1];
1221
+
1222
+ if (($ai_wp_data [AI_WP_DEBUGGING] & AI_DEBUG_POSITIONS) != 0) {
1223
+ $class = AI_DEBUG_STATUS_CLASS;
1224
+ echo "<section class='$class'>OUTPUT BUFFERING</section>";
1225
+ }
1226
 
1227
+ ai_custom_hook ('above_header', 'Above Header');
1228
+ echo $body;
1229
+ } else echo $page;
1230
 
1231
  // echo ai_content_hook ($matches [2]);
1232
 
1625
  $ai_wp_data [AI_STICKY_WIDGETS] ||
1626
  (defined ('AI_ADBLOCKING_DETECTION') && AI_ADBLOCKING_DETECTION && $ai_wp_data [AI_ADB_DETECTION]);
1627
 
1628
+ if ($footer_inline_scripts || $ai_wp_data [AI_CLIENT_SIDE_INSERTION] || $ai_wp_data [AI_FRONTEND_JS_DEBUGGING]) {
1629
  // force loading of jquery on frontend
1630
+ wp_enqueue_script ('ai-jquery-js', plugins_url ('includes/js/ai-jquery.js', __FILE__ ), array (
1631
  'jquery',
1632
  ));
1633
+ if ($ai_wp_data [AI_FRONTEND_JS_DEBUGGING]) {
1634
+ wp_add_inline_script ('ai-jquery-js', 'ai_debugging = true;');
1635
+ }
1636
+ if ($ai_wp_data [AI_CLIENT_SIDE_INSERTION]) {
1637
+ wp_add_inline_script ('ai-jquery-js', ai_get_js ('ai-insert', false));
1638
+ }
1639
  }
1640
  }
1641
 
1650
  function add_head_inline_styles_and_scripts () {
1651
  global $ai_wp_data;
1652
 
 
 
 
 
 
 
 
 
 
 
 
 
1653
  if ($ai_wp_data [AI_CLIENT_SIDE_DETECTION] ||
1654
  get_dynamic_blocks () == AI_DYNAMIC_BLOCKS_CLIENT_SIDE ||
1655
  get_admin_toolbar_debugging () && (get_remote_debugging () || ($ai_wp_data [AI_WP_USER] & AI_USER_LOGGED_IN) != 0) ||
2266
 
2267
  if (defined ('AI_ADBLOCKING_DETECTION') && AI_ADBLOCKING_DETECTION) {
2268
  if ($ai_wp_data [AI_ADB_DETECTION]) {
2269
+ $page_type = "<section id='ai-adb-bar' class='".AI_DEBUG_STATUS_CLASS.' '.AI_DEBUG_ADB_CLASS."' title='Click to delete ad blocking detection cokies'>AD BLOCKING <span id='ai-adb-status'>STATUS UNKNOWN</span></section>";
2270
  }
2271
  }
2272
 
2742
  break;
2743
  }
2744
  echo "\n";
2745
+ echo 'NO PAR. COUNTING INSIDE: ', get_no_paragraph_counting_inside (), "\n";
2746
  if (defined ('AI_BUFFERING')) {
2747
  echo 'OUTPUT BUFFERING: ';
2748
  switch (get_output_buffering()) {
2974
  if (count ($ai_db_options_extract [$custom_hook ['action'] . CUSTOM_HOOK_BLOCKS][AI_PT_ANY]))
2975
  echo substr (strtoupper (str_replace (array ('&lt;', '&gt;'), array ('<', '>'), get_hook_name ($custom_hook ['index']))) . " HOOK: ", 0, 25), implode (", ", $ai_db_options_extract [$custom_hook ['action'] . CUSTOM_HOOK_BLOCKS][AI_PT_ANY]), "\n";
2976
  }
2977
+ if (count ($ai_db_options_extract [HTML_ELEMENT_HOOK_BLOCKS][AI_PT_ANY]))
2978
+ echo "HTML ELEMENT: ", implode (", ", $ai_db_options_extract [HTML_ELEMENT_HOOK_BLOCKS][AI_PT_ANY]), "\n";
2979
 
2980
  echo "\nBLOCKS FOR THIS PAGE TYPE\n";
2981
  if (isset ($ai_db_options_extract [ABOVE_HEADER_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]) && count ($ai_db_options_extract [ABOVE_HEADER_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]))
3014
  if (isset ($ai_db_options_extract [$custom_hook ['action'] . CUSTOM_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]) && count ($ai_db_options_extract [$custom_hook ['action'] . CUSTOM_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]))
3015
  echo substr (strtoupper (str_replace (array ('&lt;', '&gt;'), array ('<', '>'), get_hook_name ($custom_hook ['index']))) . " HOOK: ", 0, 25), implode (", ", $ai_db_options_extract [$custom_hook ['action'] . CUSTOM_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]), "\n";
3016
  }
3017
+ if (isset ($ai_db_options_extract [HTML_ELEMENT_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]) && count ($ai_db_options_extract [HTML_ELEMENT_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]))
3018
+ echo "HTML ELEMENT: ", implode (", ", $ai_db_options_extract [HTML_ELEMENT_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]), "\n";
3019
 
3020
  if ($write_processing_log) {
3021
  echo "\nTIME EVENT\n";
3042
  echo "Display Errors: ", ini_get ('display_errors'), "\n";
3043
  echo "cURL: ", function_exists ('curl_version') ? 'ENABLED' : 'DISABLED', "\n";
3044
  echo "fsockopen: ", function_exists ('fsockopen') ? 'ENABLED' : 'DISABLED', "\n";
3045
+ echo "DOMDocument: ", class_exists ('DOMDocument') ? 'YES' : 'NO', "\n";
3046
  echo "\n\n";
3047
 
3048
  global $wp_version;
3059
  }
3060
  $all_plugins = get_plugins();
3061
  $active_plugins = get_option ('active_plugins');
3062
+ $active_sitewide_plugins = is_multisite () ? get_site_option ('active_sitewide_plugins') : false;
3063
+
3064
  foreach ($all_plugins as $plugin_path => $plugin) {
3065
+ $multisite_status = ' ';
3066
+ if ($active_sitewide_plugins !== false) {
3067
+ $multisite_status = array_key_exists ($plugin_path, $active_sitewide_plugins) ? '# ' : ' ';
3068
+ }
3069
+ echo in_array ($plugin_path, $active_plugins) ? '* ' : $multisite_status, html_entity_decode ($plugin ["Name"]), ' ', $plugin ["Version"], "\n";
3070
  }
3071
  }
3072
 
3983
 
3984
  if (defined ('AI_BUFFERING')) {
3985
  $above_header_hook_blocks = array (AI_PT_ANY => array (), AI_PT_HOMEPAGE => array(), AI_PT_CATEGORY => array(), AI_PT_SEARCH => array(), AI_PT_ARCHIVE => array(), AI_PT_STATIC => array(), AI_PT_POST => array(), AI_PT_404 => array(), AI_PT_FEED => array(), AI_PT_AJAX => array());
3986
+ $html_element_hook_blocks = array (AI_PT_ANY => array (), AI_PT_HOMEPAGE => array(), AI_PT_CATEGORY => array(), AI_PT_SEARCH => array(), AI_PT_ARCHIVE => array(), AI_PT_STATIC => array(), AI_PT_POST => array(), AI_PT_404 => array(), AI_PT_FEED => array(), AI_PT_AJAX => array());
3987
  }
3988
 
3989
  $content_hook_blocks = array (AI_PT_ANY => array (), AI_PT_HOMEPAGE => array(), AI_PT_CATEGORY => array(), AI_PT_SEARCH => array(), AI_PT_ARCHIVE => array(), AI_PT_STATIC => array(), AI_PT_POST => array(), AI_PT_404 => array(), AI_PT_FEED => array(), AI_PT_AJAX => array());
4053
  switch ($automatic_insertion) {
4054
  case AI_AUTOMATIC_INSERTION_BEFORE_HTML_ELEMENT:
4055
  case AI_AUTOMATIC_INSERTION_AFTER_HTML_ELEMENT:
4056
+ switch ($obj->get_html_element_insertion ()) {
4057
+ case AI_HTML_INSERTION_SEREVR_SIDE:
4058
+ $automatic_insertion = AI_AUTOMATIC_INSERTION_OUTPUT_BUFFERING;
4059
+ break;
4060
+ default:
4061
+ $automatic_insertion = $obj->get_server_side_insertion ();
4062
+ break;
4063
+ }
4064
  break;
4065
  }
4066
 
4071
  $above_header_hook_blocks [AI_PT_ANY][]= $block;
4072
  }
4073
  break;
4074
+ case AI_AUTOMATIC_INSERTION_OUTPUT_BUFFERING:
4075
+ if (defined ('AI_BUFFERING')) {
4076
+ foreach ($page_types as $block_page_type) $html_element_hook_blocks [$block_page_type][]= $block;
4077
+ $html_element_hook_blocks [AI_PT_ANY][]= $block;
4078
+ }
4079
+ break;
4080
  case AI_AUTOMATIC_INSERTION_BEFORE_PARAGRAPH:
4081
  case AI_AUTOMATIC_INSERTION_AFTER_PARAGRAPH:
4082
  case AI_AUTOMATIC_INSERTION_BEFORE_CONTENT:
4141
 
4142
  if (defined ('AI_BUFFERING')) {
4143
  $extract [ABOVE_HEADER_HOOK_BLOCKS] = $above_header_hook_blocks;
4144
+ $extract [HTML_ELEMENT_HOOK_BLOCKS] = $html_element_hook_blocks;
4145
  }
4146
  $extract [CONTENT_HOOK_BLOCKS] = $content_hook_blocks;
4147
  $extract [EXCERPT_HOOK_BLOCKS] = $excerpt_hook_blocks;
4226
  switch ($obj->get_automatic_insertion()) {
4227
  case AI_AUTOMATIC_INSERTION_BEFORE_HTML_ELEMENT:
4228
  case AI_AUTOMATIC_INSERTION_AFTER_HTML_ELEMENT:
4229
+ if ($obj->get_html_element_insertion () != AI_HTML_INSERTION_SEREVR_SIDE) $ai_wp_data [AI_CLIENT_SIDE_INSERTION] = true;
4230
  break;
4231
  }
4232
  }
4365
  .ai-debug-tags {font-weight: bold; color: white; padding: 2px;}
4366
  .ai-debug-positions {clear: both; text-align: center; padding: 10px 0; font-weight: bold; border: 1px solid blue; color: blue; background: #eef;}
4367
  .ai-debug-page-type {text-align: center; padding: 10px 0; font-weight: bold; border: 1px solid green; color: green; background: #efe;}
4368
+ .ai-debug-status {clear: both; text-align: center; padding: 10px 0; font-weight: bold; border: 1px solid red; color: red; background: #fee;}
4369
+ .ai-debug-adb {opacity: 0.85; cursor: pointer;}
4370
  .ai-debug-widget {margin: 0; padding: 0 5px; font-size: 10px; white-space: pre; overflow-x: auto; overflow-y: hidden;}
4371
  a.ai-debug-left {float: left; font-size: 10px; text-decoration: none; color: transparent; padding: 0px 10px 0 0; box-shadow: none;}
4372
  a.ai-debug-right {float: right; font-size: 10px; text-decoration: none; color: #88f; padding: 0px 10px 0 0; box-shadow: none;}
class.php CHANGED
@@ -1637,15 +1637,18 @@ abstract class ai_CodeBlock extends ai_BaseCodeBlock {
1637
  global $ai_wp_data, $block_object;
1638
 
1639
  $html_element_insertion = false;
1640
- $viewports_insertion = $this->get_detection_client_side() && $this->get_client_side_action () == AI_CLIENT_SIDE_ACTION_INSERT;
 
1641
 
1642
  switch ($this->get_automatic_insertion()) {
1643
  case AI_AUTOMATIC_INSERTION_BEFORE_HTML_ELEMENT:
1644
  $insertion = 'before';
 
1645
  $html_element_insertion = true;
1646
  break;
1647
  case AI_AUTOMATIC_INSERTION_AFTER_HTML_ELEMENT:
1648
  $insertion = 'after';
 
1649
  $html_element_insertion = true;
1650
  break;
1651
  default:
@@ -2621,7 +2624,7 @@ abstract class ai_CodeBlock extends ai_BaseCodeBlock {
2621
  public function after_paragraph ($content, $position_preview = false) {
2622
  global $ai_wp_data, $ai_last_check, $special_element_tags;
2623
 
2624
- $no_closing_tag = array ('img', 'hr');
2625
 
2626
  $multibyte = get_paragraph_counting_functions() == AI_MULTIBYTE_PARAGRAPH_COUNTING_FUNCTIONS;
2627
 
1637
  global $ai_wp_data, $block_object;
1638
 
1639
  $html_element_insertion = false;
1640
+ $viewports_insertion = $this->get_detection_client_side() && $this->get_client_side_action () == AI_CLIENT_SIDE_ACTION_INSERT;
1641
+ $server_side_html_insertion = $this->get_html_element_insertion () == AI_HTML_INSERTION_SEREVR_SIDE;
1642
 
1643
  switch ($this->get_automatic_insertion()) {
1644
  case AI_AUTOMATIC_INSERTION_BEFORE_HTML_ELEMENT:
1645
  $insertion = 'before';
1646
+ if ($server_side_html_insertion) return $this->get_code_for_insertion ($include_viewport_classes, $hidden_widgets, $code_only);
1647
  $html_element_insertion = true;
1648
  break;
1649
  case AI_AUTOMATIC_INSERTION_AFTER_HTML_ELEMENT:
1650
  $insertion = 'after';
1651
+ if ($server_side_html_insertion) return $this->get_code_for_insertion ($include_viewport_classes, $hidden_widgets, $code_only);
1652
  $html_element_insertion = true;
1653
  break;
1654
  default:
2624
  public function after_paragraph ($content, $position_preview = false) {
2625
  global $ai_wp_data, $ai_last_check, $special_element_tags;
2626
 
2627
+ $no_closing_tag = array ('img', 'hr', 'br');
2628
 
2629
  $multibyte = get_paragraph_counting_functions() == AI_MULTIBYTE_PARAGRAPH_COUNTING_FUNCTIONS;
2630
 
constants.php CHANGED
@@ -24,7 +24,7 @@ if (!defined( 'AD_INSERTER_NAME'))
24
  define ('AD_INSERTER_NAME', 'Ad Inserter');
25
 
26
  if (!defined( 'AD_INSERTER_VERSION'))
27
- define ('AD_INSERTER_VERSION', '2.3.0');
28
 
29
  if (!defined ('AD_INSERTER_PLUGIN_BASENAME'))
30
  define ('AD_INSERTER_PLUGIN_BASENAME', plugin_basename (__FILE__));
@@ -205,6 +205,8 @@ define('AI_AUTOMATIC_INSERTION_FOOTER', 13);
205
  define('AI_AUTOMATIC_INSERTION_ABOVE_HEADER', 14);
206
  define('AI_AUTOMATIC_INSERTION_BEFORE_HTML_ELEMENT', 15);
207
  define('AI_AUTOMATIC_INSERTION_AFTER_HTML_ELEMENT', 16);
 
 
208
  define('AI_AUTOMATIC_INSERTION_CUSTOM_HOOK', 100);
209
 
210
  define('AI_TEXT_DISABLED', 'Disabled');
@@ -578,6 +580,9 @@ define ('DEFAULT_VIEWPORT_WIDTH_3', 0);
578
 
579
  define ('DEFAULT_COUNTRY_GROUP_NAME', "Group");
580
 
 
 
 
581
  define ('CONTENT_HOOK_BLOCKS', 'content_hook');
582
  define ('EXCERPT_HOOK_BLOCKS', 'excerpt_hook');
583
  define ('LOOP_START_HOOK_BLOCKS', 'loop_start_hook');
@@ -588,6 +593,7 @@ define ('BETWEEN_COMMENTS_HOOK_BLOCKS', 'between_comments_hook');
588
  define ('AFTER_COMMENTS_HOOK_BLOCKS', 'after_comments_hook');
589
  define ('FOOTER_HOOK_BLOCKS', 'wp_footer_hook');
590
  define ('ABOVE_HEADER_HOOK_BLOCKS', 'above_header_hook');
 
591
  define ('CUSTOM_HOOK_BLOCKS', '_hook');
592
  define ('AI_EXTRACT_USED_BLOCKS', 'used_blocks');
593
 
@@ -726,6 +732,7 @@ define ('AI_URL_DEBUG_NO_INSERTION', 'ai-debug-no-insertion');
726
  define ('AI_URL_DEBUG_COUNTRY', 'ai-debug-country');
727
  define ('AI_URL_DEBUG_AD_BLOCKING' , 'ai-debug-adb');
728
  define ('AI_URL_DEBUG_AD_BLOCKING_STATUS', 'ai-debug-adb-status');
 
729
 
730
  define ('AI_DEBUG_PROCESSING', 0x01); // AI_DEBUG_PROCESSING_
731
  define ('AI_DEBUG_BLOCKS', 0x02);
@@ -749,6 +756,7 @@ define ('AI_DEBUG_WIDGET_STYLE', 'margin: 0; padding: 0 5px; font-size:
749
  define ('AI_DEBUG_TAGS_CLASS', 'ai-debug-tags');
750
  define ('AI_DEBUG_POSITIONS_CLASS', 'ai-debug-positions');
751
  define ('AI_DEBUG_PAGE_TYPE_CLASS', 'ai-debug-page-type');
 
752
  define ('AI_DEBUG_ADB_CLASS', 'ai-debug-adb');
753
 
754
  define ('AI_CODE_UNKNOWN', 100);
24
  define ('AD_INSERTER_NAME', 'Ad Inserter');
25
 
26
  if (!defined( 'AD_INSERTER_VERSION'))
27
+ define ('AD_INSERTER_VERSION', '2.3.1');
28
 
29
  if (!defined ('AD_INSERTER_PLUGIN_BASENAME'))
30
  define ('AD_INSERTER_PLUGIN_BASENAME', plugin_basename (__FILE__));
205
  define('AI_AUTOMATIC_INSERTION_ABOVE_HEADER', 14);
206
  define('AI_AUTOMATIC_INSERTION_BEFORE_HTML_ELEMENT', 15);
207
  define('AI_AUTOMATIC_INSERTION_AFTER_HTML_ELEMENT', 16);
208
+
209
+ define('AI_AUTOMATIC_INSERTION_OUTPUT_BUFFERING', 99);
210
  define('AI_AUTOMATIC_INSERTION_CUSTOM_HOOK', 100);
211
 
212
  define('AI_TEXT_DISABLED', 'Disabled');
580
 
581
  define ('DEFAULT_COUNTRY_GROUP_NAME', "Group");
582
 
583
+ define ('AI_MARKER_START', '%##');
584
+ define ('AI_MARKER_END', '##%');
585
+
586
  define ('CONTENT_HOOK_BLOCKS', 'content_hook');
587
  define ('EXCERPT_HOOK_BLOCKS', 'excerpt_hook');
588
  define ('LOOP_START_HOOK_BLOCKS', 'loop_start_hook');
593
  define ('AFTER_COMMENTS_HOOK_BLOCKS', 'after_comments_hook');
594
  define ('FOOTER_HOOK_BLOCKS', 'wp_footer_hook');
595
  define ('ABOVE_HEADER_HOOK_BLOCKS', 'above_header_hook');
596
+ define ('HTML_ELEMENT_HOOK_BLOCKS', 'html_element_hook');
597
  define ('CUSTOM_HOOK_BLOCKS', '_hook');
598
  define ('AI_EXTRACT_USED_BLOCKS', 'used_blocks');
599
 
732
  define ('AI_URL_DEBUG_COUNTRY', 'ai-debug-country');
733
  define ('AI_URL_DEBUG_AD_BLOCKING' , 'ai-debug-adb');
734
  define ('AI_URL_DEBUG_AD_BLOCKING_STATUS', 'ai-debug-adb-status');
735
+ define ('AI_URL_DEBUG_JAVASCRIPT', 'ai-debug-js');
736
 
737
  define ('AI_DEBUG_PROCESSING', 0x01); // AI_DEBUG_PROCESSING_
738
  define ('AI_DEBUG_BLOCKS', 0x02);
756
  define ('AI_DEBUG_TAGS_CLASS', 'ai-debug-tags');
757
  define ('AI_DEBUG_POSITIONS_CLASS', 'ai-debug-positions');
758
  define ('AI_DEBUG_PAGE_TYPE_CLASS', 'ai-debug-page-type');
759
+ define ('AI_DEBUG_STATUS_CLASS', 'ai-debug-status');
760
  define ('AI_DEBUG_ADB_CLASS', 'ai-debug-adb');
761
 
762
  define ('AI_CODE_UNKNOWN', 100);
css/ad-inserter.css CHANGED
@@ -1,5 +1,5 @@
1
  #ai-data {
2
- font-family: "2.3.0"; /* Used for version number of the file */
3
  }
4
 
5
  #blocked-warning {
1
  #ai-data {
2
+ font-family: "2.3.1"; /* Used for version number of the file */
3
  }
4
 
5
  #blocked-warning {
includes/js/ai-jquery.js ADDED
File without changes
includes/phpQuery.php ADDED
@@ -0,0 +1,5702 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * phpQuery is a server-side, chainable, CSS3 selector driven
4
+ * Document Object Model (DOM) API based on jQuery JavaScript Library.
5
+ *
6
+ * @version 0.9.5
7
+ * @link http://code.google.com/p/phpquery/
8
+ * @link http://phpquery-library.blogspot.com/
9
+ * @link http://jquery.com/
10
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
11
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
12
+ * @package phpQuery
13
+ */
14
+
15
+ // class names for instanceof
16
+ // TODO move them as class constants into phpQuery
17
+ define('DOMDOCUMENT', 'DOMDocument');
18
+ define('DOMELEMENT', 'DOMElement');
19
+ define('DOMNODELIST', 'DOMNodeList');
20
+ define('DOMNODE', 'DOMNode');
21
+
22
+ /**
23
+ * DOMEvent class.
24
+ *
25
+ * Based on
26
+ * @link http://developer.mozilla.org/En/DOM:event
27
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
28
+ * @package phpQuery
29
+ * @todo implement ArrayAccess ?
30
+ */
31
+ class DOMEvent {
32
+ /**
33
+ * Returns a boolean indicating whether the event bubbles up through the DOM or not.
34
+ *
35
+ * @var unknown_type
36
+ */
37
+ public $bubbles = true;
38
+ /**
39
+ * Returns a boolean indicating whether the event is cancelable.
40
+ *
41
+ * @var unknown_type
42
+ */
43
+ public $cancelable = true;
44
+ /**
45
+ * Returns a reference to the currently registered target for the event.
46
+ *
47
+ * @var unknown_type
48
+ */
49
+ public $currentTarget;
50
+ /**
51
+ * Returns detail about the event, depending on the type of event.
52
+ *
53
+ * @var unknown_type
54
+ * @link http://developer.mozilla.org/en/DOM/event.detail
55
+ */
56
+ public $detail; // ???
57
+ /**
58
+ * Used to indicate which phase of the event flow is currently being evaluated.
59
+ *
60
+ * NOT IMPLEMENTED
61
+ *
62
+ * @var unknown_type
63
+ * @link http://developer.mozilla.org/en/DOM/event.eventPhase
64
+ */
65
+ public $eventPhase; // ???
66
+ /**
67
+ * The explicit original target of the event (Mozilla-specific).
68
+ *
69
+ * NOT IMPLEMENTED
70
+ *
71
+ * @var unknown_type
72
+ */
73
+ public $explicitOriginalTarget; // moz only
74
+ /**
75
+ * The original target of the event, before any retargetings (Mozilla-specific).
76
+ *
77
+ * NOT IMPLEMENTED
78
+ *
79
+ * @var unknown_type
80
+ */
81
+ public $originalTarget; // moz only
82
+ /**
83
+ * Identifies a secondary target for the event.
84
+ *
85
+ * @var unknown_type
86
+ */
87
+ public $relatedTarget;
88
+ /**
89
+ * Returns a reference to the target to which the event was originally dispatched.
90
+ *
91
+ * @var unknown_type
92
+ */
93
+ public $target;
94
+ /**
95
+ * Returns the time that the event was created.
96
+ *
97
+ * @var unknown_type
98
+ */
99
+ public $timeStamp;
100
+ /**
101
+ * Returns the name of the event (case-insensitive).
102
+ */
103
+ public $type;
104
+ public $runDefault = true;
105
+ public $data = null;
106
+ public function __construct($data) {
107
+ foreach($data as $k => $v) {
108
+ $this->$k = $v;
109
+ }
110
+ if (! $this->timeStamp)
111
+ $this->timeStamp = time();
112
+ }
113
+ /**
114
+ * Cancels the event (if it is cancelable).
115
+ *
116
+ */
117
+ public function preventDefault() {
118
+ $this->runDefault = false;
119
+ }
120
+ /**
121
+ * Stops the propagation of events further along in the DOM.
122
+ *
123
+ */
124
+ public function stopPropagation() {
125
+ $this->bubbles = false;
126
+ }
127
+ }
128
+
129
+
130
+ /**
131
+ * DOMDocumentWrapper class simplifies work with DOMDocument.
132
+ *
133
+ * Know bug:
134
+ * - in XHTML fragments, <br /> changes to <br clear="none" />
135
+ *
136
+ * @todo check XML catalogs compatibility
137
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
138
+ * @package phpQuery
139
+ */
140
+ class DOMDocumentWrapper {
141
+ /**
142
+ * @var DOMDocument
143
+ */
144
+ public $document;
145
+ public $id;
146
+ /**
147
+ * @todo Rewrite as method and quess if null.
148
+ * @var unknown_type
149
+ */
150
+ public $contentType = '';
151
+ public $xpath;
152
+ public $uuid = 0;
153
+ public $data = array();
154
+ public $dataNodes = array();
155
+ public $events = array();
156
+ public $eventsNodes = array();
157
+ public $eventsGlobal = array();
158
+ /**
159
+ * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28
160
+ * @var unknown_type
161
+ */
162
+ public $frames = array();
163
+ /**
164
+ * Document root, by default equals to document itself.
165
+ * Used by documentFragments.
166
+ *
167
+ * @var DOMNode
168
+ */
169
+ public $root;
170
+ public $isDocumentFragment;
171
+ public $isXML = false;
172
+ public $isXHTML = false;
173
+ public $isHTML = false;
174
+ public $charset;
175
+ public function __construct($markup = null, $contentType = null, $newDocumentID = null) {
176
+ if (isset($markup))
177
+ $this->load($markup, $contentType, $newDocumentID);
178
+ $this->id = $newDocumentID
179
+ ? $newDocumentID
180
+ : md5(microtime());
181
+ }
182
+ public function load($markup, $contentType = null, $newDocumentID = null) {
183
+ // phpQuery::$documents[$id] = $this;
184
+ $this->contentType = strtolower($contentType);
185
+ if ($markup instanceof DOMDOCUMENT) {
186
+ $this->document = $markup;
187
+ $this->root = $this->document;
188
+ $this->charset = $this->document->encoding;
189
+ // TODO isDocumentFragment
190
+ } else {
191
+ $loaded = $this->loadMarkup($markup);
192
+ }
193
+ if ($loaded) {
194
+ // $this->document->formatOutput = true;
195
+ $this->document->preserveWhiteSpace = true;
196
+ $this->xpath = new DOMXPath($this->document);
197
+ $this->afterMarkupLoad();
198
+ return true;
199
+ // remember last loaded document
200
+ // return phpQuery::selectDocument($id);
201
+ }
202
+ return false;
203
+ }
204
+ protected function afterMarkupLoad() {
205
+ if ($this->isXHTML) {
206
+ $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml");
207
+ }
208
+ }
209
+ protected function loadMarkup($markup) {
210
+ $loaded = false;
211
+ if ($this->contentType) {
212
+ self::debug("Load markup for content type {$this->contentType}");
213
+ // content determined by contentType
214
+ list($contentType, $charset) = $this->contentTypeToArray($this->contentType);
215
+ switch($contentType) {
216
+ case 'text/html':
217
+ phpQuery::debug("Loading HTML, content type '{$this->contentType}'");
218
+ $loaded = $this->loadMarkupHTML($markup, $charset);
219
+ break;
220
+ case 'text/xml':
221
+ case 'application/xhtml+xml':
222
+ phpQuery::debug("Loading XML, content type '{$this->contentType}'");
223
+ $loaded = $this->loadMarkupXML($markup, $charset);
224
+ break;
225
+ default:
226
+ // for feeds or anything that sometimes doesn't use text/xml
227
+ if (strpos('xml', $this->contentType) !== false) {
228
+ phpQuery::debug("Loading XML, content type '{$this->contentType}'");
229
+ $loaded = $this->loadMarkupXML($markup, $charset);
230
+ } else
231
+ phpQuery::debug("Could not determine document type from content type '{$this->contentType}'");
232
+ }
233
+ } else {
234
+ // content type autodetection
235
+ if ($this->isXML($markup)) {
236
+ phpQuery::debug("Loading XML, isXML() == true");
237
+ $loaded = $this->loadMarkupXML($markup);
238
+ if (! $loaded && $this->isXHTML) {
239
+ phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true');
240
+ $loaded = $this->loadMarkupHTML($markup);
241
+ }
242
+ } else {
243
+ phpQuery::debug("Loading HTML, isXML() == false");
244
+ $loaded = $this->loadMarkupHTML($markup);
245
+ }
246
+ }
247
+ return $loaded;
248
+ }
249
+ protected function loadMarkupReset() {
250
+ $this->isXML = $this->isXHTML = $this->isHTML = false;
251
+ }
252
+ protected function documentCreate($charset, $version = '1.0') {
253
+ if (! $version)
254
+ $version = '1.0';
255
+ $this->document = new DOMDocument($version, $charset);
256
+ $this->charset = $this->document->encoding;
257
+ // $this->document->encoding = $charset;
258
+ $this->document->formatOutput = true;
259
+ $this->document->preserveWhiteSpace = true;
260
+ }
261
+ protected function loadMarkupHTML($markup, $requestedCharset = null) {
262
+ if (phpQuery::$debug)
263
+ phpQuery::debug('Full markup load (HTML): '.substr($markup, 0, 250));
264
+ $this->loadMarkupReset();
265
+ $this->isHTML = true;
266
+ if (!isset($this->isDocumentFragment))
267
+ $this->isDocumentFragment = self::isDocumentFragmentHTML($markup);
268
+ $charset = null;
269
+ $documentCharset = $this->charsetFromHTML($markup);
270
+ $addDocumentCharset = false;
271
+ if ($documentCharset) {
272
+ $charset = $documentCharset;
273
+ $markup = $this->charsetFixHTML($markup);
274
+ } else if ($requestedCharset) {
275
+ $charset = $requestedCharset;
276
+ }
277
+ if (! $charset)
278
+ $charset = phpQuery::$defaultCharset;
279
+ // HTTP 1.1 says that the default charset is ISO-8859-1
280
+ // @see http://www.w3.org/International/O-HTTP-charset
281
+ if (! $documentCharset) {
282
+ $documentCharset = 'ISO-8859-1';
283
+ $addDocumentCharset = true;
284
+ }
285
+ // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding'
286
+ // Worse, some pages can have mixed encodings... we'll try not to worry about that
287
+ $requestedCharset = strtoupper($requestedCharset);
288
+ $documentCharset = strtoupper($documentCharset);
289
+ phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset");
290
+ if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) {
291
+ phpQuery::debug("CHARSET CONVERT");
292
+ // Document Encoding Conversion
293
+ // http://code.google.com/p/phpquery/issues/detail?id=86
294
+ if (function_exists('mb_detect_encoding')) {
295
+ $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO');
296
+ $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets));
297
+ if (! $docEncoding)
298
+ $docEncoding = $documentCharset; // ok trust the document
299
+ phpQuery::debug("DETECTED '$docEncoding'");
300
+ // Detected does not match what document says...
301
+ if ($docEncoding !== $documentCharset) {
302
+ // Tricky..
303
+ }
304
+ if ($docEncoding !== $requestedCharset) {
305
+ phpQuery::debug("CONVERT $docEncoding => $requestedCharset");
306
+ $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding);
307
+ $markup = $this->charsetAppendToHTML($markup, $requestedCharset);
308
+ $charset = $requestedCharset;
309
+ }
310
+ } else {
311
+ phpQuery::debug("TODO: charset conversion without mbstring...");
312
+ }
313
+ }
314
+ $return = false;
315
+ if ($this->isDocumentFragment) {
316
+ phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'");
317
+ $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
318
+ } else {
319
+ if ($addDocumentCharset) {
320
+ phpQuery::debug("Full markup load (HTML), appending charset: '$charset'");
321
+ $markup = $this->charsetAppendToHTML($markup, $charset);
322
+ }
323
+ phpQuery::debug("Full markup load (HTML), documentCreate('$charset')");
324
+ $this->documentCreate($charset);
325
+ $return = phpQuery::$debug === 2
326
+ ? $this->document->loadHTML($markup)
327
+ : @$this->document->loadHTML($markup);
328
+ if ($return)
329
+ $this->root = $this->document;
330
+ }
331
+ if ($return && ! $this->contentType)
332
+ $this->contentType = 'text/html';
333
+ return $return;
334
+ }
335
+ protected function loadMarkupXML($markup, $requestedCharset = null) {
336
+ if (phpQuery::$debug)
337
+ phpQuery::debug('Full markup load (XML): '.substr($markup, 0, 250));
338
+ $this->loadMarkupReset();
339
+ $this->isXML = true;
340
+ // check agains XHTML in contentType or markup
341
+ $isContentTypeXHTML = $this->isXHTML();
342
+ $isMarkupXHTML = $this->isXHTML($markup);
343
+ if ($isContentTypeXHTML || $isMarkupXHTML) {
344
+ self::debug('Full markup load (XML), XHTML detected');
345
+ $this->isXHTML = true;
346
+ }
347
+ // determine document fragment
348
+ if (! isset($this->isDocumentFragment))
349
+ $this->isDocumentFragment = $this->isXHTML
350
+ ? self::isDocumentFragmentXHTML($markup)
351
+ : self::isDocumentFragmentXML($markup);
352
+ // this charset will be used
353
+ $charset = null;
354
+ // charset from XML declaration @var string
355
+ $documentCharset = $this->charsetFromXML($markup);
356
+ if (! $documentCharset) {
357
+ if ($this->isXHTML) {
358
+ // this is XHTML, try to get charset from content-type meta header
359
+ $documentCharset = $this->charsetFromHTML($markup);
360
+ if ($documentCharset) {
361
+ phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'");
362
+ $this->charsetAppendToXML($markup, $documentCharset);
363
+ $charset = $documentCharset;
364
+ }
365
+ }
366
+ if (! $documentCharset) {
367
+ // if still no document charset...
368
+ $charset = $requestedCharset;
369
+ }
370
+ } else if ($requestedCharset) {
371
+ $charset = $requestedCharset;
372
+ }
373
+ if (! $charset) {
374
+ $charset = phpQuery::$defaultCharset;
375
+ }
376
+ if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) {
377
+ // TODO place for charset conversion
378
+ // $charset = $requestedCharset;
379
+ }
380
+ $return = false;
381
+ if ($this->isDocumentFragment) {
382
+ phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'");
383
+ $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
384
+ } else {
385
+ // FIXME ???
386
+ if ($isContentTypeXHTML && ! $isMarkupXHTML)
387
+ if (! $documentCharset) {
388
+ phpQuery::debug("Full markup load (XML), appending charset '$charset'");
389
+ $markup = $this->charsetAppendToXML($markup, $charset);
390
+ }
391
+ // see http://pl2.php.net/manual/en/book.dom.php#78929
392
+ // LIBXML_DTDLOAD (>= PHP 5.1)
393
+ // does XML ctalogues works with LIBXML_NONET
394
+ // $this->document->resolveExternals = true;
395
+ // TODO test LIBXML_COMPACT for performance improvement
396
+ // create document
397
+ $this->documentCreate($charset);
398
+ if (phpversion() < 5.1) {
399
+ $this->document->resolveExternals = true;
400
+ $return = phpQuery::$debug === 2
401
+ ? $this->document->loadXML($markup)
402
+ : @$this->document->loadXML($markup);
403
+ } else {
404
+ /** @link http://pl2.php.net/manual/en/libxml.constants.php */
405
+ $libxmlStatic = phpQuery::$debug === 2
406
+ ? LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET
407
+ : LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOERROR;
408
+ $return = $this->document->loadXML($markup, $libxmlStatic);
409
+ // if (! $return)
410
+ // $return = $this->document->loadHTML($markup);
411
+ }
412
+ if ($return)
413
+ $this->root = $this->document;
414
+ }
415
+ if ($return) {
416
+ if (! $this->contentType) {
417
+ if ($this->isXHTML)
418
+ $this->contentType = 'application/xhtml+xml';
419
+ else
420
+ $this->contentType = 'text/xml';
421
+ }
422
+ return $return;
423
+ } else {
424
+ throw new Exception("Error loading XML markup");
425
+ }
426
+ }
427
+ protected function isXHTML($markup = null) {
428
+ if (! isset($markup)) {
429
+ return strpos($this->contentType, 'xhtml') !== false;
430
+ }
431
+ // XXX ok ?
432
+ return strpos($markup, "<!DOCTYPE html") !== false;
433
+ // return stripos($doctype, 'xhtml') !== false;
434
+ // $doctype = isset($dom->doctype) && is_object($dom->doctype)
435
+ // ? $dom->doctype->publicId
436
+ // : self::$defaultDoctype;
437
+ }
438
+ protected function isXML($markup) {
439
+ // return strpos($markup, '<?xml') !== false && stripos($markup, 'xhtml') === false;
440
+ return strpos(substr($markup, 0, 100), '<'.'?xml') !== false;
441
+ }
442
+ protected function contentTypeToArray($contentType) {
443
+ $matches = explode(';', trim(strtolower($contentType)));
444
+ if (isset($matches[1])) {
445
+ $matches[1] = explode('=', $matches[1]);
446
+ // strip 'charset='
447
+ $matches[1] = isset($matches[1][1]) && trim($matches[1][1])
448
+ ? $matches[1][1]
449
+ : $matches[1][0];
450
+ } else
451
+ $matches[1] = null;
452
+ return $matches;
453
+ }
454
+ /**
455
+ *
456
+ * @param $markup
457
+ * @return array contentType, charset
458
+ */
459
+ protected function contentTypeFromHTML($markup) {
460
+ $matches = array();
461
+ // find meta tag
462
+ preg_match('@<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
463
+ $markup, $matches
464
+ );
465
+ if (! isset($matches[0]))
466
+ return array(null, null);
467
+ // get attr 'content'
468
+ preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches);
469
+ if (! isset($matches[0]))
470
+ return array(null, null);
471
+ return $this->contentTypeToArray($matches[2]);
472
+ }
473
+ protected function charsetFromHTML($markup) {
474
+ $contentType = $this->contentTypeFromHTML($markup);
475
+ return $contentType[1];
476
+ }
477
+ protected function charsetFromXML($markup) {
478
+ $matches;
479
+ // find declaration
480
+ preg_match('@<'.'?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i',
481
+ $markup, $matches
482
+ );
483
+ return isset($matches[2])
484
+ ? strtolower($matches[2])
485
+ : null;
486
+ }
487
+ /**
488
+ * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug.
489
+ *
490
+ * @link http://code.google.com/p/phpquery/issues/detail?id=80
491
+ * @param $html
492
+ */
493
+ protected function charsetFixHTML($markup) {
494
+ $matches = array();
495
+ // find meta tag
496
+ preg_match('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
497
+ $markup, $matches, PREG_OFFSET_CAPTURE
498
+ );
499
+ if (! isset($matches[0]))
500
+ return;
501
+ $metaContentType = $matches[0][0];
502
+ $markup = substr($markup, 0, $matches[0][1])
503
+ .substr($markup, $matches[0][1]+strlen($metaContentType));
504
+ $headStart = stripos($markup, '<head>');
505
+ $markup = substr($markup, 0, $headStart+6).$metaContentType
506
+ .substr($markup, $headStart+6);
507
+ return $markup;
508
+ }
509
+ protected function charsetAppendToHTML($html, $charset, $xhtml = false) {
510
+ // remove existing meta[type=content-type]
511
+ $html = preg_replace('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html);
512
+ $meta = '<meta http-equiv="Content-Type" content="text/html;charset='
513
+ .$charset.'" '
514
+ .($xhtml ? '/' : '')
515
+ .'>';
516
+ if (strpos($html, '<head') === false) {
517
+ if (strpos($hltml, '<html') === false) {
518
+ return $meta.$html;
519
+ } else {
520
+ return preg_replace(
521
+ '@<html(.*?)(?(?<!\?)>)@s',
522
+ "<html\\1><head>{$meta}</head>",
523
+ $html
524
+ );
525
+ }
526
+ } else {
527
+ return preg_replace(
528
+ '@<head(.*?)(?(?<!\?)>)@s',
529
+ '<head\\1>'.$meta,
530
+ $html
531
+ );
532
+ }
533
+ }
534
+ protected function charsetAppendToXML($markup, $charset) {
535
+ $declaration = '<'.'?xml version="1.0" encoding="'.$charset.'"?'.'>';
536
+ return $declaration.$markup;
537
+ }
538
+ public static function isDocumentFragmentHTML($markup) {
539
+ return stripos($markup, '<html') === false && stripos($markup, '<!doctype') === false;
540
+ }
541
+ public static function isDocumentFragmentXML($markup) {
542
+ return stripos($markup, '<'.'?xml') === false;
543
+ }
544
+ public static function isDocumentFragmentXHTML($markup) {
545
+ return self::isDocumentFragmentHTML($markup);
546
+ }
547
+ public function importAttr($value) {
548
+ // TODO
549
+ }
550
+ /**
551
+ *
552
+ * @param $source
553
+ * @param $target
554
+ * @param $sourceCharset
555
+ * @return array Array of imported nodes.
556
+ */
557
+ public function import($source, $sourceCharset = null) {
558
+ // TODO charset conversions
559
+ $return = array();
560
+ if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
561
+ $source = array($source);
562
+ // if (is_array($source)) {
563
+ // foreach($source as $node) {
564
+ // if (is_string($node)) {
565
+ // // string markup
566
+ // $fake = $this->documentFragmentCreate($node, $sourceCharset);
567
+ // if ($fake === false)
568
+ // throw new Exception("Error loading documentFragment markup");
569
+ // else
570
+ // $return = array_merge($return,
571
+ // $this->import($fake->root->childNodes)
572
+ // );
573
+ // } else {
574
+ // $return[] = $this->document->importNode($node, true);
575
+ // }
576
+ // }
577
+ // return $return;
578
+ // } else {
579
+ // // string markup
580
+ // $fake = $this->documentFragmentCreate($source, $sourceCharset);
581
+ // if ($fake === false)
582
+ // throw new Exception("Error loading documentFragment markup");
583
+ // else
584
+ // return $this->import($fake->root->childNodes);
585
+ // }
586
+ if (is_array($source) || $source instanceof DOMNODELIST) {
587
+ // dom nodes
588
+ self::debug('Importing nodes to document');
589
+ foreach($source as $node)
590
+ $return[] = $this->document->importNode($node, true);
591
+ } else {
592
+ // string markup
593
+ $fake = $this->documentFragmentCreate($source, $sourceCharset);
594
+ if ($fake === false)
595
+ throw new Exception("Error loading documentFragment markup");
596
+ else
597
+ return $this->import($fake->root->childNodes);
598
+ }
599
+ return $return;
600
+ }
601
+ /**
602
+ * Creates new document fragment.
603
+ *
604
+ * @param $source
605
+ * @return DOMDocumentWrapper
606
+ */
607
+ protected function documentFragmentCreate($source, $charset = null) {
608
+ $fake = new DOMDocumentWrapper();
609
+ $fake->contentType = $this->contentType;
610
+ $fake->isXML = $this->isXML;
611
+ $fake->isHTML = $this->isHTML;
612
+ $fake->isXHTML = $this->isXHTML;
613
+ $fake->root = $fake->document;
614
+ if (! $charset)
615
+ $charset = $this->charset;
616
+ // $fake->documentCreate($this->charset);
617
+ if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
618
+ $source = array($source);
619
+ if (is_array($source) || $source instanceof DOMNODELIST) {
620
+ // dom nodes
621
+ // load fake document
622
+ if (! $this->documentFragmentLoadMarkup($fake, $charset))
623
+ return false;
624
+ $nodes = $fake->import($source);
625
+ foreach($nodes as $node)
626
+ $fake->root->appendChild($node);
627
+ } else {
628
+ // string markup
629
+ $this->documentFragmentLoadMarkup($fake, $charset, $source);
630
+ }
631
+ return $fake;
632
+ }
633
+ /**
634
+ *
635
+ * @param $document DOMDocumentWrapper
636
+ * @param $markup
637
+ * @return $document
638
+ */
639
+ private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) {
640
+ // TODO error handling
641
+ // TODO copy doctype
642
+ // tempolary turn off
643
+ $fragment->isDocumentFragment = false;
644
+ if ($fragment->isXML) {
645
+ if ($fragment->isXHTML) {
646
+ // add FAKE element to set default namespace
647
+ $fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?>'
648
+ .'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
649
+ .'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
650
+ .'<fake xmlns="http://www.w3.org/1999/xhtml">'.$markup.'</fake>');
651
+ $fragment->root = $fragment->document->firstChild->nextSibling;
652
+ } else {
653
+ $fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?><fake>'.$markup.'</fake>');
654
+ $fragment->root = $fragment->document->firstChild;
655
+ }
656
+ } else {
657
+ $markup2 = phpQuery::$defaultDoctype.'<html><head><meta http-equiv="Content-Type" content="text/html;charset='
658
+ .$charset.'"></head>';
659
+ $noBody = strpos($markup, '<body') === false;
660
+ if ($noBody)
661
+ $markup2 .= '<body>';
662
+ $markup2 .= $markup;
663
+ if ($noBody)
664
+ $markup2 .= '</body>';
665
+ $markup2 .= '</html>';
666
+ $fragment->loadMarkupHTML($markup2);
667
+ // TODO resolv body tag merging issue
668
+ $fragment->root = $noBody
669
+ ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling
670
+ : $fragment->document->firstChild->nextSibling->firstChild->nextSibling;
671
+ }
672
+ if (! $fragment->root)
673
+ return false;
674
+ $fragment->isDocumentFragment = true;
675
+ return true;
676
+ }
677
+ protected function documentFragmentToMarkup($fragment) {
678
+ phpQuery::debug('documentFragmentToMarkup');
679
+ $tmp = $fragment->isDocumentFragment;
680
+ $fragment->isDocumentFragment = false;
681
+ $markup = $fragment->markup();
682
+ if ($fragment->isXML) {
683
+ $markup = substr($markup, 0, strrpos($markup, '</fake>'));
684
+ if ($fragment->isXHTML) {
685
+ $markup = substr($markup, strpos($markup, '<fake')+43);
686
+ } else {
687
+ $markup = substr($markup, strpos($markup, '<fake>')+6);
688
+ }
689
+ } else {
690
+ $markup = substr($markup, strpos($markup, '<body>')+6);
691
+ $markup = substr($markup, 0, strrpos($markup, '</body>'));
692
+ }
693
+ $fragment->isDocumentFragment = $tmp;
694
+ if (phpQuery::$debug)
695
+ phpQuery::debug('documentFragmentToMarkup: '.substr($markup, 0, 150));
696
+ return $markup;
697
+ }
698
+ /**
699
+ * Return document markup, starting with optional $nodes as root.
700
+ *
701
+ * @param $nodes DOMNode|DOMNodeList
702
+ * @return string
703
+ */
704
+ public function markup($nodes = null, $innerMarkup = false) {
705
+ if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT)
706
+ $nodes = null;
707
+ if (isset($nodes)) {
708
+ $markup = '';
709
+ if (!is_array($nodes) && !($nodes instanceof DOMNODELIST) )
710
+ $nodes = array($nodes);
711
+ if ($this->isDocumentFragment && ! $innerMarkup)
712
+ foreach($nodes as $i => $node)
713
+ if ($node->isSameNode($this->root)) {
714
+ // var_dump($node);
715
+ $nodes = array_slice($nodes, 0, $i)
716
+ + phpQuery::DOMNodeListToArray($node->childNodes)
717
+ + array_slice($nodes, $i+1);
718
+ }
719
+ if ($this->isXML && ! $innerMarkup) {
720
+ self::debug("Getting outerXML with charset '{$this->charset}'");
721
+ // we need outerXML, so we can benefit from
722
+ // $node param support in saveXML()
723
+ foreach($nodes as $node)
724
+ $markup .= $this->document->saveXML($node);
725
+ } else {
726
+ $loop = array();
727
+ if ($innerMarkup)
728
+ foreach($nodes as $node) {
729
+ if ($node->childNodes)
730
+ foreach($node->childNodes as $child)
731
+ $loop[] = $child;
732
+ else
733
+ $loop[] = $node;
734
+ }
735
+ else
736
+ $loop = $nodes;
737
+ self::debug("Getting markup, moving selected nodes (".count($loop).") to new DocumentFragment");
738
+ $fake = $this->documentFragmentCreate($loop);
739
+ $markup = $this->documentFragmentToMarkup($fake);
740
+ }
741
+ if ($this->isXHTML) {
742
+ self::debug("Fixing XHTML");
743
+ $markup = self::markupFixXHTML($markup);
744
+ }
745
+ self::debug("Markup: ".substr($markup, 0, 250));
746
+ return $markup;
747
+ } else {
748
+ if ($this->isDocumentFragment) {
749
+ // documentFragment, html only...
750
+ self::debug("Getting markup, DocumentFragment detected");
751
+ // return $this->markup(
752
+ //// $this->document->getElementsByTagName('body')->item(0)
753
+ // $this->document->root, true
754
+ // );
755
+ $markup = $this->documentFragmentToMarkup($this);
756
+ // no need for markupFixXHTML, as it's done thought markup($nodes) method
757
+ return $markup;
758
+ } else {
759
+ self::debug("Getting markup (".($this->isXML?'XML':'HTML')."), final with charset '{$this->charset}'");
760
+ $markup = $this->isXML
761
+ ? $this->document->saveXML()
762
+ : $this->document->saveHTML();
763
+ if ($this->isXHTML) {
764
+ self::debug("Fixing XHTML");
765
+ $markup = self::markupFixXHTML($markup);
766
+ }
767
+ self::debug("Markup: ".substr($markup, 0, 250));
768
+ return $markup;
769
+ }
770
+ }
771
+ }
772
+ protected static function markupFixXHTML($markup) {
773
+ $markup = self::expandEmptyTag('script', $markup);
774
+ $markup = self::expandEmptyTag('select', $markup);
775
+ $markup = self::expandEmptyTag('textarea', $markup);
776
+ return $markup;
777
+ }
778
+ public static function debug($text) {
779
+ phpQuery::debug($text);
780
+ }
781
+ /**
782
+ * expandEmptyTag
783
+ *
784
+ * @param $tag
785
+ * @param $xml
786
+ * @return unknown_type
787
+ * @author mjaque at ilkebenson dot com
788
+ * @link http://php.net/manual/en/domdocument.savehtml.php#81256
789
+ */
790
+ public static function expandEmptyTag($tag, $xml){
791
+ $indice = 0;
792
+ while ($indice< strlen($xml)){
793
+ $pos = strpos($xml, "<$tag ", $indice);
794
+ if ($pos){
795
+ $posCierre = strpos($xml, ">", $pos);
796
+ if ($xml[$posCierre-1] == "/"){
797
+ $xml = substr_replace($xml, "></$tag>", $posCierre-1, 2);
798
+ }
799
+ $indice = $posCierre;
800
+ }
801
+ else break;
802
+ }
803
+ return $xml;
804
+ }
805
+ }
806
+
807
+ /**
808
+ * Event handling class.
809
+ *
810
+ * @author Tobiasz Cudnik
811
+ * @package phpQuery
812
+ * @static
813
+ */
814
+ abstract class phpQueryEvents {
815
+ /**
816
+ * Trigger a type of event on every matched element.
817
+ *
818
+ * @param DOMNode|phpQueryObject|string $document
819
+ * @param unknown_type $type
820
+ * @param unknown_type $data
821
+ *
822
+ * @TODO exclusive events (with !)
823
+ * @TODO global events (test)
824
+ * @TODO support more than event in $type (space-separated)
825
+ */
826
+ public static function trigger($document, $type, $data = array(), $node = null) {
827
+ // trigger: function(type, data, elem, donative, extra) {
828
+ $documentID = phpQuery::getDocumentID($document);
829
+ $namespace = null;
830
+ if (strpos($type, '.') !== false)
831
+ list($name, $namespace) = explode('.', $type);
832
+ else
833
+ $name = $type;
834
+ if (! $node) {
835
+ if (self::issetGlobal($documentID, $type)) {
836
+ $pq = phpQuery::getDocument($documentID);
837
+ // TODO check add($pq->document)
838
+ $pq->find('*')->add($pq->document)
839
+ ->trigger($type, $data);
840
+ }
841
+ } else {
842
+ if (isset($data[0]) && $data[0] instanceof DOMEvent) {
843
+ $event = $data[0];
844
+ $event->relatedTarget = $event->target;
845
+ $event->target = $node;
846
+ $data = array_slice($data, 1);
847
+ } else {
848
+ $event = new DOMEvent(array(
849
+ 'type' => $type,
850
+ 'target' => $node,
851
+ 'timeStamp' => time(),
852
+ ));
853
+ }
854
+ $i = 0;
855
+ while($node) {
856
+ // TODO whois
857
+ phpQuery::debug("Triggering ".($i?"bubbled ":'')."event '{$type}' on "
858
+ ."node \n");//.phpQueryObject::whois($node)."\n");
859
+ $event->currentTarget = $node;
860
+ $eventNode = self::getNode($documentID, $node);
861
+ if (isset($eventNode->eventHandlers)) {
862
+ foreach($eventNode->eventHandlers as $eventType => $handlers) {
863
+ $eventNamespace = null;
864
+ if (strpos($type, '.') !== false)
865
+ list($eventName, $eventNamespace) = explode('.', $eventType);
866
+ else
867
+ $eventName = $eventType;
868
+ if ($name != $eventName)
869
+ continue;
870
+ if ($namespace && $eventNamespace && $namespace != $eventNamespace)
871
+ continue;
872
+ foreach($handlers as $handler) {
873
+ phpQuery::debug("Calling event handler\n");
874
+ $event->data = $handler['data']
875
+ ? $handler['data']
876
+ : null;
877
+ $params = array_merge(array($event), $data);
878
+ $return = phpQuery::callbackRun($handler['callback'], $params);
879
+ if ($return === false) {
880
+ $event->bubbles = false;
881
+ }
882
+ }
883
+ }
884
+ }
885
+ // to bubble or not to bubble...
886
+ if (! $event->bubbles)
887
+ break;
888
+ $node = $node->parentNode;
889
+ $i++;
890
+ }
891
+ }
892
+ }
893
+ /**
894
+ * Binds a handler to one or more events (like click) for each matched element.
895
+ * Can also bind custom events.
896
+ *
897
+ * @param DOMNode|phpQueryObject|string $document
898
+ * @param unknown_type $type
899
+ * @param unknown_type $data Optional
900
+ * @param unknown_type $callback
901
+ *
902
+ * @TODO support '!' (exclusive) events
903
+ * @TODO support more than event in $type (space-separated)
904
+ * @TODO support binding to global events
905
+ */
906
+ public static function add($document, $node, $type, $data, $callback = null) {
907
+ phpQuery::debug("Binding '$type' event");
908
+ $documentID = phpQuery::getDocumentID($document);
909
+ // if (is_null($callback) && is_callable($data)) {
910
+ // $callback = $data;
911
+ // $data = null;
912
+ // }
913
+ $eventNode = self::getNode($documentID, $node);
914
+ if (! $eventNode)
915
+ $eventNode = self::setNode($documentID, $node);
916
+ if (!isset($eventNode->eventHandlers[$type]))
917
+ $eventNode->eventHandlers[$type] = array();
918
+ $eventNode->eventHandlers[$type][] = array(
919
+ 'callback' => $callback,
920
+ 'data' => $data,
921
+ );
922
+ }
923
+ /**
924
+ * Enter description here...
925
+ *
926
+ * @param DOMNode|phpQueryObject|string $document
927
+ * @param unknown_type $type
928
+ * @param unknown_type $callback
929
+ *
930
+ * @TODO namespace events
931
+ * @TODO support more than event in $type (space-separated)
932
+ */
933
+ public static function remove($document, $node, $type = null, $callback = null) {
934
+ $documentID = phpQuery::getDocumentID($document);
935
+ $eventNode = self::getNode($documentID, $node);
936
+ if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) {
937
+ if ($callback) {
938
+ foreach($eventNode->eventHandlers[$type] as $k => $handler)
939
+ if ($handler['callback'] == $callback)
940
+ unset($eventNode->eventHandlers[$type][$k]);
941
+ } else {
942
+ unset($eventNode->eventHandlers[$type]);
943
+ }
944
+ }
945
+ }
946
+ protected static function getNode($documentID, $node) {
947
+ foreach(phpQuery::$documents[$documentID]->eventsNodes as $eventNode) {
948
+ if ($node->isSameNode($eventNode))
949
+ return $eventNode;
950
+ }
951
+ }
952
+ protected static function setNode($documentID, $node) {
953
+ phpQuery::$documents[$documentID]->eventsNodes[] = $node;
954
+ return phpQuery::$documents[$documentID]->eventsNodes[
955
+ count(phpQuery::$documents[$documentID]->eventsNodes)-1
956
+ ];
957
+ }
958
+ protected static function issetGlobal($documentID, $type) {
959
+ return isset(phpQuery::$documents[$documentID])
960
+ ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal)
961
+ : false;
962
+ }
963
+ }
964
+
965
+
966
+ interface ICallbackNamed {
967
+ function hasName();
968
+ function getName();
969
+ }
970
+ /**
971
+ * Callback class introduces currying-like pattern.
972
+ *
973
+ * Example:
974
+ * function foo($param1, $param2, $param3) {
975
+ * var_dump($param1, $param2, $param3);
976
+ * }
977
+ * $fooCurried = new Callback('foo',
978
+ * 'param1 is now statically set',
979
+ * new CallbackParam, new CallbackParam
980
+ * );
981
+ * phpQuery::callbackRun($fooCurried,
982
+ * array('param2 value', 'param3 value'
983
+ * );
984
+ *
985
+ * Callback class is supported in all phpQuery methods which accepts callbacks.
986
+ *
987
+ * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures
988
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
989
+ *
990
+ * @TODO??? return fake forwarding function created via create_function
991
+ * @TODO honor paramStructure
992
+ */
993
+ class Callback
994
+ implements ICallbackNamed {
995
+ public $callback = null;
996
+ public $params = null;
997
+ protected $name;
998
+ public function __construct($callback, $param1 = null, $param2 = null,
999
+ $param3 = null) {
1000
+ $params = func_get_args();
1001
+ $params = array_slice($params, 1);
1002
+ if ($callback instanceof Callback) {
1003
+ // TODO implement recurention
1004
+ } else {
1005
+ $this->callback = $callback;
1006
+ $this->params = $params;
1007
+ }
1008
+ }
1009
+ public function getName() {
1010
+ return 'Callback: '.$this->name;
1011
+ }
1012
+ public function hasName() {
1013
+ return isset($this->name) && $this->name;
1014
+ }
1015
+ public function setName($name) {
1016
+ $this->name = $name;
1017
+ return $this;
1018
+ }
1019
+ // TODO test me
1020
+ // public function addParams() {
1021
+ // $params = func_get_args();
1022
+ // return new Callback($this->callback, $this->params+$params);
1023
+ // }
1024
+ }
1025
+ /**
1026
+ * Shorthand for new Callback(create_function(...), ...);
1027
+ *
1028
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1029
+ */
1030
+ class CallbackBody extends Callback {
1031
+ public function __construct($paramList, $code, $param1 = null, $param2 = null,
1032
+ $param3 = null) {
1033
+ $params = func_get_args();
1034
+ $params = array_slice($params, 2);
1035
+ $this->callback = create_function($paramList, $code);
1036
+ $this->params = $params;
1037
+ }
1038
+ }
1039
+ /**
1040
+ * Callback type which on execution returns reference passed during creation.
1041
+ *
1042
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1043
+ */
1044
+ class CallbackReturnReference extends Callback
1045
+ implements ICallbackNamed {
1046
+ protected $reference;
1047
+ public function __construct(&$reference, $name = null){
1048
+ $this->reference =& $reference;
1049
+ $this->callback = array($this, 'callback');
1050
+ }
1051
+ public function callback() {
1052
+ return $this->reference;
1053
+ }
1054
+ public function getName() {
1055
+ return 'Callback: '.$this->name;
1056
+ }
1057
+ public function hasName() {
1058
+ return isset($this->name) && $this->name;
1059
+ }
1060
+ }
1061
+ /**
1062
+ * Callback type which on execution returns value passed during creation.
1063
+ *
1064
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1065
+ */
1066
+ class CallbackReturnValue extends Callback
1067
+ implements ICallbackNamed {
1068
+ protected $value;
1069
+ protected $name;
1070
+ public function __construct($value, $name = null){
1071
+ $this->value =& $value;
1072
+ $this->name = $name;
1073
+ $this->callback = array($this, 'callback');
1074
+ }
1075
+ public function callback() {
1076
+ return $this->value;
1077
+ }
1078
+ public function __toString() {
1079
+ return $this->getName();
1080
+ }
1081
+ public function getName() {
1082
+ return 'Callback: '.$this->name;
1083
+ }
1084
+ public function hasName() {
1085
+ return isset($this->name) && $this->name;
1086
+ }
1087
+ }
1088
+ /**
1089
+ * CallbackParameterToReference can be used when we don't really want a callback,
1090
+ * only parameter passed to it. CallbackParameterToReference takes first
1091
+ * parameter's value and passes it to reference.
1092
+ *
1093
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1094
+ */
1095
+ class CallbackParameterToReference extends Callback {
1096
+ /**
1097
+ * @param $reference
1098
+ * @TODO implement $paramIndex;
1099
+ * param index choose which callback param will be passed to reference
1100
+ */
1101
+ public function __construct(&$reference){
1102
+ $this->callback =& $reference;
1103
+ }
1104
+ }
1105
+ //class CallbackReference extends Callback {
1106
+ // /**
1107
+ // *
1108
+ // * @param $reference
1109
+ // * @param $paramIndex
1110
+ // * @todo implement $paramIndex; param index choose which callback param will be passed to reference
1111
+ // */
1112
+ // public function __construct(&$reference, $name = null){
1113
+ // $this->callback =& $reference;
1114
+ // }
1115
+ //}
1116
+ class CallbackParam {}
1117
+
1118
+ /**
1119
+ * Class representing phpQuery objects.
1120
+ *
1121
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1122
+ * @package phpQuery
1123
+ * @method phpQueryObject clone() clone()
1124
+ * @method phpQueryObject empty() empty()
1125
+ * @method phpQueryObject next() next($selector = null)
1126
+ * @method phpQueryObject prev() prev($selector = null)
1127
+ * @property Int $length
1128
+ */
1129
+ class phpQueryObject
1130
+ implements Iterator, Countable, ArrayAccess {
1131
+ public $documentID = null;
1132
+ /**
1133
+ * DOMDocument class.
1134
+ *
1135
+ * @var DOMDocument
1136
+ */
1137
+ public $document = null;
1138
+ public $charset = null;
1139
+ /**
1140
+ *
1141
+ * @var DOMDocumentWrapper
1142
+ */
1143
+ public $documentWrapper = null;
1144
+ /**
1145
+ * XPath interface.
1146
+ *
1147
+ * @var DOMXPath
1148
+ */
1149
+ public $xpath = null;
1150
+ /**
1151
+ * Stack of selected elements.
1152
+ * @TODO refactor to ->nodes
1153
+ * @var array
1154
+ */
1155
+ public $elements = array();
1156
+ /**
1157
+ * @access private
1158
+ */
1159
+ protected $elementsBackup = array();
1160
+ /**
1161
+ * @access private
1162
+ */
1163
+ protected $previous = null;
1164
+ /**
1165
+ * @access private
1166
+ * @TODO deprecate
1167
+ */
1168
+ protected $root = array();
1169
+ /**
1170
+ * Indicated if doument is just a fragment (no <html> tag).
1171
+ *
1172
+ * Every document is realy a full document, so even documentFragments can
1173
+ * be queried against <html>, but getDocument(id)->htmlOuter() will return
1174
+ * only contents of <body>.
1175
+ *
1176
+ * @var bool
1177
+ */
1178
+ public $documentFragment = true;
1179
+ /**
1180
+ * Iterator interface helper
1181
+ * @access private
1182
+ */
1183
+ protected $elementsInterator = array();
1184
+ /**
1185
+ * Iterator interface helper
1186
+ * @access private
1187
+ */
1188
+ protected $valid = false;
1189
+ /**
1190
+ * Iterator interface helper
1191
+ * @access private
1192
+ */
1193
+ protected $current = null;
1194
+ /**
1195
+ * Enter description here...
1196
+ *
1197
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1198
+ */
1199
+ public function __construct($documentID) {
1200
+ // if ($documentID instanceof self)
1201
+ // var_dump($documentID->getDocumentID());
1202
+ $id = $documentID instanceof self
1203
+ ? $documentID->getDocumentID()
1204
+ : $documentID;
1205
+ // var_dump($id);
1206
+ if (! isset(phpQuery::$documents[$id] )) {
1207
+ // var_dump(phpQuery::$documents);
1208
+ throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first.");
1209
+ }
1210
+ $this->documentID = $id;
1211
+ $this->documentWrapper =& phpQuery::$documents[$id];
1212
+ $this->document =& $this->documentWrapper->document;
1213
+ $this->xpath =& $this->documentWrapper->xpath;
1214
+ $this->charset =& $this->documentWrapper->charset;
1215
+ $this->documentFragment =& $this->documentWrapper->isDocumentFragment;
1216
+ // TODO check $this->DOM->documentElement;
1217
+ // $this->root = $this->document->documentElement;
1218
+ $this->root =& $this->documentWrapper->root;
1219
+ // $this->toRoot();
1220
+ $this->elements = array($this->root);
1221
+ }
1222
+ /**
1223
+ *
1224
+ * @access private
1225
+ * @param $attr
1226
+ * @return unknown_type
1227
+ */
1228
+ public function __get($attr) {
1229
+ switch($attr) {
1230
+ // FIXME doesnt work at all ?
1231
+ case 'length':
1232
+ return $this->size();
1233
+ break;
1234
+ default:
1235
+ return $this->$attr;
1236
+ }
1237
+ }
1238
+ /**
1239
+ * Saves actual object to $var by reference.
1240
+ * Useful when need to break chain.
1241
+ * @param phpQueryObject $var
1242
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1243
+ */
1244
+ public function toReference(&$var) {
1245
+ return $var = $this;
1246
+ }
1247
+ public function documentFragment($state = null) {
1248
+ if ($state) {
1249
+ phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state;
1250
+ return $this;
1251
+ }
1252
+ return $this->documentFragment;
1253
+ }
1254
+ /**
1255
+ * @access private
1256
+ * @TODO documentWrapper
1257
+ */
1258
+ protected function isRoot( $node) {
1259
+ // return $node instanceof DOMDOCUMENT || $node->tagName == 'html';
1260
+ return $node instanceof DOMDOCUMENT
1261
+ || ($node instanceof DOMELEMENT && $node->tagName == 'html')
1262
+ || $this->root->isSameNode($node);
1263
+ }
1264
+ /**
1265
+ * @access private
1266
+ */
1267
+ protected function stackIsRoot() {
1268
+ return $this->size() == 1 && $this->isRoot($this->elements[0]);
1269
+ }
1270
+ /**
1271
+ * Enter description here...
1272
+ * NON JQUERY METHOD
1273
+ *
1274
+ * Watch out, it doesn't creates new instance, can be reverted with end().
1275
+ *
1276
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1277
+ */
1278
+ public function toRoot() {
1279
+ $this->elements = array($this->root);
1280
+ return $this;
1281
+ // return $this->newInstance(array($this->root));
1282
+ }
1283
+ /**
1284
+ * Saves object's DocumentID to $var by reference.
1285
+ * <code>
1286
+ * $myDocumentId;
1287
+ * phpQuery::newDocument('<div/>')
1288
+ * ->getDocumentIDRef($myDocumentId)
1289
+ * ->find('div')->...
1290
+ * </code>
1291
+ *
1292
+ * @param unknown_type $domId
1293
+ * @see phpQuery::newDocument
1294
+ * @see phpQuery::newDocumentFile
1295
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1296
+ */
1297
+ public function getDocumentIDRef(&$documentID) {
1298
+ $documentID = $this->getDocumentID();
1299
+ return $this;
1300
+ }
1301
+ /**
1302
+ * Returns object with stack set to document root.
1303
+ *
1304
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1305
+ */
1306
+ public function getDocument() {
1307
+ return phpQuery::getDocument($this->getDocumentID());
1308
+ }
1309
+ /**
1310
+ *
1311
+ * @return DOMDocument
1312
+ */
1313
+ public function getDOMDocument() {
1314
+ return $this->document;
1315
+ }
1316
+ /**
1317
+ * Get object's Document ID.
1318
+ *
1319
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1320
+ */
1321
+ public function getDocumentID() {
1322
+ return $this->documentID;
1323
+ }
1324
+ /**
1325
+ * Unloads whole document from memory.
1326
+ * CAUTION! None further operations will be possible on this document.
1327
+ * All objects refering to it will be useless.
1328
+ *
1329
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1330
+ */
1331
+ public function unloadDocument() {
1332
+ phpQuery::unloadDocuments($this->getDocumentID());
1333
+ }
1334
+ public function isHTML() {
1335
+ return $this->documentWrapper->isHTML;
1336
+ }
1337
+ public function isXHTML() {
1338
+ return $this->documentWrapper->isXHTML;
1339
+ }
1340
+ public function isXML() {
1341
+ return $this->documentWrapper->isXML;
1342
+ }
1343
+ /**
1344
+ * Enter description here...
1345
+ *
1346
+ * @link http://docs.jquery.com/Ajax/serialize
1347
+ * @return string
1348
+ */
1349
+ public function serialize() {
1350
+ return phpQuery::param($this->serializeArray());
1351
+ }
1352
+ /**
1353
+ * Enter description here...
1354
+ *
1355
+ * @link http://docs.jquery.com/Ajax/serializeArray
1356
+ * @return array
1357
+ */
1358
+ public function serializeArray($submit = null) {
1359
+ $source = $this->filter('form, input, select, textarea')
1360
+ ->find('input, select, textarea')
1361
+ ->andSelf()
1362
+ ->not('form');
1363
+ $return = array();
1364
+ // $source->dumpDie();
1365
+ foreach($source as $input) {
1366
+ $input = phpQuery::pq($input);
1367
+ if ($input->is('[disabled]'))
1368
+ continue;
1369
+ if (!$input->is('[name]'))
1370
+ continue;
1371
+ if ($input->is('[type=checkbox]') && !$input->is('[checked]'))
1372
+ continue;
1373
+ // jquery diff
1374
+ if ($submit && $input->is('[type=submit]')) {
1375
+ if ($submit instanceof DOMELEMENT && ! $input->elements[0]->isSameNode($submit))
1376
+ continue;
1377
+ else if (is_string($submit) && $input->attr('name') != $submit)
1378
+ continue;
1379
+ }
1380
+ $return[] = array(
1381
+ 'name' => $input->attr('name'),
1382
+ 'value' => $input->val(),
1383
+ );
1384
+ }
1385
+ return $return;
1386
+ }
1387
+ /**
1388
+ * @access private
1389
+ */
1390
+ protected function debug($in) {
1391
+ if (! phpQuery::$debug )
1392
+ return;
1393
+ print('<pre>');
1394
+ print_r($in);
1395
+ // file debug
1396
+ // file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
1397
+ // quite handy debug trace
1398
+ // if ( is_array($in))
1399
+ // print_r(array_slice(debug_backtrace(), 3));
1400
+ print("</pre>\n");
1401
+ }
1402
+ /**
1403
+ * @access private
1404
+ */
1405
+ protected function isRegexp($pattern) {
1406
+ return in_array(
1407
+ $pattern[ mb_strlen($pattern)-1 ],
1408
+ array('^','*','$')
1409
+ );
1410
+ }
1411
+ /**
1412
+ * Determines if $char is really a char.
1413
+ *
1414
+ * @param string $char
1415
+ * @return bool
1416
+ * @todo rewrite me to charcode range ! ;)
1417
+ * @access private
1418
+ */
1419
+ protected function isChar($char) {
1420
+ return extension_loaded('mbstring') && phpQuery::$mbstringSupport
1421
+ ? mb_eregi('\w', $char)
1422
+ : preg_match('@\w@', $char);
1423
+ }
1424
+ /**
1425
+ * @access private
1426
+ */
1427
+ protected function parseSelector($query) {
1428
+ // clean spaces
1429
+ // TODO include this inside parsing ?
1430
+ $query = trim(
1431
+ preg_replace('@\s+@', ' ',
1432
+ preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query)
1433
+ )
1434
+ );
1435
+ $queries = array(array());
1436
+ if (! $query)
1437
+ return $queries;
1438
+ $return =& $queries[0];
1439
+ $specialChars = array('>',' ');
1440
+ // $specialCharsMapping = array('/' => '>');
1441
+ $specialCharsMapping = array();
1442
+ $strlen = mb_strlen($query);
1443
+ $classChars = array('.', '-');
1444
+ $pseudoChars = array('-');
1445
+ $tagChars = array('*', '|', '-');
1446
+ // split multibyte string
1447
+ // http://code.google.com/p/phpquery/issues/detail?id=76
1448
+ $_query = array();
1449
+ for ($i=0; $i<$strlen; $i++)
1450
+ $_query[] = mb_substr($query, $i, 1);
1451
+ $query = $_query;
1452
+ // it works, but i dont like it...
1453
+ $i = 0;
1454
+ while( $i < $strlen) {
1455
+ $c = $query[$i];
1456
+ $tmp = '';
1457
+ // TAG
1458
+ if ($this->isChar($c) || in_array($c, $tagChars)) {
1459
+ while(isset($query[$i])
1460
+ && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))) {
1461
+ $tmp .= $query[$i];
1462
+ $i++;
1463
+ }
1464
+ $return[] = $tmp;
1465
+ // IDs
1466
+ } else if ( $c == '#') {
1467
+ $i++;
1468
+ while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) {
1469
+ $tmp .= $query[$i];
1470
+ $i++;
1471
+ }
1472
+ $return[] = '#'.$tmp;
1473
+ // SPECIAL CHARS
1474
+ } else if (in_array($c, $specialChars)) {
1475
+ $return[] = $c;
1476
+ $i++;
1477
+ // MAPPED SPECIAL MULTICHARS
1478
+ // } else if ( $c.$query[$i+1] == '//') {
1479
+ // $return[] = ' ';
1480
+ // $i = $i+2;
1481
+ // MAPPED SPECIAL CHARS
1482
+ } else if ( isset($specialCharsMapping[$c])) {
1483
+ $return[] = $specialCharsMapping[$c];
1484
+ $i++;
1485
+ // COMMA
1486
+ } else if ( $c == ',') {
1487
+ $queries[] = array();
1488
+ $return =& $queries[ count($queries)-1 ];
1489
+ $i++;
1490
+ while( isset($query[$i]) && $query[$i] == ' ')
1491
+ $i++;
1492
+ // CLASSES
1493
+ } else if ($c == '.') {
1494
+ while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) {
1495
+ $tmp .= $query[$i];
1496
+ $i++;
1497
+ }
1498
+ $return[] = $tmp;
1499
+ // ~ General Sibling Selector
1500
+ } else if ($c == '~') {
1501
+ $spaceAllowed = true;
1502
+ $tmp .= $query[$i++];
1503
+ while( isset($query[$i])
1504
+ && ($this->isChar($query[$i])
1505
+ || in_array($query[$i], $classChars)
1506
+ || $query[$i] == '*'
1507
+ || ($query[$i] == ' ' && $spaceAllowed)
1508
+ )) {
1509
+ if ($query[$i] != ' ')
1510
+ $spaceAllowed = false;
1511
+ $tmp .= $query[$i];
1512
+ $i++;
1513
+ }
1514
+ $return[] = $tmp;
1515
+ // + Adjacent sibling selectors
1516
+ } else if ($c == '+') {
1517
+ $spaceAllowed = true;
1518
+ $tmp .= $query[$i++];
1519
+ while( isset($query[$i])
1520
+ && ($this->isChar($query[$i])
1521
+ || in_array($query[$i], $classChars)
1522
+ || $query[$i] == '*'
1523
+ || ($spaceAllowed && $query[$i] == ' ')
1524
+ )) {
1525
+ if ($query[$i] != ' ')
1526
+ $spaceAllowed = false;
1527
+ $tmp .= $query[$i];
1528
+ $i++;
1529
+ }
1530
+ $return[] = $tmp;
1531
+ // ATTRS
1532
+ } else if ($c == '[') {
1533
+ $stack = 1;
1534
+ $tmp .= $c;
1535
+ while( isset($query[++$i])) {
1536
+ $tmp .= $query[$i];
1537
+ if ( $query[$i] == '[') {
1538
+ $stack++;
1539
+ } else if ( $query[$i] == ']') {
1540
+ $stack--;
1541
+ if (! $stack )
1542
+ break;
1543
+ }
1544
+ }
1545
+ $return[] = $tmp;
1546
+ $i++;
1547
+ // PSEUDO CLASSES
1548
+ } else if ($c == ':') {
1549
+ $stack = 1;
1550
+ $tmp .= $query[$i++];
1551
+ while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) {
1552
+ $tmp .= $query[$i];
1553
+ $i++;
1554
+ }
1555
+ // with arguments ?
1556
+ if ( isset($query[$i]) && $query[$i] == '(') {
1557
+ $tmp .= $query[$i];
1558
+ $stack = 1;
1559
+ while( isset($query[++$i])) {
1560
+ $tmp .= $query[$i];
1561
+ if ( $query[$i] == '(') {
1562
+ $stack++;
1563
+ } else if ( $query[$i] == ')') {
1564
+ $stack--;
1565
+ if (! $stack )
1566
+ break;
1567
+ }
1568
+ }
1569
+ $return[] = $tmp;
1570
+ $i++;
1571
+ } else {
1572
+ $return[] = $tmp;
1573
+ }
1574
+ } else {
1575
+ $i++;
1576
+ }
1577
+ }
1578
+ foreach($queries as $k => $q) {
1579
+ if (isset($q[0])) {
1580
+ if (isset($q[0][0]) && $q[0][0] == ':')
1581
+ array_unshift($queries[$k], '*');
1582
+ if ($q[0] != '>')
1583
+ array_unshift($queries[$k], ' ');
1584
+ }
1585
+ }
1586
+ return $queries;
1587
+ }
1588
+ /**
1589
+ * Return matched DOM nodes.
1590
+ *
1591
+ * @param int $index
1592
+ * @return array|DOMElement Single DOMElement or array of DOMElement.
1593
+ */
1594
+ public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1595
+ $return = isset($index)
1596
+ ? (isset($this->elements[$index]) ? $this->elements[$index] : null)
1597
+ : $this->elements;
1598
+ // pass thou callbacks
1599
+ $args = func_get_args();
1600
+ $args = array_slice($args, 1);
1601
+ foreach($args as $callback) {
1602
+ if (is_array($return))
1603
+ foreach($return as $k => $v)
1604
+ $return[$k] = phpQuery::callbackRun($callback, array($v));
1605
+ else
1606
+ $return = phpQuery::callbackRun($callback, array($return));
1607
+ }
1608
+ return $return;
1609
+ }
1610
+ /**
1611
+ * Return matched DOM nodes.
1612
+ * jQuery difference.
1613
+ *
1614
+ * @param int $index
1615
+ * @return array|string Returns string if $index != null
1616
+ * @todo implement callbacks
1617
+ * @todo return only arrays ?
1618
+ * @todo maybe other name...
1619
+ */
1620
+ public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1621
+ if ($index)
1622
+ $return = $this->eq($index)->text();
1623
+ else {
1624
+ $return = array();
1625
+ for($i = 0; $i < $this->size(); $i++) {
1626
+ $return[] = $this->eq($i)->text();
1627
+ }
1628
+ }
1629
+ // pass thou callbacks
1630
+ $args = func_get_args();
1631
+ $args = array_slice($args, 1);
1632
+ foreach($args as $callback) {
1633
+ $return = phpQuery::callbackRun($callback, array($return));
1634
+ }
1635
+ return $return;
1636
+ }
1637
+ /**
1638
+ * Return matched DOM nodes.
1639
+ * jQuery difference.
1640
+ *
1641
+ * @param int $index
1642
+ * @return array|string Returns string if $index != null
1643
+ * @todo implement callbacks
1644
+ * @todo return only arrays ?
1645
+ * @todo maybe other name...
1646
+ */
1647
+ public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1648
+ if ($index)
1649
+ $return = $this->eq($index)->text();
1650
+ else {
1651
+ $return = array();
1652
+ for($i = 0; $i < $this->size(); $i++) {
1653
+ $return[] = $this->eq($i)->text();
1654
+ }
1655
+ // pass thou callbacks
1656
+ $args = func_get_args();
1657
+ $args = array_slice($args, 1);
1658
+ }
1659
+ foreach($args as $callback) {
1660
+ if (is_array($return))
1661
+ foreach($return as $k => $v)
1662
+ $return[$k] = phpQuery::callbackRun($callback, array($v));
1663
+ else
1664
+ $return = phpQuery::callbackRun($callback, array($return));
1665
+ }
1666
+ return $return;
1667
+ }
1668
+ /**
1669
+ * Returns new instance of actual class.
1670
+ *
1671
+ * @param array $newStack Optional. Will replace old stack with new and move old one to history.c
1672
+ */
1673
+ public function newInstance($newStack = null) {
1674
+ $class = get_class($this);
1675
+ // support inheritance by passing old object to overloaded constructor
1676
+ $new = $class != 'phpQuery'
1677
+ ? new $class($this, $this->getDocumentID())
1678
+ : new phpQueryObject($this->getDocumentID());
1679
+ $new->previous = $this;
1680
+ if (is_null($newStack)) {
1681
+ $new->elements = $this->elements;
1682
+ if ($this->elementsBackup)
1683
+ $this->elements = $this->elementsBackup;
1684
+ } else if (is_string($newStack)) {
1685
+ $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack();
1686
+ } else {
1687
+ $new->elements = $newStack;
1688
+ }
1689
+ return $new;
1690
+ }
1691
+ /**
1692
+ * Enter description here...
1693
+ *
1694
+ * In the future, when PHP will support XLS 2.0, then we would do that this way:
1695
+ * contains(tokenize(@class, '\s'), "something")
1696
+ * @param unknown_type $class
1697
+ * @param unknown_type $node
1698
+ * @return boolean
1699
+ * @access private
1700
+ */
1701
+ protected function matchClasses($class, $node) {
1702
+ // multi-class
1703
+ if ( mb_strpos($class, '.', 1)) {
1704
+ $classes = explode('.', substr($class, 1));
1705
+ $classesCount = count( $classes );
1706
+ $nodeClasses = explode(' ', $node->getAttribute('class') );
1707
+ $nodeClassesCount = count( $nodeClasses );
1708
+ if ( $classesCount > $nodeClassesCount )
1709
+ return false;
1710
+ $diff = count(
1711
+ array_diff(
1712
+ $classes,
1713
+ $nodeClasses
1714
+ )
1715
+ );
1716
+ if (! $diff )
1717
+ return true;
1718
+ // single-class
1719
+ } else {
1720
+ return in_array(
1721
+ // strip leading dot from class name
1722
+ substr($class, 1),
1723
+ // get classes for element as array
1724
+ explode(' ', $node->getAttribute('class') )
1725
+ );
1726
+ }
1727
+ }
1728
+ /**
1729
+ * @access private
1730
+ */
1731
+ protected function runQuery($XQuery, $selector = null, $compare = null) {
1732
+ if ($compare && ! method_exists($this, $compare))
1733
+ return false;
1734
+ $stack = array();
1735
+ if (! $this->elements)
1736
+ $this->debug('Stack empty, skipping...');
1737
+ // var_dump($this->elements[0]->nodeType);
1738
+ // element, document
1739
+ foreach($this->stack(array(1, 9, 13)) as $k => $stackNode) {
1740
+ $detachAfter = false;
1741
+ // to work on detached nodes we need temporary place them somewhere
1742
+ // thats because context xpath queries sucks ;]
1743
+ $testNode = $stackNode;
1744
+ while ($testNode) {
1745
+ if (! $testNode->parentNode && ! $this->isRoot($testNode)) {
1746
+ $this->root->appendChild($testNode);
1747
+ $detachAfter = $testNode;
1748
+ break;
1749
+ }
1750
+ $testNode = isset($testNode->parentNode)
1751
+ ? $testNode->parentNode
1752
+ : null;
1753
+ }
1754
+ // XXX tmp ?
1755
+ $xpath = $this->documentWrapper->isXHTML
1756
+ ? $this->getNodeXpath($stackNode, 'html')
1757
+ : $this->getNodeXpath($stackNode);
1758
+ // FIXME pseudoclasses-only query, support XML
1759
+ $query = $XQuery == '//' && $xpath == '/html[1]'
1760
+ ? '//*'
1761
+ : $xpath.$XQuery;
1762
+ $this->debug("XPATH: {$query}");
1763
+ // run query, get elements
1764
+ $nodes = $this->xpath->query($query);
1765
+ $this->debug("QUERY FETCHED");
1766
+ if (! $nodes->length )
1767
+ $this->debug('Nothing found');
1768
+ $debug = array();
1769
+ foreach($nodes as $node) {
1770
+ $matched = false;
1771
+ if ( $compare) {
1772
+ phpQuery::$debug ?
1773
+ $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()")
1774
+ : null;
1775
+ $phpQueryDebug = phpQuery::$debug;
1776
+ phpQuery::$debug = false;
1777
+ // TODO ??? use phpQuery::callbackRun()
1778
+ if (call_user_func_array(array($this, $compare), array($selector, $node)))
1779
+ $matched = true;
1780
+ phpQuery::$debug = $phpQueryDebug;
1781
+ } else {
1782
+ $matched = true;
1783
+ }
1784
+ if ( $matched) {
1785
+ if (phpQuery::$debug)
1786
+ $debug[] = $this->whois( $node );
1787
+ $stack[] = $node;
1788
+ }
1789
+ }
1790
+ if (phpQuery::$debug) {
1791
+ $this->debug("Matched ".count($debug).": ".implode(', ', $debug));
1792
+ }
1793
+ if ($detachAfter)
1794
+ $this->root->removeChild($detachAfter);
1795
+ }
1796
+ $this->elements = $stack;
1797
+ }
1798
+ /**
1799
+ * Enter description here...
1800
+ *
1801
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1802
+ */
1803
+ public function find($selectors, $context = null, $noHistory = false) {
1804
+ if (!$noHistory)
1805
+ // backup last stack /for end()/
1806
+ $this->elementsBackup = $this->elements;
1807
+ // allow to define context
1808
+ // TODO combine code below with phpQuery::pq() context guessing code
1809
+ // as generic function
1810
+ if ($context) {
1811
+ if (! is_array($context) && $context instanceof DOMELEMENT)
1812
+ $this->elements = array($context);
1813
+ else if (is_array($context)) {
1814
+ $this->elements = array();
1815
+ foreach ($context as $c)
1816
+ if ($c instanceof DOMELEMENT)
1817
+ $this->elements[] = $c;
1818
+ } else if ( $context instanceof self )
1819
+ $this->elements = $context->elements;
1820
+ }
1821
+ $queries = $this->parseSelector($selectors);
1822
+ $this->debug(array('FIND', $selectors, $queries));
1823
+ $XQuery = '';
1824
+ // remember stack state because of multi-queries
1825
+ $oldStack = $this->elements;
1826
+ // here we will be keeping found elements
1827
+ $stack = array();
1828
+ foreach($queries as $selector) {
1829
+ $this->elements = $oldStack;
1830
+ $delimiterBefore = false;
1831
+ foreach($selector as $s) {
1832
+ // TAG
1833
+ $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport
1834
+ ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*'
1835
+ : preg_match('@^[\w|\||-]+$@', $s) || $s == '*';
1836
+ if ($isTag) {
1837
+ if ($this->isXML()) {
1838
+ // namespace support
1839
+ if (mb_strpos($s, '|') !== false) {
1840
+ $ns = $tag = null;
1841
+ list($ns, $tag) = explode('|', $s);
1842
+ $XQuery .= "$ns:$tag";
1843
+ } else if ($s == '*') {
1844
+ $XQuery .= "*";
1845
+ } else {
1846
+ $XQuery .= "*[local-name()='$s']";
1847
+ }
1848
+ } else {
1849
+ $XQuery .= $s;
1850
+ }
1851
+ // ID
1852
+ } else if ($s[0] == '#') {
1853
+ if ($delimiterBefore)
1854
+ $XQuery .= '*';
1855
+ $XQuery .= "[@id='".substr($s, 1)."']";
1856
+ // ATTRIBUTES
1857
+ } else if ($s[0] == '[') {
1858
+ if ($delimiterBefore)
1859
+ $XQuery .= '*';
1860
+ // strip side brackets
1861
+ $attr = trim($s, '][');
1862
+ $execute = false;
1863
+ // attr with specifed value
1864
+ if (mb_strpos($s, '=')) {
1865
+ $value = null;
1866
+ list($attr, $value) = explode('=', $attr);
1867
+ $value = trim($value, "'\"");
1868
+ if ($this->isRegexp($attr)) {
1869
+ // cut regexp character
1870
+ $attr = substr($attr, 0, -1);
1871
+ $execute = true;
1872
+ $XQuery .= "[@{$attr}]";
1873
+ } else {
1874
+ $XQuery .= "[@{$attr}='{$value}']";
1875
+ }
1876
+ // attr without specified value
1877
+ } else {
1878
+ $XQuery .= "[@{$attr}]";
1879
+ }
1880
+ if ($execute) {
1881
+ $this->runQuery($XQuery, $s, 'is');
1882
+ $XQuery = '';
1883
+ if (! $this->length())
1884
+ break;
1885
+ }
1886
+ // CLASSES
1887
+ } else if ($s[0] == '.') {
1888
+ // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]");
1889
+ // thx wizDom ;)
1890
+ if ($delimiterBefore)
1891
+ $XQuery .= '*';
1892
+ $XQuery .= '[@class]';
1893
+ $this->runQuery($XQuery, $s, 'matchClasses');
1894
+ $XQuery = '';
1895
+ if (! $this->length() )
1896
+ break;
1897
+ // ~ General Sibling Selector
1898
+ } else if ($s[0] == '~') {
1899
+ $this->runQuery($XQuery);
1900
+ $XQuery = '';
1901
+ $this->elements = $this
1902
+ ->siblings(
1903
+ substr($s, 1)
1904
+ )->elements;
1905
+ if (! $this->length() )
1906
+ break;
1907
+ // + Adjacent sibling selectors
1908
+ } else if ($s[0] == '+') {
1909
+ // TODO /following-sibling::
1910
+ $this->runQuery($XQuery);
1911
+ $XQuery = '';
1912
+ $subSelector = substr($s, 1);
1913
+ $subElements = $this->elements;
1914
+ $this->elements = array();
1915
+ foreach($subElements as $node) {
1916
+ // search first DOMElement sibling
1917
+ $test = $node->nextSibling;
1918
+ while($test && ! ($test instanceof DOMELEMENT))
1919
+ $test = $test->nextSibling;
1920
+ if ($test && $this->is($subSelector, $test))
1921
+ $this->elements[] = $test;
1922
+ }
1923
+ if (! $this->length() )
1924
+ break;
1925
+ // PSEUDO CLASSES
1926
+ } else if ($s[0] == ':') {
1927
+ // TODO optimization for :first :last
1928
+ if ($XQuery) {
1929
+ $this->runQuery($XQuery);
1930
+ $XQuery = '';
1931
+ }
1932
+ if (! $this->length())
1933
+ break;
1934
+ $this->pseudoClasses($s);
1935
+ if (! $this->length())
1936
+ break;
1937
+ // DIRECT DESCENDANDS
1938
+ } else if ($s == '>') {
1939
+ $XQuery .= '/';
1940
+ $delimiterBefore = 2;
1941
+ // ALL DESCENDANDS
1942
+ } else if ($s == ' ') {
1943
+ $XQuery .= '//';
1944
+ $delimiterBefore = 2;
1945
+ // ERRORS
1946
+ } else {
1947
+ phpQuery::debug("Unrecognized token '$s'");
1948
+ }
1949
+ $delimiterBefore = $delimiterBefore === 2;
1950
+ }
1951
+ // run query if any
1952
+ if ($XQuery && $XQuery != '//') {
1953
+ $this->runQuery($XQuery);
1954
+ $XQuery = '';
1955
+ }
1956
+ foreach($this->elements as $node)
1957
+ if (! $this->elementsContainsNode($node, $stack))
1958
+ $stack[] = $node;
1959
+ }
1960
+ $this->elements = $stack;
1961
+ return $this->newInstance();
1962
+ }
1963
+ /**
1964
+ * @todo create API for classes with pseudoselectors
1965
+ * @access private
1966
+ */
1967
+ protected function pseudoClasses($class) {
1968
+ // TODO clean args parsing ?
1969
+ $class = ltrim($class, ':');
1970
+ $haveArgs = mb_strpos($class, '(');
1971
+ if ($haveArgs !== false) {
1972
+ $args = substr($class, $haveArgs+1, -1);
1973
+ $class = substr($class, 0, $haveArgs);
1974
+ }
1975
+ switch($class) {
1976
+ case 'even':
1977
+ case 'odd':
1978
+ $stack = array();
1979
+ foreach($this->elements as $i => $node) {
1980
+ if ($class == 'even' && ($i%2) == 0)
1981
+ $stack[] = $node;
1982
+ else if ( $class == 'odd' && $i % 2 )
1983
+ $stack[] = $node;
1984
+ }
1985
+ $this->elements = $stack;
1986
+ break;
1987
+ case 'eq':
1988
+ $k = intval($args);
1989
+ $this->elements = isset( $this->elements[$k] )
1990
+ ? array( $this->elements[$k] )
1991
+ : array();
1992
+ break;
1993
+ case 'gt':
1994
+ $this->elements = array_slice($this->elements, $args+1);
1995
+ break;
1996
+ case 'lt':
1997
+ $this->elements = array_slice($this->elements, 0, $args+1);
1998
+ break;
1999
+ case 'first':
2000
+ if (isset($this->elements[0]))
2001
+ $this->elements = array($this->elements[0]);
2002
+ break;
2003
+ case 'last':
2004
+ if ($this->elements)
2005
+ $this->elements = array($this->elements[count($this->elements)-1]);
2006
+ break;
2007
+ /*case 'parent':
2008
+ $stack = array();
2009
+ foreach($this->elements as $node) {
2010
+ if ( $node->childNodes->length )
2011
+ $stack[] = $node;
2012
+ }
2013
+ $this->elements = $stack;
2014
+ break;*/
2015
+ case 'contains':
2016
+ $text = trim($args, "\"'");
2017
+ $stack = array();
2018
+ foreach($this->elements as $node) {
2019
+ if (mb_stripos($node->textContent, $text) === false)
2020
+ continue;
2021
+ $stack[] = $node;
2022
+ }
2023
+ $this->elements = $stack;
2024
+ break;
2025
+ case 'not':
2026
+ $selector = self::unQuote($args);
2027
+ $this->elements = $this->not($selector)->stack();
2028
+ break;
2029
+ case 'slice':
2030
+ // TODO jQuery difference ?
2031
+ $args = explode(',',
2032
+ str_replace(', ', ',', trim($args, "\"'"))
2033
+ );
2034
+ $start = $args[0];
2035
+ $end = isset($args[1])
2036
+ ? $args[1]
2037
+ : null;
2038
+ if ($end > 0)
2039
+ $end = $end-$start;
2040
+ $this->elements = array_slice($this->elements, $start, $end);
2041
+ break;
2042
+ case 'has':
2043
+ $selector = trim($args, "\"'");
2044
+ $stack = array();
2045
+ foreach($this->stack(1) as $el) {
2046
+ if ($this->find($selector, $el, true)->length)
2047
+ $stack[] = $el;
2048
+ }
2049
+ $this->elements = $stack;
2050
+ break;
2051
+ case 'submit':
2052
+ case 'reset':
2053
+ $this->elements = phpQuery::merge(
2054
+ $this->map(array($this, 'is'),
2055
+ "input[type=$class]", new CallbackParam()
2056
+ ),
2057
+ $this->map(array($this, 'is'),
2058
+ "button[type=$class]", new CallbackParam()
2059
+ )
2060
+ );
2061
+ break;
2062
+ // $stack = array();
2063
+ // foreach($this->elements as $node)
2064
+ // if ($node->is('input[type=submit]') || $node->is('button[type=submit]'))
2065
+ // $stack[] = $el;
2066
+ // $this->elements = $stack;
2067
+ case 'input':
2068
+ $this->elements = $this->map(
2069
+ array($this, 'is'),
2070
+ 'input', new CallbackParam()
2071
+ )->elements;
2072
+ break;
2073
+ case 'password':
2074
+ case 'checkbox':
2075
+ case 'radio':
2076
+ case 'hidden':
2077
+ case 'image':
2078
+ case 'file':
2079
+ $this->elements = $this->map(
2080
+ array($this, 'is'),
2081
+ "input[type=$class]", new CallbackParam()
2082
+ )->elements;
2083
+ break;
2084
+ case 'parent':
2085
+ $this->elements = $this->map(
2086
+ create_function('$node', '
2087
+ return $node instanceof DOMELEMENT && $node->childNodes->length
2088
+ ? $node : null;')
2089
+ )->elements;
2090
+ break;
2091
+ case 'empty':
2092
+ $this->elements = $this->map(
2093
+ create_function('$node', '
2094
+ return $node instanceof DOMELEMENT && $node->childNodes->length
2095
+ ? null : $node;')
2096
+ )->elements;
2097
+ break;
2098
+ case 'disabled':
2099
+ case 'selected':
2100
+ case 'checked':
2101
+ $this->elements = $this->map(
2102
+ array($this, 'is'),
2103
+ "[$class]", new CallbackParam()
2104
+ )->elements;
2105
+ break;
2106
+ case 'enabled':
2107
+ $this->elements = $this->map(
2108
+ create_function('$node', '
2109
+ return pq($node)->not(":disabled") ? $node : null;')
2110
+ )->elements;
2111
+ break;
2112
+ case 'header':
2113
+ $this->elements = $this->map(
2114
+ create_function('$node',
2115
+ '$isHeader = isset($node->tagName) && in_array($node->tagName, array(
2116
+ "h1", "h2", "h3", "h4", "h5", "h6", "h7"
2117
+ ));
2118
+ return $isHeader
2119
+ ? $node
2120
+ : null;')
2121
+ )->elements;
2122
+ // $this->elements = $this->map(
2123
+ // create_function('$node', '$node = pq($node);
2124
+ // return $node->is("h1")
2125
+ // || $node->is("h2")
2126
+ // || $node->is("h3")
2127
+ // || $node->is("h4")
2128
+ // || $node->is("h5")
2129
+ // || $node->is("h6")
2130
+ // || $node->is("h7")
2131
+ // ? $node
2132
+ // : null;')
2133
+ // )->elements;
2134
+ break;
2135
+ case 'only-child':
2136
+ $this->elements = $this->map(
2137
+ create_function('$node',
2138
+ 'return pq($node)->siblings()->size() == 0 ? $node : null;')
2139
+ )->elements;
2140
+ break;
2141
+ case 'first-child':
2142
+ $this->elements = $this->map(
2143
+ create_function('$node', 'return pq($node)->prevAll()->size() == 0 ? $node : null;')
2144
+ )->elements;
2145
+ break;
2146
+ case 'last-child':
2147
+ $this->elements = $this->map(
2148
+ create_function('$node', 'return pq($node)->nextAll()->size() == 0 ? $node : null;')
2149
+ )->elements;
2150
+ break;
2151
+ case 'nth-child':
2152
+ $param = trim($args, "\"'");
2153
+ if (! $param)
2154
+ break;
2155
+ // nth-child(n+b) to nth-child(1n+b)
2156
+ if ($param{0} == 'n')
2157
+ $param = '1'.$param;
2158
+ // :nth-child(index/even/odd/equation)
2159
+ if ($param == 'even' || $param == 'odd')
2160
+ $mapped = $this->map(
2161
+ create_function('$node, $param',
2162
+ '$index = pq($node)->prevAll()->size()+1;
2163
+ if ($param == "even" && ($index%2) == 0)
2164
+ return $node;
2165
+ else if ($param == "odd" && $index%2 == 1)
2166
+ return $node;
2167
+ else
2168
+ return null;'),
2169
+ new CallbackParam(), $param
2170
+ );
2171
+ else if (mb_strlen($param) > 1 && $param{1} == 'n')
2172
+ // an+b
2173
+ $mapped = $this->map(
2174
+ create_function('$node, $param',
2175
+ '$prevs = pq($node)->prevAll()->size();
2176
+ $index = 1+$prevs;
2177
+ $b = mb_strlen($param) > 3
2178
+ ? $param{3}
2179
+ : 0;
2180
+ $a = $param{0};
2181
+ if ($b && $param{2} == "-")
2182
+ $b = -$b;
2183
+ if ($a > 0) {
2184
+ return ($index-$b)%$a == 0
2185
+ ? $node
2186
+ : null;
2187
+ phpQuery::debug($a."*".floor($index/$a)."+$b-1 == ".($a*floor($index/$a)+$b-1)." ?= $prevs");
2188
+ return $a*floor($index/$a)+$b-1 == $prevs
2189
+ ? $node
2190
+ : null;
2191
+ } else if ($a == 0)
2192
+ return $index == $b
2193
+ ? $node
2194
+ : null;
2195
+ else
2196
+ // negative value
2197
+ return $index <= $b
2198
+ ? $node
2199
+ : null;
2200
+ // if (! $b)
2201
+ // return $index%$a == 0
2202
+ // ? $node
2203
+ // : null;
2204
+ // else
2205
+ // return ($index-$b)%$a == 0
2206
+ // ? $node
2207
+ // : null;
2208
+ '),
2209
+ new CallbackParam(), $param
2210
+ );
2211
+ else
2212
+ // index
2213
+ $mapped = $this->map(
2214
+ create_function('$node, $index',
2215
+ '$prevs = pq($node)->prevAll()->size();
2216
+ if ($prevs && $prevs == $index-1)
2217
+ return $node;
2218
+ else if (! $prevs && $index == 1)
2219
+ return $node;
2220
+ else
2221
+ return null;'),
2222
+ new CallbackParam(), $param
2223
+ );
2224
+ $this->elements = $mapped->elements;
2225
+ break;
2226
+ default:
2227
+ $this->debug("Unknown pseudoclass '{$class}', skipping...");
2228
+ }
2229
+ }
2230
+ /**
2231
+ * @access private
2232
+ */
2233
+ protected function __pseudoClassParam($paramsString) {
2234
+ // TODO;
2235
+ }
2236
+ /**
2237
+ * Enter description here...
2238
+ *
2239
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2240
+ */
2241
+ public function is($selector, $nodes = null) {
2242
+ phpQuery::debug(array("Is:", $selector));
2243
+ if (! $selector)
2244
+ return false;
2245
+ $oldStack = $this->elements;
2246
+ $returnArray = false;
2247
+ if ($nodes && is_array($nodes)) {
2248
+ $this->elements = $nodes;
2249
+ } else if ($nodes)
2250
+ $this->elements = array($nodes);
2251
+ $this->filter($selector, true);
2252
+ $stack = $this->elements;
2253
+ $this->elements = $oldStack;
2254
+ if ($nodes)
2255
+ return $stack ? $stack : null;
2256
+ return (bool)count($stack);
2257
+ }
2258
+ /**
2259
+ * Enter description here...
2260
+ * jQuery difference.
2261
+ *
2262
+ * Callback:
2263
+ * - $index int
2264
+ * - $node DOMNode
2265
+ *
2266
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2267
+ * @link http://docs.jquery.com/Traversing/filter
2268
+ */
2269
+ public function filterCallback($callback, $_skipHistory = false) {
2270
+ if (! $_skipHistory) {
2271
+ $this->elementsBackup = $this->elements;
2272
+ $this->debug("Filtering by callback");
2273
+ }
2274
+ $newStack = array();
2275
+ foreach($this->elements as $index => $node) {
2276
+ $result = phpQuery::callbackRun($callback, array($index, $node));
2277
+ if (is_null($result) || (! is_null($result) && $result))
2278
+ $newStack[] = $node;
2279
+ }
2280
+ $this->elements = $newStack;
2281
+ return $_skipHistory
2282
+ ? $this
2283
+ : $this->newInstance();
2284
+ }
2285
+ /**
2286
+ * Enter description here...
2287
+ *
2288
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2289
+ * @link http://docs.jquery.com/Traversing/filter
2290
+ */
2291
+ public function filter($selectors, $_skipHistory = false) {
2292
+ if ($selectors instanceof Callback OR $selectors instanceof Closure)
2293
+ return $this->filterCallback($selectors, $_skipHistory);
2294
+ if (! $_skipHistory)
2295
+ $this->elementsBackup = $this->elements;
2296
+ $notSimpleSelector = array(' ', '>', '~', '+', '/');
2297
+ if (! is_array($selectors))
2298
+ $selectors = $this->parseSelector($selectors);
2299
+ if (! $_skipHistory)
2300
+ $this->debug(array("Filtering:", $selectors));
2301
+ $finalStack = array();
2302
+ foreach($selectors as $selector) {
2303
+ $stack = array();
2304
+ if (! $selector)
2305
+ break;
2306
+ // avoid first space or /
2307
+ if (in_array($selector[0], $notSimpleSelector))
2308
+ $selector = array_slice($selector, 1);
2309
+ // PER NODE selector chunks
2310
+ foreach($this->stack() as $node) {
2311
+ $break = false;
2312
+ foreach($selector as $s) {
2313
+ if (!($node instanceof DOMELEMENT)) {
2314
+ // all besides DOMElement
2315
+ if ( $s[0] == '[') {
2316
+ $attr = trim($s, '[]');
2317
+ if ( mb_strpos($attr, '=')) {
2318
+ list( $attr, $val ) = explode('=', $attr);
2319
+ if ($attr == 'nodeType' && $node->nodeType != $val)
2320
+ $break = true;
2321
+ }
2322
+ } else
2323
+ $break = true;
2324
+ } else {
2325
+ // DOMElement only
2326
+ // ID
2327
+ if ( $s[0] == '#') {
2328
+ if ( $node->getAttribute('id') != substr($s, 1) )
2329
+ $break = true;
2330
+ // CLASSES
2331
+ } else if ( $s[0] == '.') {
2332
+ if (! $this->matchClasses( $s, $node ) )
2333
+ $break = true;
2334
+ // ATTRS
2335
+ } else if ( $s[0] == '[') {
2336
+ // strip side brackets
2337
+ $attr = trim($s, '[]');
2338
+ if (mb_strpos($attr, '=')) {
2339
+ list($attr, $val) = explode('=', $attr);
2340
+ $val = self::unQuote($val);
2341
+ if ($attr == 'nodeType') {
2342
+ if ($val != $node->nodeType)
2343
+ $break = true;
2344
+ } else if ($this->isRegexp($attr)) {
2345
+ $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport
2346
+ ? quotemeta(trim($val, '"\''))
2347
+ : preg_quote(trim($val, '"\''), '@');
2348
+ // switch last character
2349
+ switch( substr($attr, -1)) {
2350
+ // quotemeta used insted of preg_quote
2351
+ // http://code.google.com/p/phpquery/issues/detail?id=76
2352
+ case '^':
2353
+ $pattern = '^'.$val;
2354
+ break;
2355
+ case '*':
2356
+ $pattern = '.*'.$val.'.*';
2357
+ break;
2358
+ case '$':
2359
+ $pattern = '.*'.$val.'$';
2360
+ break;
2361
+ }
2362
+ // cut last character
2363
+ $attr = substr($attr, 0, -1);
2364
+ $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport
2365
+ ? mb_ereg_match($pattern, $node->getAttribute($attr))
2366
+ : preg_match("@{$pattern}@", $node->getAttribute($attr));
2367
+ if (! $isMatch)
2368
+ $break = true;
2369
+ } else if ($node->getAttribute($attr) != $val)
2370
+ $break = true;
2371
+ } else if (! $node->hasAttribute($attr))
2372
+ $break = true;
2373
+ // PSEUDO CLASSES
2374
+ } else if ( $s[0] == ':') {
2375
+ // skip
2376
+ // TAG
2377
+ } else if (trim($s)) {
2378
+ if ($s != '*') {
2379
+ // TODO namespaces
2380
+ if (isset($node->tagName)) {
2381
+ if ($node->tagName != $s)
2382
+ $break = true;
2383
+ } else if ($s == 'html' && ! $this->isRoot($node))
2384
+ $break = true;
2385
+ }
2386
+ // AVOID NON-SIMPLE SELECTORS
2387
+ } else if (in_array($s, $notSimpleSelector)) {
2388
+ $break = true;
2389
+ $this->debug(array('Skipping non simple selector', $selector));
2390
+ }
2391
+ }
2392
+ if ($break)
2393
+ break;
2394
+ }
2395
+ // if element passed all chunks of selector - add it to new stack
2396
+ if (! $break )
2397
+ $stack[] = $node;
2398
+ }
2399
+ $tmpStack = $this->elements;
2400
+ $this->elements = $stack;
2401
+ // PER ALL NODES selector chunks
2402
+ foreach($selector as $s)
2403
+ // PSEUDO CLASSES
2404
+ if ($s[0] == ':')
2405
+ $this->pseudoClasses($s);
2406
+ foreach($this->elements as $node)
2407
+ // XXX it should be merged without duplicates
2408
+ // but jQuery doesnt do that
2409
+ $finalStack[] = $node;
2410
+ $this->elements = $tmpStack;
2411
+ }
2412
+ $this->elements = $finalStack;
2413
+ if ($_skipHistory) {
2414
+ return $this;
2415
+ } else {
2416
+ $this->debug("Stack length after filter(): ".count($finalStack));
2417
+ return $this->newInstance();
2418
+ }
2419
+ }
2420
+ /**
2421
+ *
2422
+ * @param $value
2423
+ * @return unknown_type
2424
+ * @TODO implement in all methods using passed parameters
2425
+ */
2426
+ protected static function unQuote($value) {
2427
+ return $value[0] == '\'' || $value[0] == '"'
2428
+ ? substr($value, 1, -1)
2429
+ : $value;
2430
+ }
2431
+ /**
2432
+ * Enter description here...
2433
+ *
2434
+ * @link http://docs.jquery.com/Ajax/load
2435
+ * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2436
+ * @todo Support $selector
2437
+ */
2438
+ public function load($url, $data = null, $callback = null) {
2439
+ if ($data && ! is_array($data)) {
2440
+ $callback = $data;
2441
+ $data = null;
2442
+ }
2443
+ if (mb_strpos($url, ' ') !== false) {
2444
+ $matches = null;
2445
+ if (extension_loaded('mbstring') && phpQuery::$mbstringSupport)
2446
+ mb_ereg('^([^ ]+) (.*)$', $url, $matches);
2447
+ else
2448
+ preg_match('^([^ ]+) (.*)$', $url, $matches);
2449
+ $url = $matches[1];
2450
+ $selector = $matches[2];
2451
+ // FIXME this sucks, pass as callback param
2452
+ $this->_loadSelector = $selector;
2453
+ }
2454
+ $ajax = array(
2455
+ 'url' => $url,
2456
+ 'type' => $data ? 'POST' : 'GET',
2457
+ 'data' => $data,
2458
+ 'complete' => $callback,
2459
+ 'success' => array($this, '__loadSuccess')
2460
+ );
2461
+ phpQuery::ajax($ajax);
2462
+ return $this;
2463
+ }
2464
+ /**
2465
+ * @access private
2466
+ * @param $html
2467
+ * @return unknown_type
2468
+ */
2469
+ public function __loadSuccess($html) {
2470
+ if ($this->_loadSelector) {
2471
+ $html = phpQuery::newDocument($html)->find($this->_loadSelector);
2472
+ unset($this->_loadSelector);
2473
+ }
2474
+ foreach($this->stack(1) as $node) {
2475
+ phpQuery::pq($node, $this->getDocumentID())
2476
+ ->markup($html);
2477
+ }
2478
+ }
2479
+ /**
2480
+ * Enter description here...
2481
+ *
2482
+ * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2483
+ * @todo
2484
+ */
2485
+ public function css() {
2486
+ // TODO
2487
+ return $this;
2488
+ }
2489
+ /**
2490
+ * @todo
2491
+ *
2492
+ */
2493
+ public function show(){
2494
+ // TODO
2495
+ return $this;
2496
+ }
2497
+ /**
2498
+ * @todo
2499
+ *
2500
+ */
2501
+ public function hide(){
2502
+ // TODO
2503
+ return $this;
2504
+ }
2505
+ /**
2506
+ * Trigger a type of event on every matched element.
2507
+ *
2508
+ * @param unknown_type $type
2509
+ * @param unknown_type $data
2510
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2511
+ * @TODO support more than event in $type (space-separated)
2512
+ */
2513
+ public function trigger($type, $data = array()) {
2514
+ foreach($this->elements as $node)
2515
+ phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node);
2516
+ return $this;
2517
+ }
2518
+ /**
2519
+ * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions.
2520
+ *
2521
+ * @param unknown_type $type
2522
+ * @param unknown_type $data
2523
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2524
+ * @TODO
2525
+ */
2526
+ public function triggerHandler($type, $data = array()) {
2527
+ // TODO;
2528
+ }
2529
+ /**
2530
+ * Binds a handler to one or more events (like click) for each matched element.
2531
+ * Can also bind custom events.
2532
+ *
2533
+ * @param unknown_type $type
2534
+ * @param unknown_type $data Optional
2535
+ * @param unknown_type $callback
2536
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2537
+ * @TODO support '!' (exclusive) events
2538
+ * @TODO support more than event in $type (space-separated)
2539
+ */
2540
+ public function bind($type, $data, $callback = null) {
2541
+ // TODO check if $data is callable, not using is_callable
2542
+ if (! isset($callback)) {
2543
+ $callback = $data;
2544
+ $data = null;
2545
+ }
2546
+ foreach($this->elements as $node)
2547
+ phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback);
2548
+ return $this;
2549
+ }
2550
+ /**
2551
+ * Enter description here...
2552
+ *
2553
+ * @param unknown_type $type
2554
+ * @param unknown_type $callback
2555
+ * @return unknown
2556
+ * @TODO namespace events
2557
+ * @TODO support more than event in $type (space-separated)
2558
+ */
2559
+ public function unbind($type = null, $callback = null) {
2560
+ foreach($this->elements as $node)
2561
+ phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback);
2562
+ return $this;
2563
+ }
2564
+ /**
2565
+ * Enter description here...
2566
+ *
2567
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2568
+ */
2569
+ public function change($callback = null) {
2570
+ if ($callback)
2571
+ return $this->bind('change', $callback);
2572
+ return $this->trigger('change');
2573
+ }
2574
+ /**
2575
+ * Enter description here...
2576
+ *
2577
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2578
+ */
2579
+ public function submit($callback = null) {
2580
+ if ($callback)
2581
+ return $this->bind('submit', $callback);
2582
+ return $this->trigger('submit');
2583
+ }
2584
+ /**
2585
+ * Enter description here...
2586
+ *
2587
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2588
+ */
2589
+ public function click($callback = null) {
2590
+ if ($callback)
2591
+ return $this->bind('click', $callback);
2592
+ return $this->trigger('click');
2593
+ }
2594
+ /**
2595
+ * Enter description here...
2596
+ *
2597
+ * @param String|phpQuery
2598
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2599
+ */
2600
+ public function wrapAllOld($wrapper) {
2601
+ $wrapper = pq($wrapper)->_clone();
2602
+ if (! $wrapper->length() || ! $this->length() )
2603
+ return $this;
2604
+ $wrapper->insertBefore($this->elements[0]);
2605
+ $deepest = $wrapper->elements[0];
2606
+ while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
2607
+ $deepest = $deepest->firstChild;
2608
+ pq($deepest)->append($this);
2609
+ return $this;
2610
+ }
2611
+ /**
2612
+ * Enter description here...
2613
+ *
2614
+ * TODO testme...
2615
+ * @param String|phpQuery
2616
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2617
+ */
2618
+ public function wrapAll($wrapper) {
2619
+ if (! $this->length())
2620
+ return $this;
2621
+ return phpQuery::pq($wrapper, $this->getDocumentID())
2622
+ ->clone()
2623
+ ->insertBefore($this->get(0))
2624
+ ->map(array($this, '___wrapAllCallback'))
2625
+ ->append($this);
2626
+ }
2627
+ /**
2628
+ *
2629
+ * @param $node
2630
+ * @return unknown_type
2631
+ * @access private
2632
+ */
2633
+ public function ___wrapAllCallback($node) {
2634
+ $deepest = $node;
2635
+ while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
2636
+ $deepest = $deepest->firstChild;
2637
+ return $deepest;
2638
+ }
2639
+ /**
2640
+ * Enter description here...
2641
+ * NON JQUERY METHOD
2642
+ *
2643
+ * @param String|phpQuery
2644
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2645
+ */
2646
+ public function wrapAllPHP($codeBefore, $codeAfter) {
2647
+ return $this
2648
+ ->slice(0, 1)
2649
+ ->beforePHP($codeBefore)
2650
+ ->end()
2651
+ ->slice(-1)
2652
+ ->afterPHP($codeAfter)
2653
+ ->end();
2654
+ }
2655
+ /**
2656
+ * Enter description here...
2657
+ *
2658
+ * @param String|phpQuery
2659
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2660
+ */
2661
+ public function wrap($wrapper) {
2662
+ foreach($this->stack() as $node)
2663
+ phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper);
2664
+ return $this;
2665
+ }
2666
+ /**
2667
+ * Enter description here...
2668
+ *
2669
+ * @param String|phpQuery
2670
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2671
+ */
2672
+ public function wrapPHP($codeBefore, $codeAfter) {
2673
+ foreach($this->stack() as $node)
2674
+ phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter);
2675
+ return $this;
2676
+ }
2677
+ /**
2678
+ * Enter description here...
2679
+ *
2680
+ * @param String|phpQuery
2681
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2682
+ */
2683
+ public function wrapInner($wrapper) {
2684
+ foreach($this->stack() as $node)
2685
+ phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper);
2686
+ return $this;
2687
+ }
2688
+ /**
2689
+ * Enter description here...
2690
+ *
2691
+ * @param String|phpQuery
2692
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2693
+ */
2694
+ public function wrapInnerPHP($codeBefore, $codeAfter) {
2695
+ foreach($this->stack(1) as $node)
2696
+ phpQuery::pq($node, $this->getDocumentID())->contents()
2697
+ ->wrapAllPHP($codeBefore, $codeAfter);
2698
+ return $this;
2699
+ }
2700
+ /**
2701
+ * Enter description here...
2702
+ *
2703
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2704
+ * @testme Support for text nodes
2705
+ */
2706
+ public function contents() {
2707
+ $stack = array();
2708
+ foreach($this->stack(1) as $el) {
2709
+ // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56
2710
+ // if (! isset($el->childNodes))
2711
+ // continue;
2712
+ foreach($el->childNodes as $node) {
2713
+ $stack[] = $node;
2714
+ }
2715
+ }
2716
+ return $this->newInstance($stack);
2717
+ }
2718
+ /**
2719
+ * Enter description here...
2720
+ *
2721
+ * jQuery difference.
2722
+ *
2723
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2724
+ */
2725
+ public function contentsUnwrap() {
2726
+ foreach($this->stack(1) as $node) {
2727
+ if (! $node->parentNode )
2728
+ continue;
2729
+ $childNodes = array();
2730
+ // any modification in DOM tree breaks childNodes iteration, so cache them first
2731
+ foreach($node->childNodes as $chNode )
2732
+ $childNodes[] = $chNode;
2733
+ foreach($childNodes as $chNode )
2734
+ // $node->parentNode->appendChild($chNode);
2735
+ $node->parentNode->insertBefore($chNode, $node);
2736
+ $node->parentNode->removeChild($node);
2737
+ }
2738
+ return $this;
2739
+ }
2740
+ /**
2741
+ * Enter description here...
2742
+ *
2743
+ * jQuery difference.
2744
+ */
2745
+ public function switchWith($markup) {
2746
+ $markup = pq($markup, $this->getDocumentID());
2747
+ $content = null;
2748
+ foreach($this->stack(1) as $node) {
2749
+ pq($node)
2750
+ ->contents()->toReference($content)->end()
2751
+ ->replaceWith($markup->clone()->append($content));
2752
+ }
2753
+ return $this;
2754
+ }
2755
+ /**
2756
+ * Enter description here...
2757
+ *
2758
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2759
+ */
2760
+ public function eq($num) {
2761
+ $oldStack = $this->elements;
2762
+ $this->elementsBackup = $this->elements;
2763
+ $this->elements = array();
2764
+ if ( isset($oldStack[$num]) )
2765
+ $this->elements[] = $oldStack[$num];
2766
+ return $this->newInstance();
2767
+ }
2768
+ /**
2769
+ * Enter description here...
2770
+ *
2771
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2772
+ */
2773
+ public function size() {
2774
+ return count($this->elements);
2775
+ }
2776
+ /**
2777
+ * Enter description here...
2778
+ *
2779
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2780
+ * @deprecated Use length as attribute
2781
+ */
2782
+ public function length() {
2783
+ return $this->size();
2784
+ }
2785
+ public function count() {
2786
+ return $this->size();
2787
+ }
2788
+ /**
2789
+ * Enter description here...
2790
+ *
2791
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2792
+ * @todo $level
2793
+ */
2794
+ public function end($level = 1) {
2795
+ // $this->elements = array_pop( $this->history );
2796
+ // return $this;
2797
+ // $this->previous->DOM = $this->DOM;
2798
+ // $this->previous->XPath = $this->XPath;
2799
+ return $this->previous
2800
+ ? $this->previous
2801
+ : $this;
2802
+ }
2803
+ /**
2804
+ * Enter description here...
2805
+ * Normal use ->clone() .
2806
+ *
2807
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2808
+ * @access private
2809
+ */
2810
+ public function _clone() {
2811
+ $newStack = array();
2812
+ //pr(array('copy... ', $this->whois()));
2813
+ //$this->dumpHistory('copy');
2814
+ $this->elementsBackup = $this->elements;
2815
+ foreach($this->elements as $node) {
2816
+ $newStack[] = $node->cloneNode(true);
2817
+ }
2818
+ $this->elements = $newStack;
2819
+ return $this->newInstance();
2820
+ }
2821
+ /**
2822
+ * Enter description here...
2823
+ *
2824
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2825
+ */
2826
+ public function replaceWithPHP($code) {
2827
+ return $this->replaceWith(phpQuery::php($code));
2828
+ }
2829
+ /**
2830
+ * Enter description here...
2831
+ *
2832
+ * @param String|phpQuery $content
2833
+ * @link http://docs.jquery.com/Manipulation/replaceWith#content
2834
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2835
+ */
2836
+ public function replaceWith($content) {
2837
+ return $this->after($content)->remove();
2838
+ }
2839
+ /**
2840
+ * Enter description here...
2841
+ *
2842
+ * @param String $selector
2843
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2844
+ * @todo this works ?
2845
+ */
2846
+ public function replaceAll($selector) {
2847
+ foreach(phpQuery::pq($selector, $this->getDocumentID()) as $node)
2848
+ phpQuery::pq($node, $this->getDocumentID())
2849
+ ->after($this->_clone())
2850
+ ->remove();
2851
+ return $this;
2852
+ }
2853
+ /**
2854
+ * Enter description here...
2855
+ *
2856
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2857
+ */
2858
+ public function remove($selector = null) {
2859
+ $loop = $selector
2860
+ ? $this->filter($selector)->elements
2861
+ : $this->elements;
2862
+ foreach($loop as $node) {
2863
+ if (! $node->parentNode )
2864
+ continue;
2865
+ if (isset($node->tagName))
2866
+ $this->debug("Removing '{$node->tagName}'");
2867
+ $node->parentNode->removeChild($node);
2868
+ // Mutation event
2869
+ $event = new DOMEvent(array(
2870
+ 'target' => $node,
2871
+ 'type' => 'DOMNodeRemoved'
2872
+ ));
2873
+ phpQueryEvents::trigger($this->getDocumentID(),
2874
+ $event->type, array($event), $node
2875
+ );
2876
+ }
2877
+ return $this;
2878
+ }
2879
+ protected function markupEvents($newMarkup, $oldMarkup, $node) {
2880
+ if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) {
2881
+ $event = new DOMEvent(array(
2882
+ 'target' => $node,
2883
+ 'type' => 'change'
2884
+ ));
2885
+ phpQueryEvents::trigger($this->getDocumentID(),
2886
+ $event->type, array($event), $node
2887
+ );
2888
+ }
2889
+ }
2890
+ /**
2891
+ * jQuey difference
2892
+ *
2893
+ * @param $markup
2894
+ * @return unknown_type
2895
+ * @TODO trigger change event for textarea
2896
+ */
2897
+ public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2898
+ $args = func_get_args();
2899
+ if ($this->documentWrapper->isXML)
2900
+ return call_user_func_array(array($this, 'xml'), $args);
2901
+ else
2902
+ return call_user_func_array(array($this, 'html'), $args);
2903
+ }
2904
+ /**
2905
+ * jQuey difference
2906
+ *
2907
+ * @param $markup
2908
+ * @return unknown_type
2909
+ */
2910
+ public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2911
+ $args = func_get_args();
2912
+ if ($this->documentWrapper->isXML)
2913
+ return call_user_func_array(array($this, 'xmlOuter'), $args);
2914
+ else
2915
+ return call_user_func_array(array($this, 'htmlOuter'), $args);
2916
+ }
2917
+ /**
2918
+ * Enter description here...
2919
+ *
2920
+ * @param unknown_type $html
2921
+ * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2922
+ * @TODO force html result
2923
+ */
2924
+ public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2925
+ if (isset($html)) {
2926
+ // INSERT
2927
+ $nodes = $this->documentWrapper->import($html);
2928
+ $this->empty();
2929
+ foreach($this->stack(1) as $alreadyAdded => $node) {
2930
+ // for now, limit events for textarea
2931
+ if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
2932
+ $oldHtml = pq($node, $this->getDocumentID())->markup();
2933
+ foreach($nodes as $newNode) {
2934
+ $node->appendChild($alreadyAdded
2935
+ ? $newNode->cloneNode(true)
2936
+ : $newNode
2937
+ );
2938
+ }
2939
+ // for now, limit events for textarea
2940
+ if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
2941
+ $this->markupEvents($html, $oldHtml, $node);
2942
+ }
2943
+ return $this;
2944
+ } else {
2945
+ // FETCH
2946
+ $return = $this->documentWrapper->markup($this->elements, true);
2947
+ $args = func_get_args();
2948
+ foreach(array_slice($args, 1) as $callback) {
2949
+ $return = phpQuery::callbackRun($callback, array($return));
2950
+ }
2951
+ return $return;
2952
+ }
2953
+ }
2954
+ /**
2955
+ * @TODO force xml result
2956
+ */
2957
+ public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2958
+ $args = func_get_args();
2959
+ return call_user_func_array(array($this, 'html'), $args);
2960
+ }
2961
+ /**
2962
+ * Enter description here...
2963
+ * @TODO force html result
2964
+ *
2965
+ * @return String
2966
+ */
2967
+ public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2968
+ $markup = $this->documentWrapper->markup($this->elements);
2969
+ // pass thou callbacks
2970
+ $args = func_get_args();
2971
+ foreach($args as $callback) {
2972
+ $markup = phpQuery::callbackRun($callback, array($markup));
2973
+ }
2974
+ return $markup;
2975
+ }
2976
+ /**
2977
+ * @TODO force xml result
2978
+ */
2979
+ public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2980
+ $args = func_get_args();
2981
+ return call_user_func_array(array($this, 'htmlOuter'), $args);
2982
+ }
2983
+ public function __toString() {
2984
+ return $this->markupOuter();
2985
+ }
2986
+ /**
2987
+ * Just like html(), but returns markup with VALID (dangerous) PHP tags.
2988
+ *
2989
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2990
+ * @todo support returning markup with PHP tags when called without param
2991
+ */
2992
+ public function php($code = null) {
2993
+ return $this->markupPHP($code);
2994
+ }
2995
+ /**
2996
+ * Enter description here...
2997
+ *
2998
+ * @param $code
2999
+ * @return unknown_type
3000
+ */
3001
+ public function markupPHP($code = null) {
3002
+ return isset($code)
3003
+ ? $this->markup(phpQuery::php($code))
3004
+ : phpQuery::markupToPHP($this->markup());
3005
+ }
3006
+ /**
3007
+ * Enter description here...
3008
+ *
3009
+ * @param $code
3010
+ * @return unknown_type
3011
+ */
3012
+ public function markupOuterPHP() {
3013
+ return phpQuery::markupToPHP($this->markupOuter());
3014
+ }
3015
+ /**
3016
+ * Enter description here...
3017
+ *
3018
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3019
+ */
3020
+ public function children($selector = null) {
3021
+ $stack = array();
3022
+ foreach($this->stack(1) as $node) {
3023
+ // foreach($node->getElementsByTagName('*') as $newNode) {
3024
+ foreach($node->childNodes as $newNode) {
3025
+ if ($newNode->nodeType != 1)
3026
+ continue;
3027
+ if ($selector && ! $this->is($selector, $newNode))
3028
+ continue;
3029
+ if ($this->elementsContainsNode($newNode, $stack))
3030
+ continue;
3031
+ $stack[] = $newNode;
3032
+ }
3033
+ }
3034
+ $this->elementsBackup = $this->elements;
3035
+ $this->elements = $stack;
3036
+ return $this->newInstance();
3037
+ }
3038
+ /**
3039
+ * Enter description here...
3040
+ *
3041
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3042
+ */
3043
+ public function ancestors($selector = null) {
3044
+ return $this->children( $selector );
3045
+ }
3046
+ /**
3047
+ * Enter description here...
3048
+ *
3049
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3050
+ */
3051
+ public function append( $content) {
3052
+ return $this->insert($content, __FUNCTION__);
3053
+ }
3054
+ /**
3055
+ * Enter description here...
3056
+ *
3057
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3058
+ */
3059
+ public function appendPHP( $content) {
3060
+ return $this->insert("<php><!-- {$content} --></php>", 'append');
3061
+ }
3062
+ /**
3063
+ * Enter description here...
3064
+ *
3065
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3066
+ */
3067
+ public function appendTo( $seletor) {
3068
+ return $this->insert($seletor, __FUNCTION__);
3069
+ }
3070
+ /**
3071
+ * Enter description here...
3072
+ *
3073
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3074
+ */
3075
+ public function prepend( $content) {
3076
+ return $this->insert($content, __FUNCTION__);
3077
+ }
3078
+ /**
3079
+ * Enter description here...
3080
+ *
3081
+ * @todo accept many arguments, which are joined, arrays maybe also
3082
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3083
+ */
3084
+ public function prependPHP( $content) {
3085
+ return $this->insert("<php><!-- {$content} --></php>", 'prepend');
3086
+ }
3087
+ /**
3088
+ * Enter description here...
3089
+ *
3090
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3091
+ */
3092
+ public function prependTo( $seletor) {
3093
+ return $this->insert($seletor, __FUNCTION__);
3094
+ }
3095
+ /**
3096
+ * Enter description here...
3097
+ *
3098
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3099
+ */
3100
+ public function before($content) {
3101
+ return $this->insert($content, __FUNCTION__);
3102
+ }
3103
+ /**
3104
+ * Enter description here...
3105
+ *
3106
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3107
+ */
3108
+ public function beforePHP( $content) {
3109
+ return $this->insert("<php><!-- {$content} --></php>", 'before');
3110
+ }
3111
+ /**
3112
+ * Enter description here...
3113
+ *
3114
+ * @param String|phpQuery
3115
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3116
+ */
3117
+ public function insertBefore( $seletor) {
3118
+ return $this->insert($seletor, __FUNCTION__);
3119
+ }
3120
+ /**
3121
+ * Enter description here...
3122
+ *
3123
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3124
+ */
3125
+ public function after( $content) {
3126
+ return $this->insert($content, __FUNCTION__);
3127
+ }
3128
+ /**
3129
+ * Enter description here...
3130
+ *
3131
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3132
+ */
3133
+ public function afterPHP( $content) {
3134
+ return $this->insert("<php><!-- {$content} --></php>", 'after');
3135
+ }
3136
+ /**
3137
+ * Enter description here...
3138
+ *
3139
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3140
+ */
3141
+ public function insertAfter( $seletor) {
3142
+ return $this->insert($seletor, __FUNCTION__);
3143
+ }
3144
+ /**
3145
+ * Internal insert method. Don't use it.
3146
+ *
3147
+ * @param unknown_type $target
3148
+ * @param unknown_type $type
3149
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3150
+ * @access private
3151
+ */
3152
+ public function insert($target, $type) {
3153
+ $this->debug("Inserting data with '{$type}'");
3154
+ $to = false;
3155
+ switch( $type) {
3156
+ case 'appendTo':
3157
+ case 'prependTo':
3158
+ case 'insertBefore':
3159
+ case 'insertAfter':
3160
+ $to = true;
3161
+ }
3162
+ switch(gettype($target)) {
3163
+ case 'string':
3164
+ $insertFrom = $insertTo = array();
3165
+ if ($to) {
3166
+ // INSERT TO
3167
+ $insertFrom = $this->elements;
3168
+ if (phpQuery::isMarkup($target)) {
3169
+ // $target is new markup, import it
3170
+ $insertTo = $this->documentWrapper->import($target);
3171
+ // insert into selected element
3172
+ } else {
3173
+ // $tagret is a selector
3174
+ $thisStack = $this->elements;
3175
+ $this->toRoot();
3176
+ $insertTo = $this->find($target)->elements;
3177
+ $this->elements = $thisStack;
3178
+ }
3179
+ } else {
3180
+ // INSERT FROM
3181
+ $insertTo = $this->elements;
3182
+ $insertFrom = $this->documentWrapper->import($target);
3183
+ }
3184
+ break;
3185
+ case 'object':
3186
+ $insertFrom = $insertTo = array();
3187
+ // phpQuery
3188
+ if ($target instanceof self) {
3189
+ if ($to) {
3190
+ $insertTo = $target->elements;
3191
+ if ($this->documentFragment && $this->stackIsRoot())
3192
+ // get all body children
3193
+ // $loop = $this->find('body > *')->elements;
3194
+ // TODO test it, test it hard...
3195
+ // $loop = $this->newInstance($this->root)->find('> *')->elements;
3196
+ $loop = $this->root->childNodes;
3197
+ else
3198
+ $loop = $this->elements;
3199
+ // import nodes if needed
3200
+ $insertFrom = $this->getDocumentID() == $target->getDocumentID()
3201
+ ? $loop
3202
+ : $target->documentWrapper->import($loop);
3203
+ } else {
3204
+ $insertTo = $this->elements;
3205
+ if ( $target->documentFragment && $target->stackIsRoot() )
3206
+ // get all body children
3207
+ // $loop = $target->find('body > *')->elements;
3208
+ $loop = $target->root->childNodes;
3209
+ else
3210
+ $loop = $target->elements;
3211
+ // import nodes if needed
3212
+ $insertFrom = $this->getDocumentID() == $target->getDocumentID()
3213
+ ? $loop
3214
+ : $this->documentWrapper->import($loop);
3215
+ }
3216
+ // DOMNODE
3217
+ } elseif ($target instanceof DOMNODE) {
3218
+ // import node if needed
3219
+ // if ( $target->ownerDocument != $this->DOM )
3220
+ // $target = $this->DOM->importNode($target, true);
3221
+ if ( $to) {
3222
+ $insertTo = array($target);
3223
+ if ($this->documentFragment && $this->stackIsRoot())
3224
+ // get all body children
3225
+ $loop = $this->root->childNodes;
3226
+ // $loop = $this->find('body > *')->elements;
3227
+ else
3228
+ $loop = $this->elements;
3229
+ foreach($loop as $fromNode)
3230
+ // import nodes if needed
3231
+ $insertFrom[] = ! $fromNode->ownerDocument->isSameNode($target->ownerDocument)
3232
+ ? $target->ownerDocument->importNode($fromNode, true)
3233
+ : $fromNode;
3234
+ } else {
3235
+ // import node if needed
3236
+ if (! $target->ownerDocument->isSameNode($this->document))
3237
+ $target = $this->document->importNode($target, true);
3238
+ $insertTo = $this->elements;
3239
+ $insertFrom[] = $target;
3240
+ }
3241
+ }
3242
+ break;
3243
+ }
3244
+ phpQuery::debug("From ".count($insertFrom)."; To ".count($insertTo)." nodes");
3245
+ foreach($insertTo as $insertNumber => $toNode) {
3246
+ // we need static relative elements in some cases
3247
+ switch( $type) {
3248
+ case 'prependTo':
3249
+ case 'prepend':
3250
+ $firstChild = $toNode->firstChild;
3251
+ break;
3252
+ case 'insertAfter':
3253
+ case 'after':
3254
+ $nextSibling = $toNode->nextSibling;
3255
+ break;
3256
+ }
3257
+ foreach($insertFrom as $fromNode) {
3258
+ // clone if inserted already before
3259
+ $insert = $insertNumber
3260
+ ? $fromNode->cloneNode(true)
3261
+ : $fromNode;
3262
+ switch($type) {
3263
+ case 'appendTo':
3264
+ case 'append':
3265
+ // $toNode->insertBefore(
3266
+ // $fromNode,
3267
+ // $toNode->lastChild->nextSibling
3268
+ // );
3269
+ $toNode->appendChild($insert);
3270
+ $eventTarget = $insert;
3271
+ break;
3272
+ case 'prependTo':
3273
+ case 'prepend':
3274
+ $toNode->insertBefore(
3275
+ $insert,
3276
+ $firstChild
3277
+ );
3278
+ break;
3279
+ case 'insertBefore':
3280
+ case 'before':
3281
+ if (! $toNode->parentNode)
3282
+ throw new Exception("No parentNode, can't do {$type}()");
3283
+ else
3284
+ $toNode->parentNode->insertBefore(
3285
+ $insert,
3286
+ $toNode
3287
+ );
3288
+ break;
3289
+ case 'insertAfter':
3290
+ case 'after':
3291
+ if (! $toNode->parentNode)
3292
+ throw new Exception("No parentNode, can't do {$type}()");
3293
+ else
3294
+ $toNode->parentNode->insertBefore(
3295
+ $insert,
3296
+ $nextSibling
3297
+ );
3298
+ break;
3299
+ }
3300
+ // Mutation event
3301
+ $event = new DOMEvent(array(
3302
+ 'target' => $insert,
3303
+ 'type' => 'DOMNodeInserted'
3304
+ ));
3305
+ phpQueryEvents::trigger($this->getDocumentID(),
3306
+ $event->type, array($event), $insert
3307
+ );
3308
+ }
3309
+ }
3310
+ return $this;
3311
+ }
3312
+ /**
3313
+ * Enter description here...
3314
+ *
3315
+ * @return Int
3316
+ */
3317
+ public function index($subject) {
3318
+ $index = -1;
3319
+ $subject = $subject instanceof phpQueryObject
3320
+ ? $subject->elements[0]
3321
+ : $subject;
3322
+ foreach($this->newInstance() as $k => $node) {
3323
+ if ($node->isSameNode($subject))
3324
+ $index = $k;
3325
+ }
3326
+ return $index;
3327
+ }
3328
+ /**
3329
+ * Enter description here...
3330
+ *
3331
+ * @param unknown_type $start
3332
+ * @param unknown_type $end
3333
+ *
3334
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3335
+ * @testme
3336
+ */
3337
+ public function slice($start, $end = null) {
3338
+ // $last = count($this->elements)-1;
3339
+ // $end = $end
3340
+ // ? min($end, $last)
3341
+ // : $last;
3342
+ // if ($start < 0)
3343
+ // $start = $last+$start;
3344
+ // if ($start > $last)
3345
+ // return array();
3346
+ if ($end > 0)
3347
+ $end = $end-$start;
3348
+ return $this->newInstance(
3349
+ array_slice($this->elements, $start, $end)
3350
+ );
3351
+ }
3352
+ /**
3353
+ * Enter description here...
3354
+ *
3355
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3356
+ */
3357
+ public function reverse() {
3358
+ $this->elementsBackup = $this->elements;
3359
+ $this->elements = array_reverse($this->elements);
3360
+ return $this->newInstance();
3361
+ }
3362
+ /**
3363
+ * Return joined text content.
3364
+ * @return String
3365
+ */
3366
+ public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) {
3367
+ if (isset($text))
3368
+ return $this->html(htmlspecialchars($text));
3369
+ $args = func_get_args();
3370
+ $args = array_slice($args, 1);
3371
+ $return = '';
3372
+ foreach($this->elements as $node) {
3373
+ $text = $node->textContent;
3374
+ if (count($this->elements) > 1 && $text)
3375
+ $text .= "\n";
3376
+ foreach($args as $callback) {
3377
+ $text = phpQuery::callbackRun($callback, array($text));
3378
+ }
3379
+ $return .= $text;
3380
+ }
3381
+ return $return;
3382
+ }
3383
+ /**
3384
+ * Enter description here...
3385
+ *
3386
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3387
+ */
3388
+ public function plugin($class, $file = null) {
3389
+ phpQuery::plugin($class, $file);
3390
+ return $this;
3391
+ }
3392
+ /**
3393
+ * Deprecated, use $pq->plugin() instead.
3394
+ *
3395
+ * @deprecated
3396
+ * @param $class
3397
+ * @param $file
3398
+ * @return unknown_type
3399
+ */
3400
+ public static function extend($class, $file = null) {
3401
+ return $this->plugin($class, $file);
3402
+ }
3403
+ /**
3404
+ *
3405
+ * @access private
3406
+ * @param $method
3407
+ * @param $args
3408
+ * @return unknown_type
3409
+ */
3410
+ public function __call($method, $args) {
3411
+ $aliasMethods = array('clone', 'empty');
3412
+ if (isset(phpQuery::$extendMethods[$method])) {
3413
+ array_unshift($args, $this);
3414
+ return phpQuery::callbackRun(
3415
+ phpQuery::$extendMethods[$method], $args
3416
+ );
3417
+ } else if (isset(phpQuery::$pluginsMethods[$method])) {
3418
+ array_unshift($args, $this);
3419
+ $class = phpQuery::$pluginsMethods[$method];
3420
+ $realClass = "phpQueryObjectPlugin_$class";
3421
+ $return = call_user_func_array(
3422
+ array($realClass, $method),
3423
+ $args
3424
+ );
3425
+ // XXX deprecate ?
3426
+ return is_null($return)
3427
+ ? $this
3428
+ : $return;
3429
+ } else if (in_array($method, $aliasMethods)) {
3430
+ return call_user_func_array(array($this, '_'.$method), $args);
3431
+ } else
3432
+ throw new Exception("Method '{$method}' doesnt exist");
3433
+ }
3434
+ /**
3435
+ * Safe rename of next().
3436
+ *
3437
+ * Use it ONLY when need to call next() on an iterated object (in same time).
3438
+ * Normaly there is no need to do such thing ;)
3439
+ *
3440
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3441
+ * @access private
3442
+ */
3443
+ public function _next($selector = null) {
3444
+ return $this->newInstance(
3445
+ $this->getElementSiblings('nextSibling', $selector, true)
3446
+ );
3447
+ }
3448
+ /**
3449
+ * Use prev() and next().
3450
+ *
3451
+ * @deprecated
3452
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3453
+ * @access private
3454
+ */
3455
+ public function _prev($selector = null) {
3456
+ return $this->prev($selector);
3457
+ }
3458
+ /**
3459
+ * Enter description here...
3460
+ *
3461
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3462
+ */
3463
+ public function prev($selector = null) {
3464
+ return $this->newInstance(
3465
+ $this->getElementSiblings('previousSibling', $selector, true)
3466
+ );
3467
+ }
3468
+ /**
3469
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3470
+ * @todo
3471
+ */
3472
+ public function prevAll($selector = null) {
3473
+ return $this->newInstance(
3474
+ $this->getElementSiblings('previousSibling', $selector)
3475
+ );
3476
+ }
3477
+ /**
3478
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3479
+ * @todo FIXME: returns source elements insted of next siblings
3480
+ */
3481
+ public function nextAll($selector = null) {
3482
+ return $this->newInstance(
3483
+ $this->getElementSiblings('nextSibling', $selector)
3484
+ );
3485
+ }
3486
+ /**
3487
+ * @access private
3488
+ */
3489
+ protected function getElementSiblings($direction, $selector = null, $limitToOne = false) {
3490
+ $stack = array();
3491
+ $count = 0;
3492
+ foreach($this->stack() as $node) {
3493
+ $test = $node;
3494
+ while( isset($test->{$direction}) && $test->{$direction}) {
3495
+ $test = $test->{$direction};
3496
+ if (! $test instanceof DOMELEMENT)
3497
+ continue;
3498
+ $stack[] = $test;
3499
+ if ($limitToOne)
3500
+ break;
3501
+ }
3502
+ }
3503
+ if ($selector) {
3504
+ $stackOld = $this->elements;
3505
+ $this->elements = $stack;
3506
+ $stack = $this->filter($selector, true)->stack();
3507
+ $this->elements = $stackOld;
3508
+ }
3509
+ return $stack;
3510
+ }
3511
+ /**
3512
+ * Enter description here...
3513
+ *
3514
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3515
+ */
3516
+ public function siblings($selector = null) {
3517
+ $stack = array();
3518
+ $siblings = array_merge(
3519
+ $this->getElementSiblings('previousSibling', $selector),
3520
+ $this->getElementSiblings('nextSibling', $selector)
3521
+ );
3522
+ foreach($siblings as $node) {
3523
+ if (! $this->elementsContainsNode($node, $stack))
3524
+ $stack[] = $node;
3525
+ }
3526
+ return $this->newInstance($stack);
3527
+ }
3528
+ /**
3529
+ * Enter description here...
3530
+ *
3531
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3532
+ */
3533
+ public function not($selector = null) {
3534
+ if (is_string($selector))
3535
+ phpQuery::debug(array('not', $selector));
3536
+ else
3537
+ phpQuery::debug('not');
3538
+ $stack = array();
3539
+ if ($selector instanceof self || $selector instanceof DOMNODE) {
3540
+ foreach($this->stack() as $node) {
3541
+ if ($selector instanceof self) {
3542
+ $matchFound = false;
3543
+ foreach($selector->stack() as $notNode) {
3544
+ if ($notNode->isSameNode($node))
3545
+ $matchFound = true;
3546
+ }
3547
+ if (! $matchFound)
3548
+ $stack[] = $node;
3549
+ } else if ($selector instanceof DOMNODE) {
3550
+ if (! $selector->isSameNode($node))
3551
+ $stack[] = $node;
3552
+ } else {
3553
+ if (! $this->is($selector))
3554
+ $stack[] = $node;
3555
+ }
3556
+ }
3557
+ } else {
3558
+ $orgStack = $this->stack();
3559
+ $matched = $this->filter($selector, true)->stack();
3560
+ // $matched = array();
3561
+ // // simulate OR in filter() instead of AND 5y
3562
+ // foreach($this->parseSelector($selector) as $s) {
3563
+ // $matched = array_merge($matched,
3564
+ // $this->filter(array($s))->stack()
3565
+ // );
3566
+ // }
3567
+ foreach($orgStack as $node)
3568
+ if (! $this->elementsContainsNode($node, $matched))
3569
+ $stack[] = $node;
3570
+ }
3571
+ return $this->newInstance($stack);
3572
+ }
3573
+ /**
3574
+ * Enter description here...
3575
+ *
3576
+ * @param string|phpQueryObject
3577
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3578
+ */
3579
+ public function add($selector = null) {
3580
+ if (! $selector)
3581
+ return $this;
3582
+ $stack = array();
3583
+ $this->elementsBackup = $this->elements;
3584
+ $found = phpQuery::pq($selector, $this->getDocumentID());
3585
+ $this->merge($found->elements);
3586
+ return $this->newInstance();
3587
+ }
3588
+ /**
3589
+ * @access private
3590
+ */
3591
+ protected function merge() {
3592
+ foreach(func_get_args() as $nodes)
3593
+ foreach($nodes as $newNode )
3594
+ if (! $this->elementsContainsNode($newNode) )
3595
+ $this->elements[] = $newNode;
3596
+ }
3597
+ /**
3598
+ * @access private
3599
+ * TODO refactor to stackContainsNode
3600
+ */
3601
+ protected function elementsContainsNode($nodeToCheck, $elementsStack = null) {
3602
+ $loop = ! is_null($elementsStack)
3603
+ ? $elementsStack
3604
+ : $this->elements;
3605
+ foreach($loop as $node) {
3606
+ if ( $node->isSameNode( $nodeToCheck ) )
3607
+ return true;
3608
+ }
3609
+ return false;
3610
+ }
3611
+ /**
3612
+ * Enter description here...
3613
+ *
3614
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3615
+ */
3616
+ public function parent($selector = null) {
3617
+ $stack = array();
3618
+ foreach($this->elements as $node )
3619
+ if ( $node->parentNode && ! $this->elementsContainsNode($node->parentNode, $stack) )
3620
+ $stack[] = $node->parentNode;
3621
+ $this->elementsBackup = $this->elements;
3622
+ $this->elements = $stack;
3623
+ if ( $selector )
3624
+ $this->filter($selector, true);
3625
+ return $this->newInstance();
3626
+ }
3627
+ /**
3628
+ * Enter description here...
3629
+ *
3630
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3631
+ */
3632
+ public function parents($selector = null) {
3633
+ $stack = array();
3634
+ if (! $this->elements )
3635
+ $this->debug('parents() - stack empty');
3636
+ foreach($this->elements as $node) {
3637
+ $test = $node;
3638
+ while( $test->parentNode) {
3639
+ $test = $test->parentNode;
3640
+ if ($this->isRoot($test))
3641
+ break;
3642
+ if (! $this->elementsContainsNode($test, $stack)) {
3643
+ $stack[] = $test;
3644
+ continue;
3645
+ }
3646
+ }
3647
+ }
3648
+ $this->elementsBackup = $this->elements;
3649
+ $this->elements = $stack;
3650
+ if ( $selector )
3651
+ $this->filter($selector, true);
3652
+ return $this->newInstance();
3653
+ }
3654
+ /**
3655
+ * Internal stack iterator.
3656
+ *
3657
+ * @access private
3658
+ */
3659
+ public function stack($nodeTypes = null) {
3660
+ if (!isset($nodeTypes))
3661
+ return $this->elements;
3662
+ if (!is_array($nodeTypes))
3663
+ $nodeTypes = array($nodeTypes);
3664
+ $return = array();
3665
+ foreach($this->elements as $node) {
3666
+ if (in_array($node->nodeType, $nodeTypes))
3667
+ $return[] = $node;
3668
+ }
3669
+ return $return;
3670
+ }
3671
+ // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes
3672
+ protected function attrEvents($attr, $oldAttr, $oldValue, $node) {
3673
+ // skip events for XML documents
3674
+ if (! $this->isXHTML() && ! $this->isHTML())
3675
+ return;
3676
+ $event = null;
3677
+ // identify
3678
+ $isInputValue = $node->tagName == 'input'
3679
+ && (
3680
+ in_array($node->getAttribute('type'),
3681
+ array('text', 'password', 'hidden'))
3682
+ || !$node->getAttribute('type')
3683
+ );
3684
+ $isRadio = $node->tagName == 'input'
3685
+ && $node->getAttribute('type') == 'radio';
3686
+ $isCheckbox = $node->tagName == 'input'
3687
+ && $node->getAttribute('type') == 'checkbox';
3688
+ $isOption = $node->tagName == 'option';
3689
+ if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) {
3690
+ $event = new DOMEvent(array(
3691
+ 'target' => $node,
3692
+ 'type' => 'change'
3693
+ ));
3694
+ } else if (($isRadio || $isCheckbox) && $attr == 'checked' && (
3695
+ // check
3696
+ (! $oldAttr && $node->hasAttribute($attr))
3697
+ // un-check
3698
+ || (! $node->hasAttribute($attr) && $oldAttr)
3699
+ )) {
3700
+ $event = new DOMEvent(array(
3701
+ 'target' => $node,
3702
+ 'type' => 'change'
3703
+ ));
3704
+ } else if ($isOption && $node->parentNode && $attr == 'selected' && (
3705
+ // select
3706
+ (! $oldAttr && $node->hasAttribute($attr))
3707
+ // un-select
3708
+ || (! $node->hasAttribute($attr) && $oldAttr)
3709
+ )) {
3710
+ $event = new DOMEvent(array(
3711
+ 'target' => $node->parentNode,
3712
+ 'type' => 'change'
3713
+ ));
3714
+ }
3715
+ if ($event) {
3716
+ phpQueryEvents::trigger($this->getDocumentID(),
3717
+ $event->type, array($event), $node
3718
+ );
3719
+ }
3720
+ }
3721
+ public function attr($attr = null, $value = null) {
3722
+ foreach($this->stack(1) as $node) {
3723
+ if (! is_null($value)) {
3724
+ $loop = $attr == '*'
3725
+ ? $this->getNodeAttrs($node)
3726
+ : array($attr);
3727
+ foreach($loop as $a) {
3728
+ $oldValue = $node->getAttribute($a);
3729
+ $oldAttr = $node->hasAttribute($a);
3730
+ // TODO raises an error when charset other than UTF-8
3731
+ // while document's charset is also not UTF-8
3732
+ @$node->setAttribute($a, $value);
3733
+ $this->attrEvents($a, $oldAttr, $oldValue, $node);
3734
+ }
3735
+ } else if ($attr == '*') {
3736
+ // jQuery difference
3737
+ $return = array();
3738
+ foreach($node->attributes as $n => $v)
3739
+ $return[$n] = $v->value;
3740
+ return $return;
3741
+ } else
3742
+ return $node->hasAttribute($attr)
3743
+ ? $node->getAttribute($attr)
3744
+ : null;
3745
+ }
3746
+ return is_null($value)
3747
+ ? '' : $this;
3748
+ }
3749
+ /**
3750
+ * @access private
3751
+ */
3752
+ protected function getNodeAttrs($node) {
3753
+ $return = array();
3754
+ foreach($node->attributes as $n => $o)
3755
+ $return[] = $n;
3756
+ return $return;
3757
+ }
3758
+ /**
3759
+ * Enter description here...
3760
+ *
3761
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3762
+ * @todo check CDATA ???
3763
+ */
3764
+ public function attrPHP($attr, $code) {
3765
+ if (! is_null($code)) {
3766
+ $value = '<'.'?php '.$code.' ?'.'>';
3767
+ // TODO tempolary solution
3768
+ // http://code.google.com/p/phpquery/issues/detail?id=17
3769
+ // if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII')
3770
+ // $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES');
3771
+ }
3772
+ foreach($this->stack(1) as $node) {
3773
+ if (! is_null($code)) {
3774
+ // $attrNode = $this->DOM->createAttribute($attr);
3775
+ $node->setAttribute($attr, $value);
3776
+ // $attrNode->value = $value;
3777
+ // $node->appendChild($attrNode);
3778
+ } else if ( $attr == '*') {
3779
+ // jQuery diff
3780
+ $return = array();
3781
+ foreach($node->attributes as $n => $v)
3782
+ $return[$n] = $v->value;
3783
+ return $return;
3784
+ } else
3785
+ return $node->getAttribute($attr);
3786
+ }
3787
+ return $this;
3788
+ }
3789
+ /**
3790
+ * Enter description here...
3791
+ *
3792
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3793
+ */
3794
+ public function removeAttr($attr) {
3795
+ foreach($this->stack(1) as $node) {
3796
+ $loop = $attr == '*'
3797
+ ? $this->getNodeAttrs($node)
3798
+ : array($attr);
3799
+ foreach($loop as $a) {
3800
+ $oldValue = $node->getAttribute($a);
3801
+ $node->removeAttribute($a);
3802
+ $this->attrEvents($a, $oldValue, null, $node);
3803
+ }
3804
+ }
3805
+ return $this;
3806
+ }
3807
+ /**
3808
+ * Return form element value.
3809
+ *
3810
+ * @return String Fields value.
3811
+ */
3812
+ public function val($val = null) {
3813
+ if (! isset($val)) {
3814
+ if ($this->eq(0)->is('select')) {
3815
+ $selected = $this->eq(0)->find('option[selected=selected]');
3816
+ if ($selected->is('[value]'))
3817
+ return $selected->attr('value');
3818
+ else
3819
+ return $selected->text();
3820
+ } else if ($this->eq(0)->is('textarea'))
3821
+ return $this->eq(0)->markup();
3822
+ else
3823
+ return $this->eq(0)->attr('value');
3824
+ } else {
3825
+ $_val = null;
3826
+ foreach($this->stack(1) as $node) {
3827
+ $node = pq($node, $this->getDocumentID());
3828
+ if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) {
3829
+ $isChecked = in_array($node->attr('value'), $val)
3830
+ || in_array($node->attr('name'), $val);
3831
+ if ($isChecked)
3832
+ $node->attr('checked', 'checked');
3833
+ else
3834
+ $node->removeAttr('checked');
3835
+ } else if ($node->get(0)->tagName == 'select') {
3836
+ if (! isset($_val)) {
3837
+ $_val = array();
3838
+ if (! is_array($val))
3839
+ $_val = array((string)$val);
3840
+ else
3841
+ foreach($val as $v)
3842
+ $_val[] = $v;
3843
+ }
3844
+ foreach($node['option']->stack(1) as $option) {
3845
+ $option = pq($option, $this->getDocumentID());
3846
+ $selected = false;
3847
+ // XXX: workaround for string comparsion, see issue #96
3848
+ // http://code.google.com/p/phpquery/issues/detail?id=96
3849
+ $selected = is_null($option->attr('value'))
3850
+ ? in_array($option->markup(), $_val)
3851
+ : in_array($option->attr('value'), $_val);
3852
+ // $optionValue = $option->attr('value');
3853
+ // $optionText = $option->text();
3854
+ // $optionTextLenght = mb_strlen($optionText);
3855
+ // foreach($_val as $v)
3856
+ // if ($optionValue == $v)
3857
+ // $selected = true;
3858
+ // else if ($optionText == $v && $optionTextLenght == mb_strlen($v))
3859
+ // $selected = true;
3860
+ if ($selected)
3861
+ $option->attr('selected', 'selected');
3862
+ else
3863
+ $option->removeAttr('selected');
3864
+ }
3865
+ } else if ($node->get(0)->tagName == 'textarea')
3866
+ $node->markup($val);
3867
+ else
3868
+ $node->attr('value', $val);
3869
+ }
3870
+ }
3871
+ return $this;
3872
+ }
3873
+ /**
3874
+ * Enter description here...
3875
+ *
3876
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3877
+ */
3878
+ public function andSelf() {
3879
+ if ( $this->previous )
3880
+ $this->elements = array_merge($this->elements, $this->previous->elements);
3881
+ return $this;
3882
+ }
3883
+ /**
3884
+ * Enter description here...
3885
+ *
3886
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3887
+ */
3888
+ public function addClass( $className) {
3889
+ if (! $className)
3890
+ return $this;
3891
+ foreach($this->stack(1) as $node) {
3892
+ if (! $this->is(".$className", $node))
3893
+ $node->setAttribute(
3894
+ 'class',
3895
+ trim($node->getAttribute('class').' '.$className)
3896
+ );
3897
+ }
3898
+ return $this;
3899
+ }
3900
+ /**
3901
+ * Enter description here...
3902
+ *
3903
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3904
+ */
3905
+ public function addClassPHP( $className) {
3906
+ foreach($this->stack(1) as $node) {
3907
+ $classes = $node->getAttribute('class');
3908
+ $newValue = $classes
3909
+ ? $classes.' <'.'?php '.$className.' ?'.'>'
3910
+ : '<'.'?php '.$className.' ?'.'>';
3911
+ $node->setAttribute('class', $newValue);
3912
+ }
3913
+ return $this;
3914
+ }
3915
+ /**
3916
+ * Enter description here...
3917
+ *
3918
+ * @param string $className
3919
+ * @return bool
3920
+ */
3921
+ public function hasClass($className) {
3922
+ foreach($this->stack(1) as $node) {
3923
+ if ( $this->is(".$className", $node))
3924
+ return true;
3925
+ }
3926
+ return false;
3927
+ }
3928
+ /**
3929
+ * Enter description here...
3930
+ *
3931
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3932
+ */
3933
+ public function removeClass($className) {
3934
+ foreach($this->stack(1) as $node) {
3935
+ $classes = explode( ' ', $node->getAttribute('class'));
3936
+ if ( in_array($className, $classes)) {
3937
+ $classes = array_diff($classes, array($className));
3938
+ if ( $classes )
3939
+ $node->setAttribute('class', implode(' ', $classes));
3940
+ else
3941
+ $node->removeAttribute('class');
3942
+ }
3943
+ }
3944
+ return $this;
3945
+ }
3946
+ /**
3947
+ * Enter description here...
3948
+ *
3949
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3950
+ */
3951
+ public function toggleClass($className) {
3952
+ foreach($this->stack(1) as $node) {
3953
+ if ( $this->is( $node, '.'.$className ))
3954
+ $this->removeClass($className);
3955
+ else
3956
+ $this->addClass($className);
3957
+ }
3958
+ return $this;
3959
+ }
3960
+ /**
3961
+ * Proper name without underscore (just ->empty()) also works.
3962
+ *
3963
+ * Removes all child nodes from the set of matched elements.
3964
+ *
3965
+ * Example:
3966
+ * pq("p")._empty()
3967
+ *
3968
+ * HTML:
3969
+ * <p>Hello, <span>Person</span> <a href="#">and person</a></p>
3970
+ *
3971
+ * Result:
3972
+ * [ <p></p> ]
3973
+ *
3974
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3975
+ * @access private
3976
+ */
3977
+ public function _empty() {
3978
+ foreach($this->stack(1) as $node) {
3979
+ // thx to 'dave at dgx dot cz'
3980
+ $node->nodeValue = '';
3981
+ }
3982
+ return $this;
3983
+ }
3984
+ /**
3985
+ * Enter description here...
3986
+ *
3987
+ * @param array|string $callback Expects $node as first param, $index as second
3988
+ * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope)
3989
+ * @param array $arg1 Will ba passed as third and futher args to callback.
3990
+ * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on...
3991
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3992
+ */
3993
+ public function each($callback, $param1 = null, $param2 = null, $param3 = null) {
3994
+ $paramStructure = null;
3995
+ if (func_num_args() > 1) {
3996
+ $paramStructure = func_get_args();
3997
+ $paramStructure = array_slice($paramStructure, 1);
3998
+ }
3999
+ foreach($this->elements as $v)
4000
+ phpQuery::callbackRun($callback, array($v), $paramStructure);
4001
+ return $this;
4002
+ }
4003
+ /**
4004
+ * Run callback on actual object.
4005
+ *
4006
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4007
+ */
4008
+ public function callback($callback, $param1 = null, $param2 = null, $param3 = null) {
4009
+ $params = func_get_args();
4010
+ $params[0] = $this;
4011
+ phpQuery::callbackRun($callback, $params);
4012
+ return $this;
4013
+ }
4014
+ /**
4015
+ * Enter description here...
4016
+ *
4017
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4018
+ * @todo add $scope and $args as in each() ???
4019
+ */
4020
+ public function map($callback, $param1 = null, $param2 = null, $param3 = null) {
4021
+ // $stack = array();
4022
+ //// foreach($this->newInstance() as $node) {
4023
+ // foreach($this->newInstance() as $node) {
4024
+ // $result = call_user_func($callback, $node);
4025
+ // if ($result)
4026
+ // $stack[] = $result;
4027
+ // }
4028
+ $params = func_get_args();
4029
+ array_unshift($params, $this->elements);
4030
+ return $this->newInstance(
4031
+ call_user_func_array(array('phpQuery', 'map'), $params)
4032
+ // phpQuery::map($this->elements, $callback)
4033
+ );
4034
+ }
4035
+ /**
4036
+ * Enter description here...
4037
+ *
4038
+ * @param <type> $key
4039
+ * @param <type> $value
4040
+ */
4041
+ public function data($key, $value = null) {
4042
+ if (! isset($value)) {
4043
+ // TODO? implement specific jQuery behavior od returning parent values
4044
+ // is child which we look up doesn't exist
4045
+ return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID());
4046
+ } else {
4047
+ foreach($this as $node)
4048
+ phpQuery::data($node, $key, $value, $this->getDocumentID());
4049
+ return $this;
4050
+ }
4051
+ }
4052
+ /**
4053
+ * Enter description here...
4054
+ *
4055
+ * @param <type> $key
4056
+ */
4057
+ public function removeData($key) {
4058
+ foreach($this as $node)
4059
+ phpQuery::removeData($node, $key, $this->getDocumentID());
4060
+ return $this;
4061
+ }
4062
+ // INTERFACE IMPLEMENTATIONS
4063
+
4064
+ // ITERATOR INTERFACE
4065
+ /**
4066
+ * @access private
4067
+ */
4068
+ public function rewind(){
4069
+ $this->debug('iterating foreach');
4070
+ // phpQuery::selectDocument($this->getDocumentID());
4071
+ $this->elementsBackup = $this->elements;
4072
+ $this->elementsInterator = $this->elements;
4073
+ $this->valid = isset( $this->elements[0] )
4074
+ ? 1 : 0;
4075
+ // $this->elements = $this->valid
4076
+ // ? array($this->elements[0])
4077
+ // : array();
4078
+ $this->current = 0;
4079
+ }
4080
+ /**
4081
+ * @access private
4082
+ */
4083
+ public function current(){
4084
+ return $this->elementsInterator[ $this->current ];
4085
+ }
4086
+ /**
4087
+ * @access private
4088
+ */
4089
+ public function key(){
4090
+ return $this->current;
4091
+ }
4092
+ /**
4093
+ * Double-function method.
4094
+ *
4095
+ * First: main iterator interface method.
4096
+ * Second: Returning next sibling, alias for _next().
4097
+ *
4098
+ * Proper functionality is choosed automagicaly.
4099
+ *
4100
+ * @see phpQueryObject::_next()
4101
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4102
+ */
4103
+ public function next($cssSelector = null){
4104
+ // if ($cssSelector || $this->valid)
4105
+ // return $this->_next($cssSelector);
4106
+ $this->valid = isset( $this->elementsInterator[ $this->current+1 ] )
4107
+ ? true
4108
+ : false;
4109
+ if (! $this->valid && $this->elementsInterator) {
4110
+ $this->elementsInterator = null;
4111
+ } else if ($this->valid) {
4112
+ $this->current++;
4113
+ } else {
4114
+ return $this->_next($cssSelector);
4115
+ }
4116
+ }
4117
+ /**
4118
+ * @access private
4119
+ */
4120
+ public function valid(){
4121
+ return $this->valid;
4122
+ }
4123
+ // ITERATOR INTERFACE END
4124
+ // ARRAYACCESS INTERFACE
4125
+ /**
4126
+ * @access private
4127
+ */
4128
+ public function offsetExists($offset) {
4129
+ return $this->find($offset)->size() > 0;
4130
+ }
4131
+ /**
4132
+ * @access private
4133
+ */
4134
+ public function offsetGet($offset) {
4135
+ return $this->find($offset);
4136
+ }
4137
+ /**
4138
+ * @access private
4139
+ */
4140
+ public function offsetSet($offset, $value) {
4141
+ // $this->find($offset)->replaceWith($value);
4142
+ $this->find($offset)->html($value);
4143
+ }
4144
+ /**
4145
+ * @access private
4146
+ */
4147
+ public function offsetUnset($offset) {
4148
+ // empty
4149
+ throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML.");
4150
+ }
4151
+ // ARRAYACCESS INTERFACE END
4152
+ /**
4153
+ * Returns node's XPath.
4154
+ *
4155
+ * @param unknown_type $oneNode
4156
+ * @return string
4157
+ * @TODO use native getNodePath is avaible
4158
+ * @access private
4159
+ */
4160
+ protected function getNodeXpath($oneNode = null, $namespace = null) {
4161
+ $return = array();
4162
+ $loop = $oneNode
4163
+ ? array($oneNode)
4164
+ : $this->elements;
4165
+ // if ($namespace)
4166
+ // $namespace .= ':';
4167
+ foreach($loop as $node) {
4168
+ if ($node instanceof DOMDOCUMENT) {
4169
+ $return[] = '';
4170
+ continue;
4171
+ }
4172
+ $xpath = array();
4173
+ while(! ($node instanceof DOMDOCUMENT)) {
4174
+ $i = 1;
4175
+ $sibling = $node;
4176
+ while($sibling->previousSibling) {
4177
+ $sibling = $sibling->previousSibling;
4178
+ $isElement = $sibling instanceof DOMELEMENT;
4179
+ if ($isElement && $sibling->tagName == $node->tagName)
4180
+ $i++;
4181
+ }
4182
+ $xpath[] = $this->isXML()
4183
+ ? "*[local-name()='{$node->tagName}'][{$i}]"
4184
+ : "{$node->tagName}[{$i}]";
4185
+ $node = $node->parentNode;
4186
+ }
4187
+ $xpath = join('/', array_reverse($xpath));
4188
+ $return[] = '/'.$xpath;
4189
+ }
4190
+ return $oneNode
4191
+ ? $return[0]
4192
+ : $return;
4193
+ }
4194
+ // HELPERS
4195
+ public function whois($oneNode = null) {
4196
+ $return = array();
4197
+ $loop = $oneNode
4198
+ ? array( $oneNode )
4199
+ : $this->elements;
4200
+ foreach($loop as $node) {
4201
+ if (isset($node->tagName)) {
4202
+ $tag = in_array($node->tagName, array('php', 'js'))
4203
+ ? strtoupper($node->tagName)
4204
+ : $node->tagName;
4205
+ $return[] = $tag
4206
+ .($node->getAttribute('id')
4207
+ ? '#'.$node->getAttribute('id'):'')
4208
+ .($node->getAttribute('class')
4209
+ ? '.'.join('.', split(' ', $node->getAttribute('class'))):'')
4210
+ .($node->getAttribute('name')
4211
+ ? '[name="'.$node->getAttribute('name').'"]':'')
4212
+ .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false
4213
+ ? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'')
4214
+ .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false
4215
+ ? '[value=PHP]':'')
4216
+ .($node->getAttribute('selected')
4217
+ ? '[selected]':'')
4218
+ .($node->getAttribute('checked')
4219
+ ? '[checked]':'')
4220
+ ;
4221
+ } else if ($node instanceof DOMTEXT) {
4222
+ if (trim($node->textContent))
4223
+ $return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15);
4224
+ } else {
4225
+
4226
+ }
4227
+ }
4228
+ return $oneNode && isset($return[0])
4229
+ ? $return[0]
4230
+ : $return;
4231
+ }
4232
+ /**
4233
+ * Dump htmlOuter and preserve chain. Usefull for debugging.
4234
+ *
4235
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4236
+ *
4237
+ */
4238
+ public function dump() {
4239
+ print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4240
+ $debug = phpQuery::$debug;
4241
+ phpQuery::$debug = false;
4242
+ // print __FILE__.':'.__LINE__."\n";
4243
+ var_dump($this->htmlOuter());
4244
+ return $this;
4245
+ }
4246
+ public function dumpWhois() {
4247
+ print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4248
+ $debug = phpQuery::$debug;
4249
+ phpQuery::$debug = false;
4250
+ // print __FILE__.':'.__LINE__."\n";
4251
+ var_dump('whois', $this->whois());
4252
+ phpQuery::$debug = $debug;
4253
+ return $this;
4254
+ }
4255
+ public function dumpLength() {
4256
+ print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4257
+ $debug = phpQuery::$debug;
4258
+ phpQuery::$debug = false;
4259
+ // print __FILE__.':'.__LINE__."\n";
4260
+ var_dump('length', $this->length());
4261
+ phpQuery::$debug = $debug;
4262
+ return $this;
4263
+ }
4264
+ public function dumpTree($html = true, $title = true) {
4265
+ $output = $title
4266
+ ? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : '';
4267
+ $debug = phpQuery::$debug;
4268
+ phpQuery::$debug = false;
4269
+ foreach($this->stack() as $node)
4270
+ $output .= $this->__dumpTree($node);
4271
+ phpQuery::$debug = $debug;
4272
+ print $html
4273
+ ? nl2br(str_replace(' ', '&nbsp;', $output))
4274
+ : $output;
4275
+ return $this;
4276
+ }
4277
+ private function __dumpTree($node, $intend = 0) {
4278
+ $whois = $this->whois($node);
4279
+ $return = '';
4280
+ if ($whois)
4281
+ $return .= str_repeat(' - ', $intend).$whois."\n";
4282
+ if (isset($node->childNodes))
4283
+ foreach($node->childNodes as $chNode)
4284
+ $return .= $this->__dumpTree($chNode, $intend+1);
4285
+ return $return;
4286
+ }
4287
+ /**
4288
+ * Dump htmlOuter and stop script execution. Usefull for debugging.
4289
+ *
4290
+ */
4291
+ public function dumpDie() {
4292
+ print __FILE__.':'.__LINE__;
4293
+ var_dump($this->htmlOuter());
4294
+ die();
4295
+ }
4296
+ }
4297
+
4298
+
4299
+ // -- Multibyte Compatibility functions ---------------------------------------
4300
+ // http://svn.iphonewebdev.com/lace/lib/mb_compat.php
4301
+
4302
+ /**
4303
+ * mb_internal_encoding()
4304
+ *
4305
+ * Included for mbstring pseudo-compatability.
4306
+ */
4307
+ if (!function_exists('mb_internal_encoding'))
4308
+ {
4309
+ function mb_internal_encoding($enc) {return true; }
4310
+ }
4311
+
4312
+ /**
4313
+ * mb_regex_encoding()
4314
+ *
4315
+ * Included for mbstring pseudo-compatability.
4316
+ */
4317
+ if (!function_exists('mb_regex_encoding'))
4318
+ {
4319
+ function mb_regex_encoding($enc) {return true; }
4320
+ }
4321
+
4322
+ /**
4323
+ * mb_strlen()
4324
+ *
4325
+ * Included for mbstring pseudo-compatability.
4326
+ */
4327
+ if (!function_exists('mb_strlen'))
4328
+ {
4329
+ function mb_strlen($str)
4330
+ {
4331
+ return strlen($str);
4332
+ }
4333
+ }
4334
+
4335
+ /**
4336
+ * mb_strpos()
4337
+ *
4338
+ * Included for mbstring pseudo-compatability.
4339
+ */
4340
+ if (!function_exists('mb_strpos'))
4341
+ {
4342
+ function mb_strpos($haystack, $needle, $offset=0)
4343
+ {
4344
+ return strpos($haystack, $needle, $offset);
4345
+ }
4346
+ }
4347
+ /**
4348
+ * mb_stripos()
4349
+ *
4350
+ * Included for mbstring pseudo-compatability.
4351
+ */
4352
+ if (!function_exists('mb_stripos'))
4353
+ {
4354
+ function mb_stripos($haystack, $needle, $offset=0)
4355
+ {
4356
+ return stripos($haystack, $needle, $offset);
4357
+ }
4358
+ }
4359
+
4360
+ /**
4361
+ * mb_substr()
4362
+ *
4363
+ * Included for mbstring pseudo-compatability.
4364
+ */
4365
+ if (!function_exists('mb_substr'))
4366
+ {
4367
+ function mb_substr($str, $start, $length=0)
4368
+ {
4369
+ return substr($str, $start, $length);
4370
+ }
4371
+ }
4372
+
4373
+ /**
4374
+ * mb_substr_count()
4375
+ *
4376
+ * Included for mbstring pseudo-compatability.
4377
+ */
4378
+ if (!function_exists('mb_substr_count'))
4379
+ {
4380
+ function mb_substr_count($haystack, $needle)
4381
+ {
4382
+ return substr_count($haystack, $needle);
4383
+ }
4384
+ }
4385
+
4386
+
4387
+ /**
4388
+ * Static namespace for phpQuery functions.
4389
+ *
4390
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
4391
+ * @package phpQuery
4392
+ */
4393
+ abstract class phpQuery {
4394
+ /**
4395
+ * XXX: Workaround for mbstring problems
4396
+ *
4397
+ * @var bool
4398
+ */
4399
+ public static $mbstringSupport = true;
4400
+ public static $debug = false;
4401
+ public static $documents = array();
4402
+ public static $defaultDocumentID = null;
4403
+ // public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"';
4404
+ /**
4405
+ * Applies only to HTML.
4406
+ *
4407
+ * @var unknown_type
4408
+ */
4409
+ public static $defaultDoctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
4410
+ "http://www.w3.org/TR/html4/loose.dtd">';
4411
+ public static $defaultCharset = 'UTF-8';
4412
+ /**
4413
+ * Static namespace for plugins.
4414
+ *
4415
+ * @var object
4416
+ */
4417
+ public static $plugins = array();
4418
+ /**
4419
+ * List of loaded plugins.
4420
+ *
4421
+ * @var unknown_type
4422
+ */
4423
+ public static $pluginsLoaded = array();
4424
+ public static $pluginsMethods = array();
4425
+ public static $pluginsStaticMethods = array();
4426
+ public static $extendMethods = array();
4427
+ /**
4428
+ * @TODO implement
4429
+ */
4430
+ public static $extendStaticMethods = array();
4431
+ /**
4432
+ * Hosts allowed for AJAX connections.
4433
+ * Dot '.' means $_SERVER['HTTP_HOST'] (if any).
4434
+ *
4435
+ * @var array
4436
+ */
4437
+ public static $ajaxAllowedHosts = array(
4438
+ '.'
4439
+ );
4440
+ /**
4441
+ * AJAX settings.
4442
+ *
4443
+ * @var array
4444
+ * XXX should it be static or not ?
4445
+ */
4446
+ public static $ajaxSettings = array(
4447
+ 'url' => '',//TODO
4448
+ 'global' => true,
4449
+ 'type' => "GET",
4450
+ 'timeout' => null,
4451
+ 'contentType' => "application/x-www-form-urlencoded",
4452
+ 'processData' => true,
4453
+ // 'async' => true,
4454
+ 'data' => null,
4455
+ 'username' => null,
4456
+ 'password' => null,
4457
+ 'accepts' => array(
4458
+ 'xml' => "application/xml, text/xml",
4459
+ 'html' => "text/html",
4460
+ 'script' => "text/javascript, application/javascript",
4461
+ 'json' => "application/json, text/javascript",
4462
+ 'text' => "text/plain",
4463
+ '_default' => "*/*"
4464
+ )
4465
+ );
4466
+ public static $lastModified = null;
4467
+ public static $active = 0;
4468
+ public static $dumpCount = 0;
4469
+ /**
4470
+ * Multi-purpose function.
4471
+ * Use pq() as shortcut.
4472
+ *
4473
+ * In below examples, $pq is any result of pq(); function.
4474
+ *
4475
+ * 1. Import markup into existing document (without any attaching):
4476
+ * - Import into selected document:
4477
+ * pq('<div/>') // DOESNT accept text nodes at beginning of input string !
4478
+ * - Import into document with ID from $pq->getDocumentID():
4479
+ * pq('<div/>', $pq->getDocumentID())
4480
+ * - Import into same document as DOMNode belongs to:
4481
+ * pq('<div/>', DOMNode)
4482
+ * - Import into document from phpQuery object:
4483
+ * pq('<div/>', $pq)
4484
+ *
4485
+ * 2. Run query:
4486
+ * - Run query on last selected document:
4487
+ * pq('div.myClass')
4488
+ * - Run query on document with ID from $pq->getDocumentID():
4489
+ * pq('div.myClass', $pq->getDocumentID())
4490
+ * - Run query on same document as DOMNode belongs to and use node(s)as root for query:
4491
+ * pq('div.myClass', DOMNode)
4492
+ * - Run query on document from phpQuery object
4493
+ * and use object's stack as root node(s) for query:
4494
+ * pq('div.myClass', $pq)
4495
+ *
4496
+ * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes
4497
+ * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root)
4498
+ *
4499
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false
4500
+ * phpQuery object or false in case of error.
4501
+ */
4502
+ public static function pq($arg1, $context = null) {
4503
+ if ($arg1 instanceof DOMNODE && ! isset($context)) {
4504
+ foreach(phpQuery::$documents as $documentWrapper) {
4505
+ $compare = $arg1 instanceof DOMDocument
4506
+ ? $arg1 : $arg1->ownerDocument;
4507
+ if ($documentWrapper->document->isSameNode($compare))
4508
+ $context = $documentWrapper->id;
4509
+ }
4510
+ }
4511
+ if (! $context) {
4512
+ $domId = self::$defaultDocumentID;
4513
+ if (! $domId)
4514
+ throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first.");
4515
+ // } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
4516
+ } else if (is_object($context) && $context instanceof phpQueryObject)
4517
+ $domId = $context->getDocumentID();
4518
+ else if ($context instanceof DOMDOCUMENT) {
4519
+ $domId = self::getDocumentID($context);
4520
+ if (! $domId) {
4521
+ //throw new Exception('Orphaned DOMDocument');
4522
+ $domId = self::newDocument($context)->getDocumentID();
4523
+ }
4524
+ } else if ($context instanceof DOMNODE) {
4525
+ $domId = self::getDocumentID($context);
4526
+ if (! $domId) {
4527
+ throw new Exception('Orphaned DOMNode');
4528
+ // $domId = self::newDocument($context->ownerDocument);
4529
+ }
4530
+ } else
4531
+ $domId = $context;
4532
+ if ($arg1 instanceof phpQueryObject) {
4533
+ // if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) {
4534
+ /**
4535
+ * Return $arg1 or import $arg1 stack if document differs:
4536
+ * pq(pq('<div/>'))
4537
+ */
4538
+ if ($arg1->getDocumentID() == $domId)
4539
+ return $arg1;
4540
+ $class = get_class($arg1);
4541
+ // support inheritance by passing old object to overloaded constructor
4542
+ $phpQuery = $class != 'phpQuery'
4543
+ ? new $class($arg1, $domId)
4544
+ : new phpQueryObject($domId);
4545
+ $phpQuery->elements = array();
4546
+ foreach($arg1->elements as $node)
4547
+ $phpQuery->elements[] = $phpQuery->document->importNode($node, true);
4548
+ return $phpQuery;
4549
+ } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) {
4550
+ /*
4551
+ * Wrap DOM nodes with phpQuery object, import into document when needed:
4552
+ * pq(array($domNode1, $domNode2))
4553
+ */
4554
+ $phpQuery = new phpQueryObject($domId);
4555
+ if (!($arg1 instanceof DOMNODELIST) && ! is_array($arg1))
4556
+ $arg1 = array($arg1);
4557
+ $phpQuery->elements = array();
4558
+ foreach($arg1 as $node) {
4559
+ $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT
4560
+ && ! $node->ownerDocument->isSameNode($phpQuery->document);
4561
+ $phpQuery->elements[] = $sameDocument
4562
+ ? $phpQuery->document->importNode($node, true)
4563
+ : $node;
4564
+ }
4565
+ return $phpQuery;
4566
+ } else if (self::isMarkup($arg1)) {
4567
+ /**
4568
+ * Import HTML:
4569
+ * pq('<div/>')
4570
+ */
4571
+ $phpQuery = new phpQueryObject($domId);
4572
+ return $phpQuery->newInstance(
4573
+ $phpQuery->documentWrapper->import($arg1)
4574
+ );
4575
+ } else {
4576
+ /**
4577
+ * Run CSS query:
4578
+ * pq('div.myClass')
4579
+ */
4580
+ $phpQuery = new phpQueryObject($domId);
4581
+ // if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
4582
+ if ($context && $context instanceof phpQueryObject)
4583
+ $phpQuery->elements = $context->elements;
4584
+ else if ($context && $context instanceof DOMNODELIST) {
4585
+ $phpQuery->elements = array();
4586
+ foreach($context as $node)
4587
+ $phpQuery->elements[] = $node;
4588
+ } else if ($context && $context instanceof DOMNODE)
4589
+ $phpQuery->elements = array($context);
4590
+ return $phpQuery->find($arg1);
4591
+ }
4592
+ }
4593
+ /**
4594
+ * Sets default document to $id. Document has to be loaded prior
4595
+ * to using this method.
4596
+ * $id can be retrived via getDocumentID() or getDocumentIDRef().
4597
+ *
4598
+ * @param unknown_type $id
4599
+ */
4600
+ public static function selectDocument($id) {
4601
+ $id = self::getDocumentID($id);
4602
+ self::debug("Selecting document '$id' as default one");
4603
+ self::$defaultDocumentID = self::getDocumentID($id);
4604
+ }
4605
+ /**
4606
+ * Returns document with id $id or last used as phpQueryObject.
4607
+ * $id can be retrived via getDocumentID() or getDocumentIDRef().
4608
+ * Chainable.
4609
+ *
4610
+ * @see phpQuery::selectDocument()
4611
+ * @param unknown_type $id
4612
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4613
+ */
4614
+ public static function getDocument($id = null) {
4615
+ if ($id)
4616
+ phpQuery::selectDocument($id);
4617
+ else
4618
+ $id = phpQuery::$defaultDocumentID;
4619
+ return new phpQueryObject($id);
4620
+ }
4621
+ /**
4622
+ * Creates new document from markup.
4623
+ * Chainable.
4624
+ *
4625
+ * @param unknown_type $markup
4626
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4627
+ */
4628
+ public static function newDocument($markup = null, $contentType = null) {
4629
+ if (! $markup)
4630
+ $markup = '';
4631
+ $documentID = phpQuery::createDocumentWrapper($markup, $contentType);
4632
+ return new phpQueryObject($documentID);
4633
+ }
4634
+ /**
4635
+ * Creates new document from markup.
4636
+ * Chainable.
4637
+ *
4638
+ * @param unknown_type $markup
4639
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4640
+ */
4641
+ public static function newDocumentHTML($markup = null, $charset = null) {
4642
+ $contentType = $charset
4643
+ ? ";charset=$charset"
4644
+ : '';
4645
+ return self::newDocument($markup, "text/html{$contentType}");
4646
+ }
4647
+ /**
4648
+ * Creates new document from markup.
4649
+ * Chainable.
4650
+ *
4651
+ * @param unknown_type $markup
4652
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4653
+ */
4654
+ public static function newDocumentXML($markup = null, $charset = null) {
4655
+ $contentType = $charset
4656
+ ? ";charset=$charset"
4657
+ : '';
4658
+ return self::newDocument($markup, "text/xml{$contentType}");
4659
+ }
4660
+ /**
4661
+ * Creates new document from markup.
4662
+ * Chainable.
4663
+ *
4664
+ * @param unknown_type $markup
4665
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4666
+ */
4667
+ public static function newDocumentXHTML($markup = null, $charset = null) {
4668
+ $contentType = $charset
4669
+ ? ";charset=$charset"
4670
+ : '';
4671
+ return self::newDocument($markup, "application/xhtml+xml{$contentType}");
4672
+ }
4673
+ /**
4674
+ * Creates new document from markup.
4675
+ * Chainable.
4676
+ *
4677
+ * @param unknown_type $markup
4678
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4679
+ */
4680
+ public static function newDocumentPHP($markup = null, $contentType = "text/html") {
4681
+ // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function)
4682
+ $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset);
4683
+ return self::newDocument($markup, $contentType);
4684
+ }
4685
+ public static function phpToMarkup($php, $charset = 'utf-8') {
4686
+ $regexes = array(
4687
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?>)([^\']*)\'@s',
4688
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?>)([^"]*)"@s',
4689
+ );
4690
+ foreach($regexes as $regex)
4691
+ while (preg_match($regex, $php, $matches)) {
4692
+ $php = preg_replace_callback(
4693
+ $regex,
4694
+ // create_function('$m, $charset = "'.$charset.'"',
4695
+ // 'return $m[1].$m[2]
4696
+ // .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
4697
+ // .$m[5].$m[2];'
4698
+ // ),
4699
+ array('phpQuery', '_phpToMarkupCallback'),
4700
+ $php
4701
+ );
4702
+ }
4703
+ $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s';
4704
+ //preg_match_all($regex, $php, $matches);
4705
+ //var_dump($matches);
4706
+ $php = preg_replace($regex, '\\1<php><!-- \\3 --></php>', $php);
4707
+ return $php;
4708
+ }
4709
+ public static function _phpToMarkupCallback($php, $charset = 'utf-8') {
4710
+ return $m[1].$m[2]
4711
+ .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
4712
+ .$m[5].$m[2];
4713
+ }
4714
+ public static function _markupToPHPCallback($m) {
4715
+ return "<"."?php ".htmlspecialchars_decode($m[1])." ?".">";
4716
+ }
4717
+ /**
4718
+ * Converts document markup containing PHP code generated by phpQuery::php()
4719
+ * into valid (executable) PHP code syntax.
4720
+ *
4721
+ * @param string|phpQueryObject $content
4722
+ * @return string PHP code.
4723
+ */
4724
+ public static function markupToPHP($content) {
4725
+ if ($content instanceof phpQueryObject)
4726
+ $content = $content->markupOuter();
4727
+ /* <php>...</php> to <?php...? > */
4728
+ $content = preg_replace_callback(
4729
+ '@<php>\s*<!--(.*?)-->\s*</php>@s',
4730
+ // create_function('$m',
4731
+ // 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";'
4732
+ // ),
4733
+ array('phpQuery', '_markupToPHPCallback'),
4734
+ $content
4735
+ );
4736
+ /* <node attr='< ?php ? >'> extra space added to save highlighters */
4737
+ $regexes = array(
4738
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^\']*)\'@s',
4739
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^"]*)"@s',
4740
+ );
4741
+ foreach($regexes as $regex)
4742
+ while (preg_match($regex, $content))
4743
+ $content = preg_replace_callback(
4744
+ $regex,
4745
+ create_function('$m',
4746
+ 'return $m[1].$m[2].$m[3]."<?php "
4747
+ .str_replace(
4748
+ array("%20", "%3E", "%09", "&#10;", "&#9;", "%7B", "%24", "%7D", "%22", "%5B", "%5D"),
4749
+ array(" ", ">", " ", "\n", " ", "{", "$", "}", \'"\', "[", "]"),
4750
+ htmlspecialchars_decode($m[4])
4751
+ )
4752
+ ." ?>".$m[5].$m[2];'
4753
+ ),
4754
+ $content
4755
+ );
4756
+ return $content;
4757
+ }
4758
+ /**
4759
+ * Creates new document from file $file.
4760
+ * Chainable.
4761
+ *
4762
+ * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources.
4763
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4764
+ */
4765
+ public static function newDocumentFile($file, $contentType = null) {
4766
+ $documentID = self::createDocumentWrapper(
4767
+ file_get_contents($file), $contentType
4768
+ );
4769
+ return new phpQueryObject($documentID);
4770
+ }
4771
+ /**
4772
+ * Creates new document from markup.
4773
+ * Chainable.
4774
+ *
4775
+ * @param unknown_type $markup
4776
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4777
+ */
4778
+ public static function newDocumentFileHTML($file, $charset = null) {
4779
+ $contentType = $charset
4780
+ ? ";charset=$charset"
4781
+ : '';
4782
+ return self::newDocumentFile($file, "text/html{$contentType}");
4783
+ }
4784
+ /**
4785
+ * Creates new document from markup.
4786
+ * Chainable.
4787
+ *
4788
+ * @param unknown_type $markup
4789
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4790
+ */
4791
+ public static function newDocumentFileXML($file, $charset = null) {
4792
+ $contentType = $charset
4793
+ ? ";charset=$charset"
4794
+ : '';
4795
+ return self::newDocumentFile($file, "text/xml{$contentType}");
4796
+ }
4797
+ /**
4798
+ * Creates new document from markup.
4799
+ * Chainable.
4800
+ *
4801
+ * @param unknown_type $markup
4802
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4803
+ */
4804
+ public static function newDocumentFileXHTML($file, $charset = null) {
4805
+ $contentType = $charset
4806
+ ? ";charset=$charset"
4807
+ : '';
4808
+ return self::newDocumentFile($file, "application/xhtml+xml{$contentType}");
4809
+ }
4810
+ /**
4811
+ * Creates new document from markup.
4812
+ * Chainable.
4813
+ *
4814
+ * @param unknown_type $markup
4815
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4816
+ */
4817
+ public static function newDocumentFilePHP($file, $contentType = null) {
4818
+ return self::newDocumentPHP(file_get_contents($file), $contentType);
4819
+ }
4820
+ /**
4821
+ * Reuses existing DOMDocument object.
4822
+ * Chainable.
4823
+ *
4824
+ * @param $document DOMDocument
4825
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4826
+ * @TODO support DOMDocument
4827
+ */
4828
+ public static function loadDocument($document) {
4829
+ // TODO
4830
+ die('TODO loadDocument');
4831
+ }
4832
+ /**
4833
+ * Enter description here...
4834
+ *
4835
+ * @param unknown_type $html
4836
+ * @param unknown_type $domId
4837
+ * @return unknown New DOM ID
4838
+ * @todo support PHP tags in input
4839
+ * @todo support passing DOMDocument object from self::loadDocument
4840
+ */
4841
+ protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) {
4842
+ if (function_exists('domxml_open_mem'))
4843
+ throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled.");
4844
+ // $id = $documentID
4845
+ // ? $documentID
4846
+ // : md5(microtime());
4847
+ $document = null;
4848
+ if ($html instanceof DOMDOCUMENT) {
4849
+ if (self::getDocumentID($html)) {
4850
+ // document already exists in phpQuery::$documents, make a copy
4851
+ $document = clone $html;
4852
+ } else {
4853
+ // new document, add it to phpQuery::$documents
4854
+ $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
4855
+ }
4856
+ } else {
4857
+ $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
4858
+ }
4859
+ // $wrapper->id = $id;
4860
+ // bind document
4861
+ phpQuery::$documents[$wrapper->id] = $wrapper;
4862
+ // remember last loaded document
4863
+ phpQuery::selectDocument($wrapper->id);
4864
+ return $wrapper->id;
4865
+ }
4866
+ /**
4867
+ * Extend class namespace.
4868
+ *
4869
+ * @param string|array $target
4870
+ * @param array $source
4871
+ * @TODO support string $source
4872
+ * @return unknown_type
4873
+ */
4874
+ public static function extend($target, $source) {
4875
+ switch($target) {
4876
+ case 'phpQueryObject':
4877
+ $targetRef = &self::$extendMethods;
4878
+ $targetRef2 = &self::$pluginsMethods;
4879
+ break;
4880
+ case 'phpQuery':
4881
+ $targetRef = &self::$extendStaticMethods;
4882
+ $targetRef2 = &self::$pluginsStaticMethods;
4883
+ break;
4884
+ default:
4885
+ throw new Exception("Unsupported \$target type");
4886
+ }
4887
+ if (is_string($source))
4888
+ $source = array($source => $source);
4889
+ foreach($source as $method => $callback) {
4890
+ if (isset($targetRef[$method])) {
4891
+ // throw new Exception
4892
+ self::debug("Duplicate method '{$method}', can\'t extend '{$target}'");
4893
+ continue;
4894
+ }
4895
+ if (isset($targetRef2[$method])) {
4896
+ // throw new Exception
4897
+ self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}',"
4898
+ ." can\'t extend '{$target}'");
4899
+ continue;
4900
+ }
4901
+ $targetRef[$method] = $callback;
4902
+ }
4903
+ return true;
4904
+ }
4905
+ /**
4906
+ * Extend phpQuery with $class from $file.
4907
+ *
4908
+ * @param string $class Extending class name. Real class name can be prepended phpQuery_.
4909
+ * @param string $file Filename to include. Defaults to "{$class}.php".
4910
+ */
4911
+ public static function plugin($class, $file = null) {
4912
+ // TODO $class checked agains phpQuery_$class
4913
+ // if (strpos($class, 'phpQuery') === 0)
4914
+ // $class = substr($class, 8);
4915
+ if (in_array($class, self::$pluginsLoaded))
4916
+ return true;
4917
+ if (! $file)
4918
+ $file = $class.'.php';
4919
+ $objectClassExists = class_exists('phpQueryObjectPlugin_'.$class);
4920
+ $staticClassExists = class_exists('phpQueryPlugin_'.$class);
4921
+ if (! $objectClassExists && ! $staticClassExists)
4922
+ require_once($file);
4923
+ self::$pluginsLoaded[] = $class;
4924
+ // static methods
4925
+ if (class_exists('phpQueryPlugin_'.$class)) {
4926
+ $realClass = 'phpQueryPlugin_'.$class;
4927
+ $vars = get_class_vars($realClass);
4928
+ $loop = isset($vars['phpQueryMethods'])
4929
+ && ! is_null($vars['phpQueryMethods'])
4930
+ ? $vars['phpQueryMethods']
4931
+ : get_class_methods($realClass);
4932
+ foreach($loop as $method) {
4933
+ if ($method == '__initialize')
4934
+ continue;
4935
+ if (! is_callable(array($realClass, $method)))
4936
+ continue;
4937
+ if (isset(self::$pluginsStaticMethods[$method])) {
4938
+ throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsStaticMethods[$method]."'");
4939
+ return;
4940
+ }
4941
+ self::$pluginsStaticMethods[$method] = $class;
4942
+ }
4943
+ if (method_exists($realClass, '__initialize'))
4944
+ call_user_func_array(array($realClass, '__initialize'), array());
4945
+ }
4946
+ // object methods
4947
+ if (class_exists('phpQueryObjectPlugin_'.$class)) {
4948
+ $realClass = 'phpQueryObjectPlugin_'.$class;
4949
+ $vars = get_class_vars($realClass);
4950
+ $loop = isset($vars['phpQueryMethods'])
4951
+ && ! is_null($vars['phpQueryMethods'])
4952
+ ? $vars['phpQueryMethods']
4953
+ : get_class_methods($realClass);
4954
+ foreach($loop as $method) {
4955
+ if (! is_callable(array($realClass, $method)))
4956
+ continue;
4957
+ if (isset(self::$pluginsMethods[$method])) {
4958
+ throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsMethods[$method]."'");
4959
+ continue;
4960
+ }
4961
+ self::$pluginsMethods[$method] = $class;
4962
+ }
4963
+ }
4964
+ return true;
4965
+ }
4966
+ /**
4967
+ * Unloades all or specified document from memory.
4968
+ *
4969
+ * @param mixed $documentID @see phpQuery::getDocumentID() for supported types.
4970
+ */
4971
+ public static function unloadDocuments($id = null) {
4972
+ if (isset($id)) {
4973
+ if ($id = self::getDocumentID($id))
4974
+ unset(phpQuery::$documents[$id]);
4975
+ } else {
4976
+ foreach(phpQuery::$documents as $k => $v) {
4977
+ unset(phpQuery::$documents[$k]);
4978
+ }
4979
+ }
4980
+ }
4981
+ /**
4982
+ * Parses phpQuery object or HTML result against PHP tags and makes them active.
4983
+ *
4984
+ * @param phpQuery|string $content
4985
+ * @deprecated
4986
+ * @return string
4987
+ */
4988
+ public static function unsafePHPTags($content) {
4989
+ return self::markupToPHP($content);
4990
+ }
4991
+ public static function DOMNodeListToArray($DOMNodeList) {
4992
+ $array = array();
4993
+ if (! $DOMNodeList)
4994
+ return $array;
4995
+ foreach($DOMNodeList as $node)
4996
+ $array[] = $node;
4997
+ return $array;
4998
+ }
4999
+ /**
5000
+ * Checks if $input is HTML string, which has to start with '<'.
5001
+ *
5002
+ * @deprecated
5003
+ * @param String $input
5004
+ * @return Bool
5005
+ * @todo still used ?
5006
+ */
5007
+ public static function isMarkup($input) {
5008
+ return ! is_array($input) && substr(trim($input), 0, 1) == '<';
5009
+ }
5010
+ public static function debug($text) {
5011
+ if (self::$debug)
5012
+ print var_dump($text);
5013
+ }
5014
+ /**
5015
+ * Make an AJAX request.
5016
+ *
5017
+ * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions
5018
+ * Additional options are:
5019
+ * 'document' - document for global events, @see phpQuery::getDocumentID()
5020
+ * 'referer' - implemented
5021
+ * 'requested_with' - TODO; not implemented (X-Requested-With)
5022
+ * @return Zend_Http_Client
5023
+ * @link http://docs.jquery.com/Ajax/jQuery.ajax
5024
+ *
5025
+ * @TODO $options['cache']
5026
+ * @TODO $options['processData']
5027
+ * @TODO $options['xhr']
5028
+ * @TODO $options['data'] as string
5029
+ * @TODO XHR interface
5030
+ */
5031
+ public static function ajax($options = array(), $xhr = null) {
5032
+ $options = array_merge(
5033
+ self::$ajaxSettings, $options
5034
+ );
5035
+ $documentID = isset($options['document'])
5036
+ ? self::getDocumentID($options['document'])
5037
+ : null;
5038
+ if ($xhr) {
5039
+ // reuse existing XHR object, but clean it up
5040
+ $client = $xhr;
5041
+ // $client->setParameterPost(null);
5042
+ // $client->setParameterGet(null);
5043
+ $client->setAuth(false);
5044
+ $client->setHeaders("If-Modified-Since", null);
5045
+ $client->setHeaders("Referer", null);
5046
+ $client->resetParameters();
5047
+ } else {
5048
+ // create new XHR object
5049
+ require_once('Zend/Http/Client.php');
5050
+ $client = new Zend_Http_Client();
5051
+ $client->setCookieJar();
5052
+ }
5053
+ if (isset($options['timeout']))
5054
+ $client->setConfig(array(
5055
+ 'timeout' => $options['timeout'],
5056
+ ));
5057
+ // 'maxredirects' => 0,
5058
+ foreach(self::$ajaxAllowedHosts as $k => $host)
5059
+ if ($host == '.' && isset($_SERVER['HTTP_HOST']))
5060
+ self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST'];
5061
+ $host = parse_url($options['url'], PHP_URL_HOST);
5062
+ if (! in_array($host, self::$ajaxAllowedHosts)) {
5063
+ throw new Exception("Request not permitted, host '$host' not present in "
5064
+ ."phpQuery::\$ajaxAllowedHosts");
5065
+ }
5066
+ // JSONP
5067
+ $jsre = "/=\\?(&|$)/";
5068
+ if (isset($options['dataType']) && $options['dataType'] == 'jsonp') {
5069
+ $jsonpCallbackParam = $options['jsonp']
5070
+ ? $options['jsonp'] : 'callback';
5071
+ if (strtolower($options['type']) == 'get') {
5072
+ if (! preg_match($jsre, $options['url'])) {
5073
+ $sep = strpos($options['url'], '?')
5074
+ ? '&' : '?';
5075
+ $options['url'] .= "$sep$jsonpCallbackParam=?";
5076
+ }
5077
+ } else if ($options['data']) {
5078
+ $jsonp = false;
5079
+ foreach($options['data'] as $n => $v) {
5080
+ if ($v == '?')
5081
+ $jsonp = true;
5082
+ }
5083
+ if (! $jsonp) {
5084
+ $options['data'][$jsonpCallbackParam] = '?';
5085
+ }
5086
+ }
5087
+ $options['dataType'] = 'json';
5088
+ }
5089
+ if (isset($options['dataType']) && $options['dataType'] == 'json') {
5090
+ $jsonpCallback = 'json_'.md5(microtime());
5091
+ $jsonpData = $jsonpUrl = false;
5092
+ if ($options['data']) {
5093
+ foreach($options['data'] as $n => $v) {
5094
+ if ($v == '?')
5095
+ $jsonpData = $n;
5096
+ }
5097
+ }
5098
+ if (preg_match($jsre, $options['url']))
5099
+ $jsonpUrl = true;
5100
+ if ($jsonpData !== false || $jsonpUrl) {
5101
+ // remember callback name for httpData()
5102
+ $options['_jsonp'] = $jsonpCallback;
5103
+ if ($jsonpData !== false)
5104
+ $options['data'][$jsonpData] = $jsonpCallback;
5105
+ if ($jsonpUrl)
5106
+ $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']);
5107
+ }
5108
+ }
5109
+ $client->setUri($options['url']);
5110
+ $client->setMethod(strtoupper($options['type']));
5111
+ if (isset($options['referer']) && $options['referer'])
5112
+ $client->setHeaders('Referer', $options['referer']);
5113
+ $client->setHeaders(array(
5114
+ // 'content-type' => $options['contentType'],
5115
+ 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko'
5116
+ .'/2008122010 Firefox/3.0.5',
5117
+ // TODO custom charset
5118
+ 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
5119
+ // 'Connection' => 'keep-alive',
5120
+ // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
5121
+ 'Accept-Language' => 'en-us,en;q=0.5',
5122
+ ));
5123
+ if ($options['username'])
5124
+ $client->setAuth($options['username'], $options['password']);
5125
+ if (isset($options['ifModified']) && $options['ifModified'])
5126
+ $client->setHeaders("If-Modified-Since",
5127
+ self::$lastModified
5128
+ ? self::$lastModified
5129
+ : "Thu, 01 Jan 1970 00:00:00 GMT"
5130
+ );
5131
+ $client->setHeaders("Accept",
5132
+ isset($options['dataType'])
5133
+ && isset(self::$ajaxSettings['accepts'][ $options['dataType'] ])
5134
+ ? self::$ajaxSettings['accepts'][ $options['dataType'] ].", */*"
5135
+ : self::$ajaxSettings['accepts']['_default']
5136
+ );
5137
+ // TODO $options['processData']
5138
+ if ($options['data'] instanceof phpQueryObject) {
5139
+ $serialized = $options['data']->serializeArray($options['data']);
5140
+ $options['data'] = array();
5141
+ foreach($serialized as $r)
5142
+ $options['data'][ $r['name'] ] = $r['value'];
5143
+ }
5144
+ if (strtolower($options['type']) == 'get') {
5145
+ $client->setParameterGet($options['data']);
5146
+ } else if (strtolower($options['type']) == 'post') {
5147
+ $client->setEncType($options['contentType']);
5148
+ $client->setParameterPost($options['data']);
5149
+ }
5150
+ if (self::$active == 0 && $options['global'])
5151
+ phpQueryEvents::trigger($documentID, 'ajaxStart');
5152
+ self::$active++;
5153
+ // beforeSend callback
5154
+ if (isset($options['beforeSend']) && $options['beforeSend'])
5155
+ phpQuery::callbackRun($options['beforeSend'], array($client));
5156
+ // ajaxSend event
5157
+ if ($options['global'])
5158
+ phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options));
5159
+ if (phpQuery::$debug) {
5160
+ self::debug("{$options['type']}: {$options['url']}\n");
5161
+ self::debug("Options: <pre>".var_export($options, true)."</pre>\n");
5162
+ // if ($client->getCookieJar())
5163
+ // self::debug("Cookies: <pre>".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."</pre>\n");
5164
+ }
5165
+ // request
5166
+ $response = $client->request();
5167
+ if (phpQuery::$debug) {
5168
+ self::debug('Status: '.$response->getStatus().' / '.$response->getMessage());
5169
+ self::debug($client->getLastRequest());
5170
+ self::debug($response->getHeaders());
5171
+ }
5172
+ if ($response->isSuccessful()) {
5173
+ // XXX tempolary
5174
+ self::$lastModified = $response->getHeader('Last-Modified');
5175
+ $data = self::httpData($response->getBody(), $options['dataType'], $options);
5176
+ if (isset($options['success']) && $options['success'])
5177
+ phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options));
5178
+ if ($options['global'])
5179
+ phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options));
5180
+ } else {
5181
+ if (isset($options['error']) && $options['error'])
5182
+ phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage()));
5183
+ if ($options['global'])
5184
+ phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/$response->getMessage(), $options));
5185
+ }
5186
+ if (isset($options['complete']) && $options['complete'])
5187
+ phpQuery::callbackRun($options['complete'], array($client, $response->getStatus()));
5188
+ if ($options['global'])
5189
+ phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options));
5190
+ if ($options['global'] && ! --self::$active)
5191
+ phpQueryEvents::trigger($documentID, 'ajaxStop');
5192
+ return $client;
5193
+ // if (is_null($domId))
5194
+ // $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false;
5195
+ // return new phpQueryAjaxResponse($response, $domId);
5196
+ }
5197
+ protected static function httpData($data, $type, $options) {
5198
+ if (isset($options['dataFilter']) && $options['dataFilter'])
5199
+ $data = self::callbackRun($options['dataFilter'], array($data, $type));
5200
+ if (is_string($data)) {
5201
+ if ($type == "json") {
5202
+ if (isset($options['_jsonp']) && $options['_jsonp']) {
5203
+ $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data);
5204
+ }
5205
+ $data = self::parseJSON($data);
5206
+ }
5207
+ }
5208
+ return $data;
5209
+ }
5210
+ /**
5211
+ * Enter description here...
5212
+ *
5213
+ * @param array|phpQuery $data
5214
+ *
5215
+ */
5216
+ public static function param($data) {
5217
+ return http_build_query($data, null, '&');
5218
+ }
5219
+ public static function get($url, $data = null, $callback = null, $type = null) {
5220
+ if (!is_array($data)) {
5221
+ $callback = $data;
5222
+ $data = null;
5223
+ }
5224
+ // TODO some array_values on this shit
5225
+ return phpQuery::ajax(array(
5226
+ 'type' => 'GET',
5227
+ 'url' => $url,
5228
+ 'data' => $data,
5229
+ 'success' => $callback,
5230
+ 'dataType' => $type,
5231
+ ));
5232
+ }
5233
+ public static function post($url, $data = null, $callback = null, $type = null) {
5234
+ if (!is_array($data)) {
5235
+ $callback = $data;
5236
+ $data = null;
5237
+ }
5238
+ return phpQuery::ajax(array(
5239
+ 'type' => 'POST',
5240
+ 'url' => $url,
5241
+ 'data' => $data,
5242
+ 'success' => $callback,
5243
+ 'dataType' => $type,
5244
+ ));
5245
+ }
5246
+ public static function getJSON($url, $data = null, $callback = null) {
5247
+ if (!is_array($data)) {
5248
+ $callback = $data;
5249
+ $data = null;
5250
+ }
5251
+ // TODO some array_values on this shit
5252
+ return phpQuery::ajax(array(
5253
+ 'type' => 'GET',
5254
+ 'url' => $url,
5255
+ 'data' => $data,
5256
+ 'success' => $callback,
5257
+ 'dataType' => 'json',
5258
+ ));
5259
+ }
5260
+ public static function ajaxSetup($options) {
5261
+ self::$ajaxSettings = array_merge(
5262
+ self::$ajaxSettings,
5263
+ $options
5264
+ );
5265
+ }
5266
+ public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) {
5267
+ $loop = is_array($host1)
5268
+ ? $host1
5269
+ : func_get_args();
5270
+ foreach($loop as $host) {
5271
+ if ($host && ! in_array($host, phpQuery::$ajaxAllowedHosts)) {
5272
+ phpQuery::$ajaxAllowedHosts[] = $host;
5273
+ }
5274
+ }
5275
+ }
5276
+ public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) {
5277
+ $loop = is_array($url1)
5278
+ ? $url1
5279
+ : func_get_args();
5280
+ foreach($loop as $url)
5281
+ phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST));
5282
+ }
5283
+ /**
5284
+ * Returns JSON representation of $data.
5285
+ *
5286
+ * @static
5287
+ * @param mixed $data
5288
+ * @return string
5289
+ */
5290
+ public static function toJSON($data) {
5291
+ if (function_exists('json_encode'))
5292
+ return json_encode($data);
5293
+ require_once('Zend/Json/Encoder.php');
5294
+ return Zend_Json_Encoder::encode($data);
5295
+ }
5296
+ /**
5297
+ * Parses JSON into proper PHP type.
5298
+ *
5299
+ * @static
5300
+ * @param string $json
5301
+ * @return mixed
5302
+ */
5303
+ public static function parseJSON($json) {
5304
+ if (function_exists('json_decode')) {
5305
+ $return = json_decode(trim($json), true);
5306
+ // json_decode and UTF8 issues
5307
+ if (isset($return))
5308
+ return $return;
5309
+ }
5310
+ require_once('Zend/Json/Decoder.php');
5311
+ return Zend_Json_Decoder::decode($json);
5312
+ }
5313
+ /**
5314
+ * Returns source's document ID.
5315
+ *
5316
+ * @param $source DOMNode|phpQueryObject
5317
+ * @return string
5318
+ */
5319
+ public static function getDocumentID($source) {
5320
+ if ($source instanceof DOMDOCUMENT) {
5321
+ foreach(phpQuery::$documents as $id => $document) {
5322
+ if ($source->isSameNode($document->document))
5323
+ return $id;
5324
+ }
5325
+ } else if ($source instanceof DOMNODE) {
5326
+ foreach(phpQuery::$documents as $id => $document) {
5327
+ if ($source->ownerDocument->isSameNode($document->document))
5328
+ return $id;
5329
+ }
5330
+ } else if ($source instanceof phpQueryObject)
5331
+ return $source->getDocumentID();
5332
+ else if (is_string($source) && isset(phpQuery::$documents[$source]))
5333
+ return $source;
5334
+ }
5335
+ /**
5336
+ * Get DOMDocument object related to $source.
5337
+ * Returns null if such document doesn't exist.
5338
+ *
5339
+ * @param $source DOMNode|phpQueryObject|string
5340
+ * @return string
5341
+ */
5342
+ public static function getDOMDocument($source) {
5343
+ if ($source instanceof DOMDOCUMENT)
5344
+ return $source;
5345
+ $source = self::getDocumentID($source);
5346
+ return $source
5347
+ ? self::$documents[$id]['document']
5348
+ : null;
5349
+ }
5350
+
5351
+ // UTILITIES
5352
+ // http://docs.jquery.com/Utilities
5353
+
5354
+ /**
5355
+ *
5356
+ * @return unknown_type
5357
+ * @link http://docs.jquery.com/Utilities/jQuery.makeArray
5358
+ */
5359
+ public static function makeArray($obj) {
5360
+ $array = array();
5361
+ if (is_object($object) && $object instanceof DOMNODELIST) {
5362
+ foreach($object as $value)
5363
+ $array[] = $value;
5364
+ } else if (is_object($object) && ! ($object instanceof Iterator)) {
5365
+ foreach(get_object_vars($object) as $name => $value)
5366
+ $array[0][$name] = $value;
5367
+ } else {
5368
+ foreach($object as $name => $value)
5369
+ $array[0][$name] = $value;
5370
+ }
5371
+ return $array;
5372
+ }
5373
+ public static function inArray($value, $array) {
5374
+ return in_array($value, $array);
5375
+ }
5376
+ /**
5377
+ *
5378
+ * @param $object
5379
+ * @param $callback
5380
+ * @return unknown_type
5381
+ * @link http://docs.jquery.com/Utilities/jQuery.each
5382
+ */
5383
+ public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) {
5384
+ $paramStructure = null;
5385
+ if (func_num_args() > 2) {
5386
+ $paramStructure = func_get_args();
5387
+ $paramStructure = array_slice($paramStructure, 2);
5388
+ }
5389
+ if (is_object($object) && ! ($object instanceof Iterator)) {
5390
+ foreach(get_object_vars($object) as $name => $value)
5391
+ phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
5392
+ } else {
5393
+ foreach($object as $name => $value)
5394
+ phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
5395
+ }
5396
+ }
5397
+ /**
5398
+ *
5399
+ * @link http://docs.jquery.com/Utilities/jQuery.map
5400
+ */
5401
+ public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) {
5402
+ $result = array();
5403
+ $paramStructure = null;
5404
+ if (func_num_args() > 2) {
5405
+ $paramStructure = func_get_args();
5406
+ $paramStructure = array_slice($paramStructure, 2);
5407
+ }
5408
+ foreach($array as $v) {
5409
+ $vv = phpQuery::callbackRun($callback, array($v), $paramStructure);
5410
+ // $callbackArgs = $args;
5411
+ // foreach($args as $i => $arg) {
5412
+ // $callbackArgs[$i] = $arg instanceof CallbackParam
5413
+ // ? $v
5414
+ // : $arg;
5415
+ // }
5416
+ // $vv = call_user_func_array($callback, $callbackArgs);
5417
+ if (is_array($vv)) {
5418
+ foreach($vv as $vvv)
5419
+ $result[] = $vvv;
5420
+ } else if ($vv !== null) {
5421
+ $result[] = $vv;
5422
+ }
5423
+ }
5424
+ return $result;
5425
+ }
5426
+ /**
5427
+ *
5428
+ * @param $callback Callback
5429
+ * @param $params
5430
+ * @param $paramStructure
5431
+ * @return unknown_type
5432
+ */
5433
+ public static function callbackRun($callback, $params = array(), $paramStructure = null) {
5434
+ if (! $callback)
5435
+ return;
5436
+ if ($callback instanceof CallbackParameterToReference) {
5437
+ // TODO support ParamStructure to select which $param push to reference
5438
+ if (isset($params[0]))
5439
+ $callback->callback = $params[0];
5440
+ return true;
5441
+ }
5442
+ if ($callback instanceof Callback) {
5443
+ $paramStructure = $callback->params;
5444
+ $callback = $callback->callback;
5445
+ }
5446
+ if (! $paramStructure)
5447
+ return call_user_func_array($callback, $params);
5448
+ $p = 0;
5449
+ foreach($paramStructure as $i => $v) {
5450
+ $paramStructure[$i] = $v instanceof CallbackParam
5451
+ ? $params[$p++]
5452
+ : $v;
5453
+ }
5454
+ return call_user_func_array($callback, $paramStructure);
5455
+ }
5456
+ /**
5457
+ * Merge 2 phpQuery objects.
5458
+ * @param array $one
5459
+ * @param array $two
5460
+ * @protected
5461
+ * @todo node lists, phpQueryObject
5462
+ */
5463
+ public static function merge($one, $two) {
5464
+ $elements = $one->elements;
5465
+ foreach($two->elements as $node) {
5466
+ $exists = false;
5467
+ foreach($elements as $node2) {
5468
+ if ($node2->isSameNode($node))
5469
+ $exists = true;
5470
+ }
5471
+ if (! $exists)
5472
+ $elements[] = $node;
5473
+ }
5474
+ return $elements;
5475
+ // $one = $one->newInstance();
5476
+ // $one->elements = $elements;
5477
+ // return $one;
5478
+ }
5479
+ /**
5480
+ *
5481
+ * @param $array
5482
+ * @param $callback
5483
+ * @param $invert
5484
+ * @return unknown_type
5485
+ * @link http://docs.jquery.com/Utilities/jQuery.grep
5486
+ */
5487
+ public static function grep($array, $callback, $invert = false) {
5488
+ $result = array();
5489
+ foreach($array as $k => $v) {
5490
+ $r = call_user_func_array($callback, array($v, $k));
5491
+ if ($r === !(bool)$invert)
5492
+ $result[] = $v;
5493
+ }
5494
+ return $result;
5495
+ }
5496
+ public static function unique($array) {
5497
+ return array_unique($array);
5498
+ }
5499
+ /**
5500
+ *
5501
+ * @param $function
5502
+ * @return unknown_type
5503
+ * @TODO there are problems with non-static methods, second parameter pass it
5504
+ * but doesnt verify is method is really callable
5505
+ */
5506
+ public static function isFunction($function) {
5507
+ return is_callable($function);
5508
+ }
5509
+ public static function trim($str) {
5510
+ return trim($str);
5511
+ }
5512
+ /* PLUGINS NAMESPACE */
5513
+ /**
5514
+ *
5515
+ * @param $url
5516
+ * @param $callback
5517
+ * @param $param1
5518
+ * @param $param2
5519
+ * @param $param3
5520
+ * @return phpQueryObject
5521
+ */
5522
+ public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) {
5523
+ if (self::plugin('WebBrowser')) {
5524
+ $params = func_get_args();
5525
+ return self::callbackRun(array(self::$plugins, 'browserGet'), $params);
5526
+ } else {
5527
+ self::debug('WebBrowser plugin not available...');
5528
+ }
5529
+ }
5530
+ /**
5531
+ *
5532
+ * @param $url
5533
+ * @param $data
5534
+ * @param $callback
5535
+ * @param $param1
5536
+ * @param $param2
5537
+ * @param $param3
5538
+ * @return phpQueryObject
5539
+ */
5540
+ public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) {
5541
+ if (self::plugin('WebBrowser')) {
5542
+ $params = func_get_args();
5543
+ return self::callbackRun(array(self::$plugins, 'browserPost'), $params);
5544
+ } else {
5545
+ self::debug('WebBrowser plugin not available...');
5546
+ }
5547
+ }
5548
+ /**
5549
+ *
5550
+ * @param $ajaxSettings
5551
+ * @param $callback
5552
+ * @param $param1
5553
+ * @param $param2
5554
+ * @param $param3
5555
+ * @return phpQueryObject
5556
+ */
5557
+ public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) {
5558
+ if (self::plugin('WebBrowser')) {
5559
+ $params = func_get_args();
5560
+ return self::callbackRun(array(self::$plugins, 'browser'), $params);
5561
+ } else {
5562
+ self::debug('WebBrowser plugin not available...');
5563
+ }
5564
+ }
5565
+ /**
5566
+ *
5567
+ * @param $code
5568
+ * @return string
5569
+ */
5570
+ public static function php($code) {
5571
+ return self::code('php', $code);
5572
+ }
5573
+ /**
5574
+ *
5575
+ * @param $type
5576
+ * @param $code
5577
+ * @return string
5578
+ */
5579
+ public static function code($type, $code) {
5580
+ return "<$type><!-- ".trim($code)." --></$type>";
5581
+ }
5582
+
5583
+ public static function __callStatic($method, $params) {
5584
+ return call_user_func_array(
5585
+ array(phpQuery::$plugins, $method),
5586
+ $params
5587
+ );
5588
+ }
5589
+ protected static function dataSetupNode($node, $documentID) {
5590
+ // search are return if alredy exists
5591
+ foreach(phpQuery::$documents[$documentID]->dataNodes as $dataNode) {
5592
+ if ($node->isSameNode($dataNode))
5593
+ return $dataNode;
5594
+ }
5595
+ // if doesn't, add it
5596
+ phpQuery::$documents[$documentID]->dataNodes[] = $node;
5597
+ return $node;
5598
+ }
5599
+ protected static function dataRemoveNode($node, $documentID) {
5600
+ // search are return if alredy exists
5601
+ foreach(phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) {
5602
+ if ($node->isSameNode($dataNode)) {
5603
+ unset(self::$documents[$documentID]->dataNodes[$k]);
5604
+ unset(self::$documents[$documentID]->data[ $dataNode->dataID ]);
5605
+ }
5606
+ }
5607
+ }
5608
+ public static function data($node, $name, $data, $documentID = null) {
5609
+ if (! $documentID)
5610
+ // TODO check if this works
5611
+ $documentID = self::getDocumentID($node);
5612
+ $document = phpQuery::$documents[$documentID];
5613
+ $node = self::dataSetupNode($node, $documentID);
5614
+ if (! isset($node->dataID))
5615
+ $node->dataID = ++phpQuery::$documents[$documentID]->uuid;
5616
+ $id = $node->dataID;
5617
+ if (! isset($document->data[$id]))
5618
+ $document->data[$id] = array();
5619
+ if (! is_null($data))
5620
+ $document->data[$id][$name] = $data;
5621
+ if ($name) {
5622
+ if (isset($document->data[$id][$name]))
5623
+ return $document->data[$id][$name];
5624
+ } else
5625
+ return $id;
5626
+ }
5627
+ public static function removeData($node, $name, $documentID) {
5628
+ if (! $documentID)
5629
+ // TODO check if this works
5630
+ $documentID = self::getDocumentID($node);
5631
+ $document = phpQuery::$documents[$documentID];
5632
+ $node = self::dataSetupNode($node, $documentID);
5633
+ $id = $node->dataID;
5634
+ if ($name) {
5635
+ if (isset($document->data[$id][$name]))
5636
+ unset($document->data[$id][$name]);
5637
+ $name = null;
5638
+ foreach($document->data[$id] as $name)
5639
+ break;
5640
+ if (! $name)
5641
+ self::removeData($node, $name, $documentID);
5642
+ } else {
5643
+ self::dataRemoveNode($node, $documentID);
5644
+ }
5645
+ }
5646
+ }
5647
+ /**
5648
+ * Plugins static namespace class.
5649
+ *
5650
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
5651
+ * @package phpQuery
5652
+ * @todo move plugin methods here (as statics)
5653
+ */
5654
+ class phpQueryPlugins {
5655
+ public function __call($method, $args) {
5656
+ if (isset(phpQuery::$extendStaticMethods[$method])) {
5657
+ $return = call_user_func_array(
5658
+ phpQuery::$extendStaticMethods[$method],
5659
+ $args
5660
+ );
5661
+ } else if (isset(phpQuery::$pluginsStaticMethods[$method])) {
5662
+ $class = phpQuery::$pluginsStaticMethods[$method];
5663
+ $realClass = "phpQueryPlugin_$class";
5664
+ $return = call_user_func_array(
5665
+ array($realClass, $method),
5666
+ $args
5667
+ );
5668
+ return isset($return)
5669
+ ? $return
5670
+ : $this;
5671
+ } else
5672
+ throw new Exception("Method '{$method}' doesnt exist");
5673
+ }
5674
+ }
5675
+ /**
5676
+ * Shortcut to phpQuery::pq($arg1, $context)
5677
+ * Chainable.
5678
+ *
5679
+ * @see phpQuery::pq()
5680
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
5681
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
5682
+ * @package phpQuery
5683
+ */
5684
+ function pq($arg1, $context = null) {
5685
+ $args = func_get_args();
5686
+ return call_user_func_array(
5687
+ array('phpQuery', 'pq'),
5688
+ $args
5689
+ );
5690
+ }
5691
+ // add plugins dir and Zend framework to include path
5692
+ set_include_path(
5693
+ get_include_path()
5694
+ .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/'
5695
+ .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/plugins/'
5696
+ );
5697
+ // why ? no __call nor __get for statics in php...
5698
+ // XXX __callStatic will be available in PHP 5.3
5699
+ phpQuery::$plugins = new phpQueryPlugins();
5700
+ // include bootstrap file (personal library config)
5701
+ if (file_exists(dirname(__FILE__).'/phpQuery/bootstrap.php'))
5702
+ require_once dirname(__FILE__).'/phpQuery/bootstrap.php';
js/ad-inserter.js CHANGED
@@ -1,4 +1,4 @@
1
- var javascript_version = "2.3.0";
2
  var ignore_key = true;
3
  var start = 1;
4
  var end = 16;
@@ -382,7 +382,8 @@ function window_open_post (url, windowoption, name, params) {
382
  document.body.appendChild(form);
383
  //note I am using a post.htm page since I did not want to make double request to the page
384
  //it might have some Page_Load call which might screw things up.
385
- window.open("post.htm", name, windowoption);
 
386
  form.submit();
387
  document.body.removeChild(form);
388
  }
@@ -2921,6 +2922,8 @@ jQuery(document).ready(function($) {
2921
  $('#adsense-list-controls').show ();
2922
  data_container.text ('Loading...');
2923
 
 
 
2924
  update_adsense_authorization (authorization_code);
2925
  });
2926
 
1
+ var javascript_version = "2.3.1";
2
  var ignore_key = true;
3
  var start = 1;
4
  var end = 16;
382
  document.body.appendChild(form);
383
  //note I am using a post.htm page since I did not want to make double request to the page
384
  //it might have some Page_Load call which might screw things up.
385
+ // window.open ("post.htm", name, windowoption);
386
+ window.open ("admin-ajax.php", name, windowoption);
387
  form.submit();
388
  document.body.removeChild(form);
389
  }
2922
  $('#adsense-list-controls').show ();
2923
  data_container.text ('Loading...');
2924
 
2925
+ if ($(this).hasClass ('clear-adsense')) authorization_code = '';
2926
+
2927
  update_adsense_authorization (authorization_code);
2928
  });
2929
 
readme.txt CHANGED
@@ -6,7 +6,7 @@ Tags: ads, adsense, ad management, advertising manager, advanced contextual ads,
6
  Requires at least: 4.0
7
  Tested up to: 4.9
8
  Requires PHP: 5.3
9
- Stable tag: 2.2.16
10
  License: GPLv3
11
 
12
  Insert and manage ads: AdSense, Amazon, banners, ad rotation, sticky ad widgets, shortcodes, AMP, PHP, HTML, CSS, form, tracking, header, footer code
@@ -15,7 +15,9 @@ Insert and manage ads: AdSense, Amazon, banners, ad rotation, sticky ad widgets,
15
 
16
  Ad management plugin with many advanced advertising features. Supports all kinds of WordPress ads including **Google AdSense**, contextual **Amazon Native Shopping Ads, Media.net** and **rotating banners.**
17
 
18
- Ad Inserter is more than just ad manager plugin. It provides many advanced options to insert any Javascript, CSS, HTML, PHP or advert code anywhere on the page. **Ad Inserter can insert ads where other plugins fail**.
 
 
19
 
20
  **Features**
21
 
@@ -31,11 +33,10 @@ Ad Inserter is more than just ad manager plugin. It provides many advanced optio
31
  * Insert before or after multiple paragraphs
32
  * Insert before or after comments
33
  * Insert before or after excerpt
34
- * Insert before or after any HTML element in post
35
  * Insert before or after any HTML element on page
36
  * Insert above header (after `<body>` tag)
37
  * Insert in footer (before `</body>` tag)
38
- * Insert at relative position in posts
39
  * Insert between posts on blog pages (in-feed AdSense ads)
40
  * Insert between excerpts on blog pages
41
  * Insert between comments
@@ -47,11 +48,11 @@ Ad Inserter is more than just ad manager plugin. It provides many advanced optio
47
  * Insert Google Analytics, Piwik or any other web analytics code
48
  * Insert HTML, CSS, Javascript or PHP code
49
  * Code generator for banners and placeholders
50
- * Visual ad editor
51
  * Manual insertion: widgets, shortcodes, PHP function call
52
  * Sticky (fixed) widgets (sticky sidebar - the sidebar does not move when the page is scrolled)
53
  * Custom block alignments and styles
54
- * Insert (different) ads on AMP pages
55
  * Custom CSS class name for wrapping divs to avoid ad blockers
56
  * Use shortcodes from other plugins
57
  * Use custom fields as defined in posts
@@ -95,15 +96,23 @@ Ad Inserter Wordpress plugin is an advanced advertising manager - it has many fe
95
 
96
  **AdSense integration**
97
 
98
- Get AdSense ad codes from the plugin settings page - no need to copy ad codes from the AdSense admin pages and paste them to code blocks. List ad units, preview AdSense ads and get ad codes where you need them.
 
 
 
 
 
 
 
 
99
 
100
  **Endorsed by Amazon**
101
 
102
  Amazon suggests to use Ad Inserter to add Native Shopping Ads to Wordpress posts.
103
 
104
  > Native Shopping Ads provide highly relevant and dynamic product recommendations in a stylishly designed and responsive ad unit that can be placed at the end of your content or within your content to create a more compelling visitor experience and shopping opportunity.
105
- >
106
- > Check <a href="https://affiliate-program.amazon.com/help/topic/t405" target="_blank">Wordpress Integration Guide for Native Shopping Ads</a>. Ad Inserter also supports advanced contextual ads: check settings for <a href="https://adinserter.pro/settings#amazon">contextual Native Shopping Ads</a> that show items related to the post content (using post tags).
107
 
108
  **Ad Inserter Ad Manager - One Plugin - Many Functions**
109
 
@@ -136,15 +145,6 @@ Maintaining several plugins (often from different vendors) is not easy. And each
136
 
137
  **Speed Up Your WordPress: Replace them all with free Ad Inserter!**
138
 
139
- **Insert Any Code Anywhere on the Page**
140
-
141
- * Automatic insertion using standard WP hooks (the_content, the_excerpt, loop_start, loop_end, the_post,...)
142
- * Automatic insertion using custom WP hooks provided by themes (e.g. using <a href="https://genesistutorials.com/visual-hook-guide/" target="_blank">Genesis Theme Framework Hooks</a>)
143
- * Client-side (javascript) insertion before or after **any HTML element on page**
144
- * Manual insertion with shortcodes
145
- * Manual insertion with widgets
146
- * Insertion by placing PHP function calls or custom hooks in your theme files
147
-
148
  **Quick Start**
149
 
150
  Few very important things you need to know in order to <a href="http://adinserter.pro/documentation#quick-start">insert code and display some ad</a>:
@@ -160,6 +160,20 @@ Few very important things you need to know in order to <a href="http://adinserte
160
 
161
  Few typical settings are described on the <a href="https://adinserter.pro/settings" target="_blank">Common Settings</a> page. Please make sure you have also read <a href="https://wordpress.org/plugins/ad-inserter/installation/">Installation</a> page. For more detailed instructions please read <a href="http://adinserter.pro/documentation" target="_blank">Ad Inserter documentation page</a>.
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  **Please support plugin development**
164
 
165
  If you are using Ad Inserter and you like it, then please write about it and spread the word on the <a href="https://wordpress.org/support/plugin/ad-inserter/reviews/">review page</a>.
@@ -168,7 +182,7 @@ Positive reviews are a great way to show your appreciation for my work. Besides
168
 
169
  Support the advancement of this plugin:
170
 
171
- * Write a nice <a href="https://wordpress.org/support/plugin/ad-inserter/reviews/">review</a>
172
  * <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=LHGZEMRTR7WB4">Donate</a>
173
  * Buy license for Ad Inserter Pro - all-in-one <a href="http://adinserter.pro/" target="_blank">Wordpress plugin for ads</a>
174
 
@@ -769,6 +783,10 @@ AD CODE RIGHT
769
 
770
  == Changelog ==
771
 
 
 
 
 
772
  = 2.3.0 =
773
  - Added support for client-side insertion before/after any HTML element
774
  - Inplemented AdSense integration
@@ -963,6 +981,10 @@ AD CODE RIGHT
963
 
964
  == Upgrade Notice ==
965
 
 
 
 
 
966
  = 2.3.0 =
967
  Added support for client-side insertion before/after any HTML element;
968
  Inplemented AdSense integration;
6
  Requires at least: 4.0
7
  Tested up to: 4.9
8
  Requires PHP: 5.3
9
+ Stable tag: 2.3.0
10
  License: GPLv3
11
 
12
  Insert and manage ads: AdSense, Amazon, banners, ad rotation, sticky ad widgets, shortcodes, AMP, PHP, HTML, CSS, form, tracking, header, footer code
15
 
16
  Ad management plugin with many advanced advertising features. Supports all kinds of WordPress ads including **Google AdSense**, contextual **Amazon Native Shopping Ads, Media.net** and **rotating banners.**
17
 
18
+ Ad Inserter is more than just ad manager plugin. It provides many advanced options to insert any opt-in form, Javascript, CSS, HTML, PHP, analytics, tracking or advert code anywhere on the page.
19
+
20
+ **Ad Inserter can insert ads where other plugins fail**. It's all about the settings.
21
 
22
  **Features**
23
 
33
  * Insert before or after multiple paragraphs
34
  * Insert before or after comments
35
  * Insert before or after excerpt
 
36
  * Insert before or after any HTML element on page
37
  * Insert above header (after `<body>` tag)
38
  * Insert in footer (before `</body>` tag)
39
+ * Insert at relative positions in posts
40
  * Insert between posts on blog pages (in-feed AdSense ads)
41
  * Insert between excerpts on blog pages
42
  * Insert between comments
48
  * Insert Google Analytics, Piwik or any other web analytics code
49
  * Insert HTML, CSS, Javascript or PHP code
50
  * Code generator for banners and placeholders
51
+ * Visual advert editor
52
  * Manual insertion: widgets, shortcodes, PHP function call
53
  * Sticky (fixed) widgets (sticky sidebar - the sidebar does not move when the page is scrolled)
54
  * Custom block alignments and styles
55
+ * Insert ads (different ad code) on AMP pages
56
  * Custom CSS class name for wrapping divs to avoid ad blockers
57
  * Use shortcodes from other plugins
58
  * Use custom fields as defined in posts
96
 
97
  **AdSense integration**
98
 
99
+ Get <a href="https://adinserter.pro/adsense-ads#integration">AdSense ad codes</a> from the plugin settings page - no need to copy ad codes from the AdSense admin pages and paste them to code blocks. List ad units, preview AdSense ads and get ad codes where you need them.
100
+
101
+ **Endorsed by WordPress users**
102
+
103
+ Average Rating: 5 out of 5 stars
104
+
105
+ > Perfect plugin to insert anything anywhere on your WordPress website
106
+
107
+ Check Ad Inserter <a href="https://wordpress.org/support/plugin/ad-inserter/reviews/">reviews</a>
108
 
109
  **Endorsed by Amazon**
110
 
111
  Amazon suggests to use Ad Inserter to add Native Shopping Ads to Wordpress posts.
112
 
113
  > Native Shopping Ads provide highly relevant and dynamic product recommendations in a stylishly designed and responsive ad unit that can be placed at the end of your content or within your content to create a more compelling visitor experience and shopping opportunity.
114
+
115
+ Check <a href="https://affiliate-program.amazon.com/help/topic/t405" target="_blank">Wordpress Integration Guide for Native Shopping Ads</a>. Ad Inserter supports also advanced contextual ads: check settings for <a href="https://adinserter.pro/settings#amazon">contextual Native Shopping Ads</a> that show items related to the post content (using post tags).
116
 
117
  **Ad Inserter Ad Manager - One Plugin - Many Functions**
118
 
145
 
146
  **Speed Up Your WordPress: Replace them all with free Ad Inserter!**
147
 
 
 
 
 
 
 
 
 
 
148
  **Quick Start**
149
 
150
  Few very important things you need to know in order to <a href="http://adinserter.pro/documentation#quick-start">insert code and display some ad</a>:
160
 
161
  Few typical settings are described on the <a href="https://adinserter.pro/settings" target="_blank">Common Settings</a> page. Please make sure you have also read <a href="https://wordpress.org/plugins/ad-inserter/installation/">Installation</a> page. For more detailed instructions please read <a href="http://adinserter.pro/documentation" target="_blank">Ad Inserter documentation page</a>.
162
 
163
+ **Insert Any Code Anywhere on the Page**
164
+
165
+ * Automatic insertion using standard WP hooks (the_content, the_excerpt, loop_start, loop_end, the_post,...)
166
+ * Automatic insertion using custom WP hooks provided by themes (e.g. using <a href="https://genesistutorials.com/visual-hook-guide/" target="_blank">Genesis Theme Framework Hooks</a>)
167
+ * Server-side or client-side (javascript) insertion before or after **any HTML element on page** (using selectors #id, .class)
168
+ * Manual insertion with shortcodes
169
+ * Insertion with widgets
170
+ * Insertion by placing PHP function calls or custom hooks in your theme files
171
+
172
+ Ad Inserter is the only WordPress plugin that can insert any code anywhere on the page. If standard insertions before/after post, content or paragraph fail you can use advanced insertion before/after arbitrary HTML element.
173
+ If you can create a CSS slector for it (#id, .class) then you can insert before or after that element. By default this approach uses client-side insertion using Javascript to insert code block after the page loads.
174
+ However, if you enable Output buffering (tab * / tab General) you can select also Server-side insertion which will insert code block the same way as other insertions when the plugin is called and the page is created.
175
+ Please note that **Output buffering** may not work with all themes - in such case you can still use client-side insertion.
176
+
177
  **Please support plugin development**
178
 
179
  If you are using Ad Inserter and you like it, then please write about it and spread the word on the <a href="https://wordpress.org/support/plugin/ad-inserter/reviews/">review page</a>.
182
 
183
  Support the advancement of this plugin:
184
 
185
+ * Write a short <a href="https://wordpress.org/support/plugin/ad-inserter/reviews/">review</a>
186
  * <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=LHGZEMRTR7WB4">Donate</a>
187
  * Buy license for Ad Inserter Pro - all-in-one <a href="http://adinserter.pro/" target="_blank">Wordpress plugin for ads</a>
188
 
783
 
784
  == Changelog ==
785
 
786
+ = 2.3.1 =
787
+ - Added support for server-side insertion before/after any HTML element
788
+ - Few minor bug fixes
789
+
790
  = 2.3.0 =
791
  - Added support for client-side insertion before/after any HTML element
792
  - Inplemented AdSense integration
981
 
982
  == Upgrade Notice ==
983
 
984
+ = 2.3.1 =
985
+ Added support for server-side insertion before/after any HTML element;
986
+ Few minor bug fixes
987
+
988
  = 2.3.0 =
989
  Added support for client-side insertion before/after any HTML element;
990
  Inplemented AdSense integration;
settings.php CHANGED
@@ -969,7 +969,7 @@ function generate_settings_form (){
969
  <option value="<?php echo AI_HTML_INSERTION_CLIENT_SIDE; ?>" <?php echo ($html_element_insertion == AI_HTML_INSERTION_CLIENT_SIDE) ? AD_SELECT_SELECTED : AD_EMPTY_VALUE; ?>><?php echo AI_TEXT_CLIENT_SIDE; ?></option>
970
  <option value="<?php echo AI_HTML_INSERTION_CLIENT_SIDE_DOM_READY; ?>" <?php echo ($html_element_insertion == AI_HTML_INSERTION_CLIENT_SIDE_DOM_READY) ? AD_SELECT_SELECTED : AD_EMPTY_VALUE; ?>><?php echo AI_TEXT_CLIENT_SIDE_DOM_READY; ?></option>
971
  <?php if (defined ('AI_BUFFERING') && get_output_buffering ()) : ?>
972
- <!-- <option value="<?php echo AI_HTML_INSERTION_SEREVR_SIDE; ?>" <?php echo ($html_element_insertion == AI_HTML_INSERTION_SEREVR_SIDE) ? AD_SELECT_SELECTED : AD_EMPTY_VALUE; ?>><?php echo AI_TEXT_SERVER_SIDE; ?></option>-->
973
  <?php endif; ?>
974
  </select>
975
  </span>
@@ -2797,7 +2797,7 @@ function adsense_list () {
2797
  </tr>
2798
  <tr>
2799
  <td>
2800
- <button type="button" class="ai-top-button authorize-adsense" style="display: none; float: left; width: 162px; outline-color: transparent;">Clear and return to Step 1</button>
2801
  </td>
2802
  <td>
2803
  <button type="button" class="ai-top-button authorize-adsense" style="display: none; float: right; width: 162px; outline-color: transparent;">Authorize</button>
969
  <option value="<?php echo AI_HTML_INSERTION_CLIENT_SIDE; ?>" <?php echo ($html_element_insertion == AI_HTML_INSERTION_CLIENT_SIDE) ? AD_SELECT_SELECTED : AD_EMPTY_VALUE; ?>><?php echo AI_TEXT_CLIENT_SIDE; ?></option>
970
  <option value="<?php echo AI_HTML_INSERTION_CLIENT_SIDE_DOM_READY; ?>" <?php echo ($html_element_insertion == AI_HTML_INSERTION_CLIENT_SIDE_DOM_READY) ? AD_SELECT_SELECTED : AD_EMPTY_VALUE; ?>><?php echo AI_TEXT_CLIENT_SIDE_DOM_READY; ?></option>
971
  <?php if (defined ('AI_BUFFERING') && get_output_buffering ()) : ?>
972
+ <option value="<?php echo AI_HTML_INSERTION_SEREVR_SIDE; ?>" <?php echo ($html_element_insertion == AI_HTML_INSERTION_SEREVR_SIDE) ? AD_SELECT_SELECTED : AD_EMPTY_VALUE; ?>><?php echo AI_TEXT_SERVER_SIDE; ?></option>
973
  <?php endif; ?>
974
  </select>
975
  </span>
2797
  </tr>
2798
  <tr>
2799
  <td>
2800
+ <button type="button" class="ai-top-button authorize-adsense clear-adsense" style="display: none; float: left; width: 162px; outline-color: transparent;">Clear and return to Step 1</button>
2801
  </td>
2802
  <td>
2803
  <button type="button" class="ai-top-button authorize-adsense" style="display: none; float: right; width: 162px; outline-color: transparent;">Authorize</button>