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

Version Description

  • Added option to easily disable insertion of individual code block
  • Changes for compatibility with PHP 7.2
  • Added non-interaction parameter to external tracking (Pro only)
  • Few minor bug fixes, cosmetic changes and code improvements
Download this release

Release Info

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

Code changes from version 2.3.8 to 2.3.9

ad-inserter.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  /*
4
  Plugin Name: Ad Inserter
5
- Version: 2.3.8
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,8 +13,14 @@ Plugin URI: http://adinserter.pro/documentation
13
 
14
  Change Log
15
 
 
 
 
 
 
 
16
  Ad Inserter 2.3.8 - 2018-04-17
17
- - Added support rotation option shares
18
  - Added support for sticky ad settings and animations (Pro only)
19
  - Few minor bug fixes, cosmetic changes and code improvements
20
 
@@ -1079,6 +1085,7 @@ function ai_block_insertion_status ($block, $ai_last_check) {
1079
  case AI_CHECK_MIN_NUMBER_OF_WORDS: $status .= "MIN NUMBER OF WORDS " . intval ($obj->get_minimum_words()); break;
1080
  case AI_CHECK_MAX_NUMBER_OF_WORDS: $status .= "MAX NUMBER OF WORDS " . (intval ($obj->get_maximum_words()) == 0 ? 1000000 : intval ($obj->get_maximum_words())); break;
1081
  case AI_CHECK_DEBUG_NO_INSERTION: $status .= "DEBUG NO INSERTION"; break;
 
1082
  case AI_CHECK_PARAGRAPH_TAGS: $status .= "PARAGRAPH TAGS"; break;
1083
  case AI_CHECK_PARAGRAPHS_WITH_TAGS: $status .= "PARAGRAPHS WITH TAGS"; break;
1084
  case AI_CHECK_PARAGRAPHS_AFTER_NO_COUNTING_INSIDE: $status .= "PARAGRAPHS AFTER NO COUNTING INSIDE"; break;
@@ -1093,9 +1100,9 @@ function ai_block_insertion_status ($block, $ai_last_check) {
1093
  case AI_CHECK_AD_BELOW: $status .= "PARAGRAPH CLEARANCE BELOW"; break;
1094
  case AI_CHECK_SHORTCODE_ATTRIBUTES: $status .= "SHORTCODE ATTRIBUTES"; break;
1095
 
1096
- case AI_CHECK_ENABLED_PHP: $status .= "ENABLED PHP FUNCTION"; break;
1097
- case AI_CHECK_ENABLED_SHORTCODE: $status .= "ENABLED SHORTCODE"; break;
1098
- case AI_CHECK_ENABLED_WIDGET: $status .= "ENABLED WIDGET"; break;
1099
 
1100
  case AI_CHECK_NONE: $status = "BLOCK $block"; break;
1101
  default: $status .= "?"; break;
@@ -1206,7 +1213,15 @@ function ai_buffering_end () {
1206
  $body = $matches [2];
1207
 
1208
  if (isset ($ai_db_options_extract [HTML_ELEMENT_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]) && class_exists ('DOMDocument')) {
1209
- require_once ('includes/phpQuery.php');
 
 
 
 
 
 
 
 
1210
 
1211
  $no_closing_tag = array ('img', 'hr', 'br');
1212
  $multibyte = get_paragraph_counting_functions() == AI_MULTIBYTE_PARAGRAPH_COUNTING_FUNCTIONS;
@@ -4661,6 +4676,7 @@ function generate_alignment_css () {
4661
 
4662
  function generate_debug_css () {
4663
  ?>
 
4664
  .ai-debug-tags {font-weight: bold; color: white; padding: 2px;}
4665
  .ai-debug-positions {clear: both; text-align: center; padding: 10px 0; font-family: arial; font-weight: bold; border: 1px solid blue; color: blue; background: #eef;}
4666
  .ai-debug-page-type {text-align: center; padding: 10px 0; font-family: arial; font-weight: bold; border: 1px solid green; color: green; background: #efe;}
@@ -4689,9 +4705,9 @@ a.ai-debug-center {text-align: center; font-size: 10px; text-decoration: none; c
4689
  .ai-info-1 {background: #000; color: #fff;}
4690
  .ai-info-2 {background: #fff; color: #000;}
4691
 
4692
- section.ai-debug-block {padding: 0; margin: 0;
4693
- }
4694
- .ai-debug-code {margin: 0; padding: 0; border: 0; font-family: monospace; font-size: 12px; line-height: 13px; background: #fff; color: #000;}
4695
 
4696
  .ai-debug-block {border: 1px solid;}
4697
 
@@ -4736,7 +4752,10 @@ section.ai-debug-block {padding: 0; margin: 0;
4736
 
4737
  .ai-debug-message {text-align: center; font-weight: bold;}
4738
 
4739
- .ai-debug-bar kbd {padding: 0; color: #fff; font-size: inherit; font-family: arial; background-color: transparent; box-shadow: none;}
 
 
 
4740
 
4741
  <?php
4742
  }
@@ -5155,6 +5174,9 @@ function ai_adinserter ($ad_number = '', $ignore = ''){
5155
  if (!$obj->check_post_page_exceptions ($selected_blocks)) return "";
5156
  }
5157
 
 
 
 
5158
  // Last check before counter check before insertion
5159
  $ai_last_check = AI_CHECK_CODE;
5160
  if ($obj->ai_getCode () == '') return "";
@@ -5264,6 +5286,9 @@ function ai_content_hook ($content = '') {
5264
  $ai_last_check = AI_CHECK_DISABLED_MANUALLY;
5265
  if ($obj->display_disabled ($content)) continue;
5266
 
 
 
 
5267
  // Last check before counter check before insertion
5268
  $ai_last_check = AI_CHECK_CODE;
5269
  if ($obj->ai_getCode () == '') continue;
@@ -5411,6 +5436,9 @@ function ai_excerpt_hook ($content = '') {
5411
  $ai_last_check = AI_CHECK_DISABLED_MANUALLY;
5412
  if ($obj->display_disabled ($content)) continue;
5413
 
 
 
 
5414
  // Last check before counter check before insertion
5415
  $ai_last_check = AI_CHECK_CODE;
5416
  if ($obj->ai_getCode () == '') continue;
@@ -5544,6 +5572,9 @@ function ai_comment_callback ($comment, $args, $depth) {
5544
  if (!$obj->check_page_types_lists_users ()) continue;
5545
  // No filter check
5546
 
 
 
 
5547
  // Last check before counter check before insertion
5548
  $ai_last_check = AI_CHECK_CODE;
5549
  if ($obj->ai_getCode () == '') continue;
@@ -5637,6 +5668,9 @@ function ai_comment_end_callback ($comment, $args, $depth) {
5637
  if (!$obj->check_page_types_lists_users ()) continue;
5638
  // No filter check
5639
 
 
 
 
5640
  // Last check before counter check before insertion
5641
  $ai_last_check = AI_CHECK_CODE;
5642
  if ($obj->ai_getCode () == '') continue;
@@ -5687,6 +5721,9 @@ function ai_comment_end_callback ($comment, $args, $depth) {
5687
  if (!$obj->check_page_types_lists_users ()) continue;
5688
  if (!$obj->check_filter ($ad_inserter_globals [AI_COMMENT_COUNTER_NAME])) continue;
5689
 
 
 
 
5690
  // Last check before counter check before insertion
5691
  $ai_last_check = AI_CHECK_CODE;
5692
  if ($obj->ai_getCode () == '') continue;
@@ -5777,6 +5814,9 @@ function ai_custom_hook ($action, $name, $hook_parameter = null, $hook_check = n
5777
  if (!$obj->check_filter ($ad_inserter_globals [$globals_name])) continue;
5778
  if (!$obj->check_number_of_words ()) continue;
5779
 
 
 
 
5780
  // Last check before counter check before insertion
5781
  $ai_last_check = AI_CHECK_CODE;
5782
  if ($obj->ai_getCode () == '') continue;
@@ -5989,6 +6029,9 @@ function ai_process_shortcode (&$block, $atts) {
5989
  }
5990
  }
5991
 
 
 
 
5992
  // Last check before counter check before insertion
5993
  $ai_last_check = AI_CHECK_CODE;
5994
  if ($obj->ai_getCode () == '') return "";
@@ -6217,6 +6260,9 @@ function ai_widget_draw ($args, $instance, &$block) {
6217
  if (!$obj->check_post_page_exceptions ($selected_blocks)) return;
6218
  }
6219
 
 
 
 
6220
  // Last check before counter check before insertion
6221
  $ai_last_check = AI_CHECK_CODE;
6222
  if ($obj->ai_getCode () == '') {
2
 
3
  /*
4
  Plugin Name: Ad Inserter
5
+ Version: 2.3.9
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.9 - 2018-05-29
17
+ - Added option to easily disable insertion of individual code block
18
+ - Changes for compatibility with PHP 7.2
19
+ - Added non-interaction parameter to external tracking (Pro only)
20
+ - Few minor bug fixes, cosmetic changes and code improvements
21
+
22
  Ad Inserter 2.3.8 - 2018-04-17
23
+ - Added support for rotation option shares
24
  - Added support for sticky ad settings and animations (Pro only)
25
  - Few minor bug fixes, cosmetic changes and code improvements
26
 
1085
  case AI_CHECK_MIN_NUMBER_OF_WORDS: $status .= "MIN NUMBER OF WORDS " . intval ($obj->get_minimum_words()); break;
1086
  case AI_CHECK_MAX_NUMBER_OF_WORDS: $status .= "MAX NUMBER OF WORDS " . (intval ($obj->get_maximum_words()) == 0 ? 1000000 : intval ($obj->get_maximum_words())); break;
1087
  case AI_CHECK_DEBUG_NO_INSERTION: $status .= "DEBUG NO INSERTION"; break;
1088
+ case AI_CHECK_INSERTION_NOT_DISABLED: $status .= "INSERTION NOT DISABLED"; break;
1089
  case AI_CHECK_PARAGRAPH_TAGS: $status .= "PARAGRAPH TAGS"; break;
1090
  case AI_CHECK_PARAGRAPHS_WITH_TAGS: $status .= "PARAGRAPHS WITH TAGS"; break;
1091
  case AI_CHECK_PARAGRAPHS_AFTER_NO_COUNTING_INSIDE: $status .= "PARAGRAPHS AFTER NO COUNTING INSIDE"; break;
1100
  case AI_CHECK_AD_BELOW: $status .= "PARAGRAPH CLEARANCE BELOW"; break;
1101
  case AI_CHECK_SHORTCODE_ATTRIBUTES: $status .= "SHORTCODE ATTRIBUTES"; break;
1102
 
1103
+ case AI_CHECK_ENABLED_PHP: $status .= "PHP FUNCTION ENABLED"; break;
1104
+ case AI_CHECK_ENABLED_SHORTCODE: $status .= "SHORTCODE ENABLED"; break;
1105
+ case AI_CHECK_ENABLED_WIDGET: $status .= "WIDGET ENABLED"; break;
1106
 
1107
  case AI_CHECK_NONE: $status = "BLOCK $block"; break;
1108
  default: $status .= "?"; break;
1213
  $body = $matches [2];
1214
 
1215
  if (isset ($ai_db_options_extract [HTML_ELEMENT_HOOK_BLOCKS][$ai_wp_data [AI_WP_PAGE_TYPE]]) && class_exists ('DOMDocument')) {
1216
+
1217
+ $php_version = explode ('.', PHP_VERSION);
1218
+ if ($php_version [0] > 5 || ($php_version [0] == 5 && $php_version [1] >= 3)) {
1219
+ // phpQuery with anonymous functions
1220
+ require_once ('includes/phpQuery.php');
1221
+ } else {
1222
+ // phpQuery with create_function
1223
+ require_once ('includes/phpQuery_52.php');
1224
+ }
1225
 
1226
  $no_closing_tag = array ('img', 'hr', 'br');
1227
  $multibyte = get_paragraph_counting_functions() == AI_MULTIBYTE_PARAGRAPH_COUNTING_FUNCTIONS;
4676
 
4677
  function generate_debug_css () {
4678
  ?>
4679
+
4680
  .ai-debug-tags {font-weight: bold; color: white; padding: 2px;}
4681
  .ai-debug-positions {clear: both; text-align: center; padding: 10px 0; font-family: arial; font-weight: bold; border: 1px solid blue; color: blue; background: #eef;}
4682
  .ai-debug-page-type {text-align: center; padding: 10px 0; font-family: arial; font-weight: bold; border: 1px solid green; color: green; background: #efe;}
4705
  .ai-info-1 {background: #000; color: #fff;}
4706
  .ai-info-2 {background: #fff; color: #000;}
4707
 
4708
+ section.ai-debug-block {padding: 0; margin: 0;}
4709
+
4710
+ .ai-debug-code {margin: 0; padding: 0; border: 0; font-family: monospace, sans-serif; font-size: 12px; line-height: 13px; background: #fff; color: #000;}
4711
 
4712
  .ai-debug-block {border: 1px solid;}
4713
 
4752
 
4753
  .ai-debug-message {text-align: center; font-weight: bold;}
4754
 
4755
+ .ai-debug-bar kbd {margin: 0; padding: 0; color: #fff; font-size: inherit; font-family: arial; background-color: transparent; box-shadow: none;}
4756
+
4757
+ .ai-debug-block pre {margin: 0;}
4758
+
4759
 
4760
  <?php
4761
  }
5174
  if (!$obj->check_post_page_exceptions ($selected_blocks)) return "";
5175
  }
5176
 
5177
+ $ai_last_check = AI_CHECK_INSERTION_NOT_DISABLED;
5178
+ if ($obj->get_disable_insertion ()) return "";
5179
+
5180
  // Last check before counter check before insertion
5181
  $ai_last_check = AI_CHECK_CODE;
5182
  if ($obj->ai_getCode () == '') return "";
5286
  $ai_last_check = AI_CHECK_DISABLED_MANUALLY;
5287
  if ($obj->display_disabled ($content)) continue;
5288
 
5289
+ $ai_last_check = AI_CHECK_INSERTION_NOT_DISABLED;
5290
+ if ($obj->get_disable_insertion ()) continue;
5291
+
5292
  // Last check before counter check before insertion
5293
  $ai_last_check = AI_CHECK_CODE;
5294
  if ($obj->ai_getCode () == '') continue;
5436
  $ai_last_check = AI_CHECK_DISABLED_MANUALLY;
5437
  if ($obj->display_disabled ($content)) continue;
5438
 
5439
+ $ai_last_check = AI_CHECK_INSERTION_NOT_DISABLED;
5440
+ if ($obj->get_disable_insertion ()) continue;
5441
+
5442
  // Last check before counter check before insertion
5443
  $ai_last_check = AI_CHECK_CODE;
5444
  if ($obj->ai_getCode () == '') continue;
5572
  if (!$obj->check_page_types_lists_users ()) continue;
5573
  // No filter check
5574
 
5575
+ $ai_last_check = AI_CHECK_INSERTION_NOT_DISABLED;
5576
+ if ($obj->get_disable_insertion ()) continue;
5577
+
5578
  // Last check before counter check before insertion
5579
  $ai_last_check = AI_CHECK_CODE;
5580
  if ($obj->ai_getCode () == '') continue;
5668
  if (!$obj->check_page_types_lists_users ()) continue;
5669
  // No filter check
5670
 
5671
+ $ai_last_check = AI_CHECK_INSERTION_NOT_DISABLED;
5672
+ if ($obj->get_disable_insertion ()) continue;
5673
+
5674
  // Last check before counter check before insertion
5675
  $ai_last_check = AI_CHECK_CODE;
5676
  if ($obj->ai_getCode () == '') continue;
5721
  if (!$obj->check_page_types_lists_users ()) continue;
5722
  if (!$obj->check_filter ($ad_inserter_globals [AI_COMMENT_COUNTER_NAME])) continue;
5723
 
5724
+ $ai_last_check = AI_CHECK_INSERTION_NOT_DISABLED;
5725
+ if ($obj->get_disable_insertion ()) continue;
5726
+
5727
  // Last check before counter check before insertion
5728
  $ai_last_check = AI_CHECK_CODE;
5729
  if ($obj->ai_getCode () == '') continue;
5814
  if (!$obj->check_filter ($ad_inserter_globals [$globals_name])) continue;
5815
  if (!$obj->check_number_of_words ()) continue;
5816
 
5817
+ $ai_last_check = AI_CHECK_INSERTION_NOT_DISABLED;
5818
+ if ($obj->get_disable_insertion ()) continue;
5819
+
5820
  // Last check before counter check before insertion
5821
  $ai_last_check = AI_CHECK_CODE;
5822
  if ($obj->ai_getCode () == '') continue;
6029
  }
6030
  }
6031
 
6032
+ $ai_last_check = AI_CHECK_INSERTION_NOT_DISABLED;
6033
+ if ($obj->get_disable_insertion ()) return "";
6034
+
6035
  // Last check before counter check before insertion
6036
  $ai_last_check = AI_CHECK_CODE;
6037
  if ($obj->ai_getCode () == '') return "";
6260
  if (!$obj->check_post_page_exceptions ($selected_blocks)) return;
6261
  }
6262
 
6263
+ $ai_last_check = AI_CHECK_INSERTION_NOT_DISABLED;
6264
+ if ($obj->get_disable_insertion ()) return;
6265
+
6266
  // Last check before counter check before insertion
6267
  $ai_last_check = AI_CHECK_CODE;
6268
  if ($obj->ai_getCode () == '') {
class.php CHANGED
@@ -406,6 +406,7 @@ abstract class ai_CodeBlock extends ai_BaseCodeBlock {
406
  parent::__construct();
407
 
408
  $this->wp_options [AI_OPTION_BLOCK_NAME] = AD_NAME;
 
409
  $this->wp_options [AI_OPTION_SHOW_LABEL] = AI_DISABLED;
410
  $this->wp_options [AI_OPTION_TRACKING] = AI_DISABLED;
411
  $this->wp_options [AI_OPTION_AUTOMATIC_INSERTION] = AI_AUTOMATIC_INSERTION_DISABLED;
@@ -433,6 +434,7 @@ abstract class ai_CodeBlock extends ai_BaseCodeBlock {
433
  $this->wp_options [AI_OPTION_INVERTED_FILTER] = AI_DISABLED;
434
  $this->wp_options [AI_OPTION_DIRECTION_TYPE] = AD_DIRECTION_FROM_TOP;
435
  $this->wp_options [AI_OPTION_ALIGNMENT_TYPE] = AI_ALIGNMENT_DEFAULT;
 
436
  if (defined ('AI_STICKY_SETTINGS') && AI_STICKY_SETTINGS) {
437
  $this->wp_options [AI_OPTION_HORIZONTAL_POSITION] = DEFAULT_HORIZONTAL_POSITION;
438
  $this->wp_options [AI_OPTION_VERTICAL_POSITION] = DEFAULT_VERTICAL_POSITION;
@@ -497,6 +499,11 @@ abstract class ai_CodeBlock extends ai_BaseCodeBlock {
497
  }
498
  }
499
 
 
 
 
 
 
500
  public function get_show_label (){
501
  $show_label = isset ($this->wp_options [AI_OPTION_SHOW_LABEL]) ? $this->wp_options [AI_OPTION_SHOW_LABEL] : AI_DISABLED;
502
  if ($show_label == '') $show_label = AI_DISABLED;
@@ -3123,6 +3130,7 @@ abstract class ai_CodeBlock extends ai_BaseCodeBlock {
3123
  $ai_last_check = AI_CHECK_PARAGRAPH_NUMBER;
3124
  if (count ($paragraph_positions) > $position) {
3125
  $this->increment_block_counter ();
 
3126
  $ai_last_check = AI_CHECK_DEBUG_NO_INSERTION;
3127
  if (($ai_wp_data [AI_WP_DEBUGGING] & AI_DEBUG_NO_INSERTION) == 0) {
3128
  $content_position = $paragraph_positions [$position];
@@ -3721,6 +3729,7 @@ abstract class ai_CodeBlock extends ai_BaseCodeBlock {
3721
  $ai_last_check = AI_CHECK_PARAGRAPH_NUMBER;
3722
  if (count ($paragraph_positions) > $position) {
3723
  $this->increment_block_counter ();
 
3724
  $ai_last_check = AI_CHECK_DEBUG_NO_INSERTION;
3725
  if (($ai_wp_data [AI_WP_DEBUGGING] & AI_DEBUG_NO_INSERTION) == 0) {
3726
  $content_position = $paragraph_positions [$position];
406
  parent::__construct();
407
 
408
  $this->wp_options [AI_OPTION_BLOCK_NAME] = AD_NAME;
409
+ $this->wp_options [AI_OPTION_DISABLE_INSERTION] = AI_DISABLED;
410
  $this->wp_options [AI_OPTION_SHOW_LABEL] = AI_DISABLED;
411
  $this->wp_options [AI_OPTION_TRACKING] = AI_DISABLED;
412
  $this->wp_options [AI_OPTION_AUTOMATIC_INSERTION] = AI_AUTOMATIC_INSERTION_DISABLED;
434
  $this->wp_options [AI_OPTION_INVERTED_FILTER] = AI_DISABLED;
435
  $this->wp_options [AI_OPTION_DIRECTION_TYPE] = AD_DIRECTION_FROM_TOP;
436
  $this->wp_options [AI_OPTION_ALIGNMENT_TYPE] = AI_ALIGNMENT_DEFAULT;
437
+
438
  if (defined ('AI_STICKY_SETTINGS') && AI_STICKY_SETTINGS) {
439
  $this->wp_options [AI_OPTION_HORIZONTAL_POSITION] = DEFAULT_HORIZONTAL_POSITION;
440
  $this->wp_options [AI_OPTION_VERTICAL_POSITION] = DEFAULT_VERTICAL_POSITION;
499
  }
500
  }
501
 
502
+ public function get_disable_insertion (){
503
+ $disable_insertion = isset ($this->wp_options [AI_OPTION_DISABLE_INSERTION]) ? $this->wp_options [AI_OPTION_DISABLE_INSERTION] : AI_DISABLED;
504
+ return $disable_insertion;
505
+ }
506
+
507
  public function get_show_label (){
508
  $show_label = isset ($this->wp_options [AI_OPTION_SHOW_LABEL]) ? $this->wp_options [AI_OPTION_SHOW_LABEL] : AI_DISABLED;
509
  if ($show_label == '') $show_label = AI_DISABLED;
3130
  $ai_last_check = AI_CHECK_PARAGRAPH_NUMBER;
3131
  if (count ($paragraph_positions) > $position) {
3132
  $this->increment_block_counter ();
3133
+
3134
  $ai_last_check = AI_CHECK_DEBUG_NO_INSERTION;
3135
  if (($ai_wp_data [AI_WP_DEBUGGING] & AI_DEBUG_NO_INSERTION) == 0) {
3136
  $content_position = $paragraph_positions [$position];
3729
  $ai_last_check = AI_CHECK_PARAGRAPH_NUMBER;
3730
  if (count ($paragraph_positions) > $position) {
3731
  $this->increment_block_counter ();
3732
+
3733
  $ai_last_check = AI_CHECK_DEBUG_NO_INSERTION;
3734
  if (($ai_wp_data [AI_WP_DEBUGGING] & AI_DEBUG_NO_INSERTION) == 0) {
3735
  $content_position = $paragraph_positions [$position];
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.8');
28
 
29
  if (!defined ('AD_INSERTER_PLUGIN_BASENAME'))
30
  define ('AD_INSERTER_PLUGIN_BASENAME', plugin_basename (__FILE__));
@@ -60,6 +60,7 @@ define ('AI_OPTION_ENABLE_MANUAL', 'enable_manual');
60
  define ('AI_OPTION_ENABLE_AMP', 'enable_amp');
61
  define ('AI_OPTION_ENABLE_WIDGET', 'enable_widget');
62
  define ('AI_OPTION_PROCESS_PHP', 'process_php');
 
63
  define ('AI_OPTION_SHOW_LABEL', 'show_label');
64
  define ('AI_OPTION_TRACKING', 'tracking');
65
  define ('AI_OPTION_ENABLE_AJAX', 'enable_ajax');
@@ -754,7 +755,8 @@ define ('AI_CHECK_PARAGRAPH_NUMBER', 48);
754
  define ('AI_CHECK_MIN_NUMBER_OF_WORDS', 49);
755
  define ('AI_CHECK_MAX_NUMBER_OF_WORDS', 50);
756
  define ('AI_CHECK_TAXONOMY', 51);
757
- define ('AI_CHECK_ENABLED_WIDGET', 52);
 
758
 
759
  define ('AI_PT_NONE', - 1);
760
  define ('AI_PT_ANY', 0);
24
  define ('AD_INSERTER_NAME', 'Ad Inserter');
25
 
26
  if (!defined( 'AD_INSERTER_VERSION'))
27
+ define ('AD_INSERTER_VERSION', '2.3.9');
28
 
29
  if (!defined ('AD_INSERTER_PLUGIN_BASENAME'))
30
  define ('AD_INSERTER_PLUGIN_BASENAME', plugin_basename (__FILE__));
60
  define ('AI_OPTION_ENABLE_AMP', 'enable_amp');
61
  define ('AI_OPTION_ENABLE_WIDGET', 'enable_widget');
62
  define ('AI_OPTION_PROCESS_PHP', 'process_php');
63
+ define ('AI_OPTION_DISABLE_INSERTION', 'disable_insertion');
64
  define ('AI_OPTION_SHOW_LABEL', 'show_label');
65
  define ('AI_OPTION_TRACKING', 'tracking');
66
  define ('AI_OPTION_ENABLE_AJAX', 'enable_ajax');
755
  define ('AI_CHECK_MIN_NUMBER_OF_WORDS', 49);
756
  define ('AI_CHECK_MAX_NUMBER_OF_WORDS', 50);
757
  define ('AI_CHECK_TAXONOMY', 51);
758
+ define ('AI_CHECK_ENABLED_WIDGET', 52);
759
+ define ('AI_CHECK_INSERTION_NOT_DISABLED', 53);
760
 
761
  define ('AI_PT_NONE', - 1);
762
  define ('AI_PT_ANY', 0);
css/ad-inserter.css CHANGED
@@ -1,5 +1,5 @@
1
  #ai-data {
2
- font-family: "2.3.8"; /* Used for version number of the file */
3
  }
4
 
5
  #blocked-warning {
@@ -946,6 +946,16 @@ img.automatic-insertion.preview {
946
  background: url('images/icons.png') -160px -80px;
947
  }
948
 
 
 
 
 
 
 
 
 
 
 
949
  .checkbox-icon.icon-enabled-all {
950
  background: url('images/icons.png') -142px -22px;
951
  }
1
  #ai-data {
2
+ font-family: "2.3.9"; /* Used for version number of the file */
3
  }
4
 
5
  #blocked-warning {
946
  background: url('images/icons.png') -160px -80px;
947
  }
948
 
949
+ .checkbox-icon.icon-pause {
950
+ background: url('images/icons.png') -180px -80px;
951
+ }
952
+
953
+ .checkbox-icon.icon-pause.on {
954
+ background: url('images/icons.png') -180px -60px;
955
+ }
956
+
957
+
958
+
959
  .checkbox-icon.icon-enabled-all {
960
  background: url('images/icons.png') -142px -22px;
961
  }
css/images/icons.png CHANGED
Binary file
includes/phpQuery.php CHANGED
@@ -1027,15 +1027,15 @@ class Callback
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
  *
@@ -2083,16 +2083,26 @@ class phpQueryObject
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':
@@ -2105,19 +2115,34 @@ class phpQueryObject
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);
@@ -2134,18 +2159,33 @@ class phpQueryObject
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':
@@ -2158,67 +2198,126 @@ class phpQueryObject
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;
@@ -4691,8 +4790,8 @@ abstract class phpQuery {
4691
  }
4692
  public static function phpToMarkup($php, $charset = 'utf-8') {
4693
  $regexes = array(
4694
- '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?>)([^\']*)\'@s',
4695
- '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?>)([^"]*)"@s',
4696
  );
4697
  foreach($regexes as $regex)
4698
  while (preg_match($regex, $php, $matches)) {
@@ -4707,7 +4806,7 @@ abstract class phpQuery {
4707
  $php
4708
  );
4709
  }
4710
- $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s';
4711
  //preg_match_all($regex, $php, $matches);
4712
  //var_dump($matches);
4713
  $php = preg_replace($regex, '\\1<php><!-- \\3 --></php>', $php);
@@ -4742,22 +4841,34 @@ abstract class phpQuery {
4742
  );
4743
  /* <node attr='< ?php ? >'> extra space added to save highlighters */
4744
  $regexes = array(
4745
- '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^\']*)\'@s',
4746
- '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^"]*)"@s',
4747
  );
4748
  foreach($regexes as $regex)
4749
  while (preg_match($regex, $content))
4750
  $content = preg_replace_callback(
4751
  $regex,
4752
- create_function('$m',
4753
- 'return $m[1].$m[2].$m[3]."<?php "
4754
- .str_replace(
4755
- array("%20", "%3E", "%09", "&#10;", "&#9;", "%7B", "%24", "%7D", "%22", "%5B", "%5D"),
4756
- array(" ", ">", " ", "\n", " ", "{", "$", "}", \'"\', "[", "]"),
4757
- htmlspecialchars_decode($m[4])
4758
- )
4759
- ." ?".">".$m[5].$m[2];'
4760
- ),
 
 
 
 
 
 
 
 
 
 
 
 
4761
  $content
4762
  );
4763
  return $content;
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
  *
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
+
2090
+ function ($node) {
2091
+ return $node instanceof DOMELEMENT && $node->childNodes->length ? $node : null;
2092
+ }
2093
+
2094
  )->elements;
2095
  break;
2096
  case 'empty':
2097
  $this->elements = $this->map(
2098
+ // create_function('$node', '
2099
+ // return $node instanceof DOMELEMENT && $node->childNodes->length
2100
+ // ? null : $node;')
2101
+
2102
+ function ($node) {
2103
+ return $node instanceof DOMELEMENT && $node->childNodes->length ? null : $node;
2104
+ }
2105
+
2106
  )->elements;
2107
  break;
2108
  case 'disabled':
2115
  break;
2116
  case 'enabled':
2117
  $this->elements = $this->map(
2118
+ // create_function('$node', '
2119
+ // return pq($node)->not(":disabled") ? $node : null;')
2120
+
2121
+ function ($node) {
2122
+ return pq($node)->not(":disabled") ? $node : null;
2123
+ }
2124
+
2125
  )->elements;
2126
  break;
2127
  case 'header':
2128
  $this->elements = $this->map(
2129
+ // create_function('$node',
2130
+ // '$isHeader = isset($node->tagName) && in_array($node->tagName, array(
2131
+ // "h1", "h2", "h3", "h4", "h5", "h6", "h7"
2132
+ // ));
2133
+ // return $isHeader
2134
+ // ? $node
2135
+ // : null;')
2136
+
2137
+ function ($node) {
2138
+ $isHeader = isset($node->tagName) && in_array($node->tagName, array(
2139
+ "h1", "h2", "h3", "h4", "h5", "h6", "h7"
2140
+ ));
2141
+ return $isHeader
2142
+ ? $node
2143
+ : null;
2144
+ }
2145
+
2146
  )->elements;
2147
  // $this->elements = $this->map(
2148
  // create_function('$node', '$node = pq($node);
2159
  break;
2160
  case 'only-child':
2161
  $this->elements = $this->map(
2162
+ // create_function('$node',
2163
+ // 'return pq($node)->siblings()->size() == 0 ? $node : null;')
2164
+
2165
+ function ($node) {
2166
+ return pq($node)->siblings()->size() == 0 ? $node : null;
2167
+ }
2168
+
2169
  )->elements;
2170
  break;
2171
  case 'first-child':
2172
+ $this->elements = $this->map(
2173
+ // create_function('$node', 'return pq($node)->prevAll()->size() == 0 ? $node : null;')
2174
+
2175
+ function ($node) {
2176
+ return pq($node)->prevAll()->size() == 0 ? $node : null;
2177
+ }
2178
+
2179
  )->elements;
2180
  break;
2181
  case 'last-child':
2182
  $this->elements = $this->map(
2183
+ // create_function('$node', 'return pq($node)->nextAll()->size() == 0 ? $node : null;')
2184
+
2185
+ function ($node) {
2186
+ return pq($node)->nextAll()->size() == 0 ? $node : null;
2187
+ }
2188
+
2189
  )->elements;
2190
  break;
2191
  case 'nth-child':
2198
  // :nth-child(index/even/odd/equation)
2199
  if ($param == 'even' || $param == 'odd')
2200
  $mapped = $this->map(
2201
+ // create_function('$node, $param',
2202
+ // '$index = pq($node)->prevAll()->size()+1;
2203
+ // if ($param == "even" && ($index%2) == 0)
2204
+ // return $node;
2205
+ // else if ($param == "odd" && $index%2 == 1)
2206
+ // return $node;
2207
+ // else
2208
+ // return null;'),
2209
+
2210
+ function ($node, $param) {
2211
+ $index = pq($node)->prevAll()->size()+1;
2212
+ if ($param == "even" && ($index%2) == 0)
2213
+ return $node;
2214
+ else if ($param == "odd" && $index%2 == 1)
2215
+ return $node;
2216
+ else
2217
+ return null;
2218
+ },
2219
+
2220
  new CallbackParam(), $param
2221
  );
2222
  else if (mb_strlen($param) > 1 && $param{1} == 'n')
2223
  // an+b
2224
  $mapped = $this->map(
2225
+ // create_function('$node, $param',
2226
+ // '$prevs = pq($node)->prevAll()->size();
2227
+ // $index = 1+$prevs;
2228
+ // $b = mb_strlen($param) > 3
2229
+ // ? $param{3}
2230
+ // : 0;
2231
+ // $a = $param{0};
2232
+ // if ($b && $param{2} == "-")
2233
+ // $b = -$b;
2234
+ // if ($a > 0) {
2235
+ // return ($index-$b)%$a == 0
2236
+ // ? $node
2237
+ // : null;
2238
+ // phpQuery::debug($a."*".floor($index/$a)."+$b-1 == ".($a*floor($index/$a)+$b-1)." ?= $prevs");
2239
+ // return $a*floor($index/$a)+$b-1 == $prevs
2240
+ // ? $node
2241
+ // : null;
2242
+ // } else if ($a == 0)
2243
+ // return $index == $b
2244
+ // ? $node
2245
+ // : null;
2246
+ // else
2247
+ // // negative value
2248
+ // return $index <= $b
2249
+ // ? $node
2250
+ // : null;
2251
+ //// if (! $b)
2252
+ //// return $index%$a == 0
2253
+ //// ? $node
2254
+ //// : null;
2255
+ //// else
2256
+ //// return ($index-$b)%$a == 0
2257
+ //// ? $node
2258
+ //// : null;
2259
+ // '),
2260
+
2261
+ function ($node, $param) {
2262
+ $prevs = pq($node)->prevAll()->size();
2263
+ $index = 1+$prevs;
2264
+ $b = mb_strlen($param) > 3
2265
+ ? $param{3}
2266
+ : 0;
2267
+ $a = $param{0};
2268
+ if ($b && $param{2} == "-")
2269
+ $b = -$b;
2270
+ if ($a > 0) {
2271
+ return ($index-$b)%$a == 0
2272
+ ? $node
2273
+ : null;
2274
+ phpQuery::debug($a."*".floor($index/$a)."+$b-1 == ".($a*floor($index/$a)+$b-1)." ?= $prevs");
2275
+ return $a*floor($index/$a)+$b-1 == $prevs
2276
+ ? $node
2277
+ : null;
2278
+ } else if ($a == 0)
2279
+ return $index == $b
2280
+ ? $node
2281
+ : null;
2282
+ else
2283
+ // negative value
2284
+ return $index <= $b
2285
+ ? $node
2286
+ : null;
2287
+ // if (! $b)
2288
+ // return $index%$a == 0
2289
+ // ? $node
2290
+ // : null;
2291
+ // else
2292
+ // return ($index-$b)%$a == 0
2293
+ // ? $node
2294
+ // : null;
2295
+ },
2296
+
2297
  new CallbackParam(), $param
2298
  );
2299
  else
2300
  // index
2301
  $mapped = $this->map(
2302
+ // create_function('$node, $index',
2303
+ // '$prevs = pq($node)->prevAll()->size();
2304
+ // if ($prevs && $prevs == $index-1)
2305
+ // return $node;
2306
+ // else if (! $prevs && $index == 1)
2307
+ // return $node;
2308
+ // else
2309
+ // return null;'),
2310
+
2311
+ function ($node, $index) {
2312
+ $prevs = pq($node)->prevAll()->size();
2313
+ if ($prevs && $prevs == $index-1)
2314
+ return $node;
2315
+ else if (! $prevs && $index == 1)
2316
+ return $node;
2317
+ else
2318
+ return null;
2319
+ },
2320
+
2321
  new CallbackParam(), $param
2322
  );
2323
  $this->elements = $mapped->elements;
4790
  }
4791
  public static function phpToMarkup($php, $charset = 'utf-8') {
4792
  $regexes = array(
4793
+ '@(<(?!\\?)(?:[^>]|\\?'.'>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?'.'>)([^\']*)\'@s',
4794
+ '@(<(?!\\?)(?:[^>]|\\?'.'>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?'.'>)([^"]*)"@s',
4795
  );
4796
  foreach($regexes as $regex)
4797
  while (preg_match($regex, $php, $matches)) {
4806
  $php
4807
  );
4808
  }
4809
+ $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?'.'>))@s';
4810
  //preg_match_all($regex, $php, $matches);
4811
  //var_dump($matches);
4812
  $php = preg_replace($regex, '\\1<php><!-- \\3 --></php>', $php);
4841
  );
4842
  /* <node attr='< ?php ? >'> extra space added to save highlighters */
4843
  $regexes = array(
4844
+ '@(<(?!\\?)(?:[^>]|\\?'.'>)+\\w+\\s*=\\s*)(\')([^\']*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^\']*)\'@s',
4845
+ '@(<(?!\\?)(?:[^>]|\\?'.'>)+\\w+\\s*=\\s*)(")([^"]*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^"]*)"@s',
4846
  );
4847
  foreach($regexes as $regex)
4848
  while (preg_match($regex, $content))
4849
  $content = preg_replace_callback(
4850
  $regex,
4851
+ // create_function('$m',
4852
+ // 'return $m[1].$m[2].$m[3]."<"."?php "
4853
+ // .str_replace(
4854
+ // array("%20", "%3E", "%09", "&#10;", "&#9;", "%7B", "%24", "%7D", "%22", "%5B", "%5D"),
4855
+ // array(" ", ">", " ", "\n", " ", "{", "$", "}", \'"\', "[", "]"
4856
+ // ),
4857
+ // htmlspecialchars_decode($m[4])
4858
+ // )
4859
+ // ." ?".">".$m[5].$m[2];'
4860
+ // ),
4861
+
4862
+ function ($m) {
4863
+ return $m[1].$m[2].$m[3]."<"."?php "
4864
+ .str_replace(
4865
+ array("%20", "%3E", "%09", "&#10;", "&#9;", "%7B", "%24", "%7D", "%22", "%5B", "%5D"),
4866
+ array(" ", ">", " ", "\n", " ", "{", "$", "}", '"', "[", "]"),
4867
+ htmlspecialchars_decode($m[4])
4868
+ )
4869
+ ." ?".">".$m[5].$m[2];
4870
+ },
4871
+
4872
  $content
4873
  );
4874
  return $content;
includes/phpQuery_52.php ADDED
@@ -0,0 +1,5712 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ protected function ai_pseudoClassParam($paramsString) {
2235
+ // TODO;
2236
+ }
2237
+ /**
2238
+ * Enter description here...
2239
+ *
2240
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2241
+ */
2242
+ public function is($selector, $nodes = null) {
2243
+ phpQuery::debug(array("Is:", $selector));
2244
+ if (! $selector)
2245
+ return false;
2246
+ $oldStack = $this->elements;
2247
+ $returnArray = false;
2248
+ if ($nodes && is_array($nodes)) {
2249
+ $this->elements = $nodes;
2250
+ } else if ($nodes)
2251
+ $this->elements = array($nodes);
2252
+ $this->filter($selector, true);
2253
+ $stack = $this->elements;
2254
+ $this->elements = $oldStack;
2255
+ if ($nodes)
2256
+ return $stack ? $stack : null;
2257
+ return (bool)count($stack);
2258
+ }
2259
+ /**
2260
+ * Enter description here...
2261
+ * jQuery difference.
2262
+ *
2263
+ * Callback:
2264
+ * - $index int
2265
+ * - $node DOMNode
2266
+ *
2267
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2268
+ * @link http://docs.jquery.com/Traversing/filter
2269
+ */
2270
+ public function filterCallback($callback, $_skipHistory = false) {
2271
+ if (! $_skipHistory) {
2272
+ $this->elementsBackup = $this->elements;
2273
+ $this->debug("Filtering by callback");
2274
+ }
2275
+ $newStack = array();
2276
+ foreach($this->elements as $index => $node) {
2277
+ $result = phpQuery::callbackRun($callback, array($index, $node));
2278
+ if (is_null($result) || (! is_null($result) && $result))
2279
+ $newStack[] = $node;
2280
+ }
2281
+ $this->elements = $newStack;
2282
+ return $_skipHistory
2283
+ ? $this
2284
+ : $this->newInstance();
2285
+ }
2286
+ /**
2287
+ * Enter description here...
2288
+ *
2289
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2290
+ * @link http://docs.jquery.com/Traversing/filter
2291
+ */
2292
+ public function filter($selectors, $_skipHistory = false) {
2293
+ if ($selectors instanceof Callback OR $selectors instanceof Closure)
2294
+ return $this->filterCallback($selectors, $_skipHistory);
2295
+ if (! $_skipHistory)
2296
+ $this->elementsBackup = $this->elements;
2297
+ $notSimpleSelector = array(' ', '>', '~', '+', '/');
2298
+ if (! is_array($selectors))
2299
+ $selectors = $this->parseSelector($selectors);
2300
+ if (! $_skipHistory)
2301
+ $this->debug(array("Filtering:", $selectors));
2302
+ $finalStack = array();
2303
+ foreach($selectors as $selector) {
2304
+ $stack = array();
2305
+ if (! $selector)
2306
+ break;
2307
+ // avoid first space or /
2308
+ if (in_array($selector[0], $notSimpleSelector))
2309
+ $selector = array_slice($selector, 1);
2310
+ // PER NODE selector chunks
2311
+ foreach($this->stack() as $node) {
2312
+ $break = false;
2313
+ foreach($selector as $s) {
2314
+ if (!($node instanceof DOMELEMENT)) {
2315
+ // all besides DOMElement
2316
+ if ( $s[0] == '[') {
2317
+ $attr = trim($s, '[]');
2318
+ if ( mb_strpos($attr, '=')) {
2319
+ list( $attr, $val ) = explode('=', $attr);
2320
+ if ($attr == 'nodeType' && $node->nodeType != $val)
2321
+ $break = true;
2322
+ }
2323
+ } else
2324
+ $break = true;
2325
+ } else {
2326
+ // DOMElement only
2327
+ // ID
2328
+ if ( $s[0] == '#') {
2329
+ if ( $node->getAttribute('id') != substr($s, 1) )
2330
+ $break = true;
2331
+ // CLASSES
2332
+ } else if ( $s[0] == '.') {
2333
+ if (! $this->matchClasses( $s, $node ) )
2334
+ $break = true;
2335
+ // ATTRS
2336
+ } else if ( $s[0] == '[') {
2337
+ // strip side brackets
2338
+ $attr = trim($s, '[]');
2339
+ if (mb_strpos($attr, '=')) {
2340
+ list($attr, $val) = explode('=', $attr);
2341
+ $val = self::unQuote($val);
2342
+ if ($attr == 'nodeType') {
2343
+ if ($val != $node->nodeType)
2344
+ $break = true;
2345
+ } else if ($this->isRegexp($attr)) {
2346
+ $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport
2347
+ ? quotemeta(trim($val, '"\''))
2348
+ : preg_quote(trim($val, '"\''), '@');
2349
+ // switch last character
2350
+ switch( substr($attr, -1)) {
2351
+ // quotemeta used insted of preg_quote
2352
+ // http://code.google.com/p/phpquery/issues/detail?id=76
2353
+ case '^':
2354
+ $pattern = '^'.$val;
2355
+ break;
2356
+ case '*':
2357
+ $pattern = '.*'.$val.'.*';
2358
+ break;
2359
+ case '$':
2360
+ $pattern = '.*'.$val.'$';
2361
+ break;
2362
+ }
2363
+ // cut last character
2364
+ $attr = substr($attr, 0, -1);
2365
+ $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport
2366
+ ? mb_ereg_match($pattern, $node->getAttribute($attr))
2367
+ : preg_match("@{$pattern}@", $node->getAttribute($attr));
2368
+ if (! $isMatch)
2369
+ $break = true;
2370
+ } else if ($node->getAttribute($attr) != $val)
2371
+ $break = true;
2372
+ } else if (! $node->hasAttribute($attr))
2373
+ $break = true;
2374
+ // PSEUDO CLASSES
2375
+ } else if ( $s[0] == ':') {
2376
+ // skip
2377
+ // TAG
2378
+ } else if (trim($s)) {
2379
+ if ($s != '*') {
2380
+ // TODO namespaces
2381
+ if (isset($node->tagName)) {
2382
+ if ($node->tagName != $s)
2383
+ $break = true;
2384
+ } else if ($s == 'html' && ! $this->isRoot($node))
2385
+ $break = true;
2386
+ }
2387
+ // AVOID NON-SIMPLE SELECTORS
2388
+ } else if (in_array($s, $notSimpleSelector)) {
2389
+ $break = true;
2390
+ $this->debug(array('Skipping non simple selector', $selector));
2391
+ }
2392
+ }
2393
+ if ($break)
2394
+ break;
2395
+ }
2396
+ // if element passed all chunks of selector - add it to new stack
2397
+ if (! $break )
2398
+ $stack[] = $node;
2399
+ }
2400
+ $tmpStack = $this->elements;
2401
+ $this->elements = $stack;
2402
+ // PER ALL NODES selector chunks
2403
+ foreach($selector as $s)
2404
+ // PSEUDO CLASSES
2405
+ if ($s[0] == ':')
2406
+ $this->pseudoClasses($s);
2407
+ foreach($this->elements as $node)
2408
+ // XXX it should be merged without duplicates
2409
+ // but jQuery doesnt do that
2410
+ $finalStack[] = $node;
2411
+ $this->elements = $tmpStack;
2412
+ }
2413
+ $this->elements = $finalStack;
2414
+ if ($_skipHistory) {
2415
+ return $this;
2416
+ } else {
2417
+ $this->debug("Stack length after filter(): ".count($finalStack));
2418
+ return $this->newInstance();
2419
+ }
2420
+ }
2421
+ /**
2422
+ *
2423
+ * @param $value
2424
+ * @return unknown_type
2425
+ * @TODO implement in all methods using passed parameters
2426
+ */
2427
+ protected static function unQuote($value) {
2428
+ return $value[0] == '\'' || $value[0] == '"'
2429
+ ? substr($value, 1, -1)
2430
+ : $value;
2431
+ }
2432
+ /**
2433
+ * Enter description here...
2434
+ *
2435
+ * @link http://docs.jquery.com/Ajax/load
2436
+ * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2437
+ * @todo Support $selector
2438
+ */
2439
+ public function load($url, $data = null, $callback = null) {
2440
+ if ($data && ! is_array($data)) {
2441
+ $callback = $data;
2442
+ $data = null;
2443
+ }
2444
+ if (mb_strpos($url, ' ') !== false) {
2445
+ $matches = null;
2446
+ if (extension_loaded('mbstring') && phpQuery::$mbstringSupport)
2447
+ mb_ereg('^([^ ]+) (.*)$', $url, $matches);
2448
+ else
2449
+ preg_match('^([^ ]+) (.*)$', $url, $matches);
2450
+ $url = $matches[1];
2451
+ $selector = $matches[2];
2452
+ // FIXME this sucks, pass as callback param
2453
+ $this->_loadSelector = $selector;
2454
+ }
2455
+ $ajax = array(
2456
+ 'url' => $url,
2457
+ 'type' => $data ? 'POST' : 'GET',
2458
+ 'data' => $data,
2459
+ 'complete' => $callback,
2460
+ // 'success' => array($this, '__loadSuccess')
2461
+ 'success' => array($this, 'ai_loadSuccess')
2462
+ );
2463
+ phpQuery::ajax($ajax);
2464
+ return $this;
2465
+ }
2466
+ /**
2467
+ * @access private
2468
+ * @param $html
2469
+ * @return unknown_type
2470
+ */
2471
+ // public function __loadSuccess($html) {
2472
+ public function ai_loadSuccess($html) {
2473
+ if ($this->_loadSelector) {
2474
+ $html = phpQuery::newDocument($html)->find($this->_loadSelector);
2475
+ unset($this->_loadSelector);
2476
+ }
2477
+ foreach($this->stack(1) as $node) {
2478
+ phpQuery::pq($node, $this->getDocumentID())
2479
+ ->markup($html);
2480
+ }
2481
+ }
2482
+ /**
2483
+ * Enter description here...
2484
+ *
2485
+ * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2486
+ * @todo
2487
+ */
2488
+ public function css() {
2489
+ // TODO
2490
+ return $this;
2491
+ }
2492
+ /**
2493
+ * @todo
2494
+ *
2495
+ */
2496
+ public function show(){
2497
+ // TODO
2498
+ return $this;
2499
+ }
2500
+ /**
2501
+ * @todo
2502
+ *
2503
+ */
2504
+ public function hide(){
2505
+ // TODO
2506
+ return $this;
2507
+ }
2508
+ /**
2509
+ * Trigger a type of event on every matched element.
2510
+ *
2511
+ * @param unknown_type $type
2512
+ * @param unknown_type $data
2513
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2514
+ * @TODO support more than event in $type (space-separated)
2515
+ */
2516
+ public function trigger($type, $data = array()) {
2517
+ foreach($this->elements as $node)
2518
+ phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node);
2519
+ return $this;
2520
+ }
2521
+ /**
2522
+ * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions.
2523
+ *
2524
+ * @param unknown_type $type
2525
+ * @param unknown_type $data
2526
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2527
+ * @TODO
2528
+ */
2529
+ public function triggerHandler($type, $data = array()) {
2530
+ // TODO;
2531
+ }
2532
+ /**
2533
+ * Binds a handler to one or more events (like click) for each matched element.
2534
+ * Can also bind custom events.
2535
+ *
2536
+ * @param unknown_type $type
2537
+ * @param unknown_type $data Optional
2538
+ * @param unknown_type $callback
2539
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2540
+ * @TODO support '!' (exclusive) events
2541
+ * @TODO support more than event in $type (space-separated)
2542
+ */
2543
+ public function bind($type, $data, $callback = null) {
2544
+ // TODO check if $data is callable, not using is_callable
2545
+ if (! isset($callback)) {
2546
+ $callback = $data;
2547
+ $data = null;
2548
+ }
2549
+ foreach($this->elements as $node)
2550
+ phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback);
2551
+ return $this;
2552
+ }
2553
+ /**
2554
+ * Enter description here...
2555
+ *
2556
+ * @param unknown_type $type
2557
+ * @param unknown_type $callback
2558
+ * @return unknown
2559
+ * @TODO namespace events
2560
+ * @TODO support more than event in $type (space-separated)
2561
+ */
2562
+ public function unbind($type = null, $callback = null) {
2563
+ foreach($this->elements as $node)
2564
+ phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback);
2565
+ return $this;
2566
+ }
2567
+ /**
2568
+ * Enter description here...
2569
+ *
2570
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2571
+ */
2572
+ public function change($callback = null) {
2573
+ if ($callback)
2574
+ return $this->bind('change', $callback);
2575
+ return $this->trigger('change');
2576
+ }
2577
+ /**
2578
+ * Enter description here...
2579
+ *
2580
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2581
+ */
2582
+ public function submit($callback = null) {
2583
+ if ($callback)
2584
+ return $this->bind('submit', $callback);
2585
+ return $this->trigger('submit');
2586
+ }
2587
+ /**
2588
+ * Enter description here...
2589
+ *
2590
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2591
+ */
2592
+ public function click($callback = null) {
2593
+ if ($callback)
2594
+ return $this->bind('click', $callback);
2595
+ return $this->trigger('click');
2596
+ }
2597
+ /**
2598
+ * Enter description here...
2599
+ *
2600
+ * @param String|phpQuery
2601
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2602
+ */
2603
+ public function wrapAllOld($wrapper) {
2604
+ $wrapper = pq($wrapper)->_clone();
2605
+ if (! $wrapper->length() || ! $this->length() )
2606
+ return $this;
2607
+ $wrapper->insertBefore($this->elements[0]);
2608
+ $deepest = $wrapper->elements[0];
2609
+ while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
2610
+ $deepest = $deepest->firstChild;
2611
+ pq($deepest)->append($this);
2612
+ return $this;
2613
+ }
2614
+ /**
2615
+ * Enter description here...
2616
+ *
2617
+ * TODO testme...
2618
+ * @param String|phpQuery
2619
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2620
+ */
2621
+ public function wrapAll($wrapper) {
2622
+ if (! $this->length())
2623
+ return $this;
2624
+ return phpQuery::pq($wrapper, $this->getDocumentID())
2625
+ ->clone()
2626
+ ->insertBefore($this->get(0))
2627
+ ->map(array($this, '___wrapAllCallback'))
2628
+ ->append($this);
2629
+ }
2630
+ /**
2631
+ *
2632
+ * @param $node
2633
+ * @return unknown_type
2634
+ * @access private
2635
+ */
2636
+ public function ___wrapAllCallback($node) {
2637
+ $deepest = $node;
2638
+ while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
2639
+ $deepest = $deepest->firstChild;
2640
+ return $deepest;
2641
+ }
2642
+ /**
2643
+ * Enter description here...
2644
+ * NON JQUERY METHOD
2645
+ *
2646
+ * @param String|phpQuery
2647
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2648
+ */
2649
+ public function wrapAllPHP($codeBefore, $codeAfter) {
2650
+ return $this
2651
+ ->slice(0, 1)
2652
+ ->beforePHP($codeBefore)
2653
+ ->end()
2654
+ ->slice(-1)
2655
+ ->afterPHP($codeAfter)
2656
+ ->end();
2657
+ }
2658
+ /**
2659
+ * Enter description here...
2660
+ *
2661
+ * @param String|phpQuery
2662
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2663
+ */
2664
+ public function wrap($wrapper) {
2665
+ foreach($this->stack() as $node)
2666
+ phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper);
2667
+ return $this;
2668
+ }
2669
+ /**
2670
+ * Enter description here...
2671
+ *
2672
+ * @param String|phpQuery
2673
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2674
+ */
2675
+ public function wrapPHP($codeBefore, $codeAfter) {
2676
+ foreach($this->stack() as $node)
2677
+ phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter);
2678
+ return $this;
2679
+ }
2680
+ /**
2681
+ * Enter description here...
2682
+ *
2683
+ * @param String|phpQuery
2684
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2685
+ */
2686
+ public function wrapInner($wrapper) {
2687
+ foreach($this->stack() as $node)
2688
+ phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper);
2689
+ return $this;
2690
+ }
2691
+ /**
2692
+ * Enter description here...
2693
+ *
2694
+ * @param String|phpQuery
2695
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2696
+ */
2697
+ public function wrapInnerPHP($codeBefore, $codeAfter) {
2698
+ foreach($this->stack(1) as $node)
2699
+ phpQuery::pq($node, $this->getDocumentID())->contents()
2700
+ ->wrapAllPHP($codeBefore, $codeAfter);
2701
+ return $this;
2702
+ }
2703
+ /**
2704
+ * Enter description here...
2705
+ *
2706
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2707
+ * @testme Support for text nodes
2708
+ */
2709
+ public function contents() {
2710
+ $stack = array();
2711
+ foreach($this->stack(1) as $el) {
2712
+ // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56
2713
+ // if (! isset($el->childNodes))
2714
+ // continue;
2715
+ foreach($el->childNodes as $node) {
2716
+ $stack[] = $node;
2717
+ }
2718
+ }
2719
+ return $this->newInstance($stack);
2720
+ }
2721
+ /**
2722
+ * Enter description here...
2723
+ *
2724
+ * jQuery difference.
2725
+ *
2726
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2727
+ */
2728
+ public function contentsUnwrap() {
2729
+ foreach($this->stack(1) as $node) {
2730
+ if (! $node->parentNode )
2731
+ continue;
2732
+ $childNodes = array();
2733
+ // any modification in DOM tree breaks childNodes iteration, so cache them first
2734
+ foreach($node->childNodes as $chNode )
2735
+ $childNodes[] = $chNode;
2736
+ foreach($childNodes as $chNode )
2737
+ // $node->parentNode->appendChild($chNode);
2738
+ $node->parentNode->insertBefore($chNode, $node);
2739
+ $node->parentNode->removeChild($node);
2740
+ }
2741
+ return $this;
2742
+ }
2743
+ /**
2744
+ * Enter description here...
2745
+ *
2746
+ * jQuery difference.
2747
+ */
2748
+ public function switchWith($markup) {
2749
+ $markup = pq($markup, $this->getDocumentID());
2750
+ $content = null;
2751
+ foreach($this->stack(1) as $node) {
2752
+ pq($node)
2753
+ ->contents()->toReference($content)->end()
2754
+ ->replaceWith($markup->clone()->append($content));
2755
+ }
2756
+ return $this;
2757
+ }
2758
+ /**
2759
+ * Enter description here...
2760
+ *
2761
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2762
+ */
2763
+ public function eq($num) {
2764
+ $oldStack = $this->elements;
2765
+ $this->elementsBackup = $this->elements;
2766
+ $this->elements = array();
2767
+ if ( isset($oldStack[$num]) )
2768
+ $this->elements[] = $oldStack[$num];
2769
+ return $this->newInstance();
2770
+ }
2771
+ /**
2772
+ * Enter description here...
2773
+ *
2774
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2775
+ */
2776
+ public function size() {
2777
+ return count($this->elements);
2778
+ }
2779
+ /**
2780
+ * Enter description here...
2781
+ *
2782
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2783
+ * @deprecated Use length as attribute
2784
+ */
2785
+ public function length() {
2786
+ return $this->size();
2787
+ }
2788
+ public function count() {
2789
+ return $this->size();
2790
+ }
2791
+ /**
2792
+ * Enter description here...
2793
+ *
2794
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2795
+ * @todo $level
2796
+ */
2797
+ public function end($level = 1) {
2798
+ // $this->elements = array_pop( $this->history );
2799
+ // return $this;
2800
+ // $this->previous->DOM = $this->DOM;
2801
+ // $this->previous->XPath = $this->XPath;
2802
+ return $this->previous
2803
+ ? $this->previous
2804
+ : $this;
2805
+ }
2806
+ /**
2807
+ * Enter description here...
2808
+ * Normal use ->clone() .
2809
+ *
2810
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2811
+ * @access private
2812
+ */
2813
+ public function _clone() {
2814
+ $newStack = array();
2815
+ //pr(array('copy... ', $this->whois()));
2816
+ //$this->dumpHistory('copy');
2817
+ $this->elementsBackup = $this->elements;
2818
+ foreach($this->elements as $node) {
2819
+ $newStack[] = $node->cloneNode(true);
2820
+ }
2821
+ $this->elements = $newStack;
2822
+ return $this->newInstance();
2823
+ }
2824
+ /**
2825
+ * Enter description here...
2826
+ *
2827
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2828
+ */
2829
+ public function replaceWithPHP($code) {
2830
+ return $this->replaceWith(phpQuery::php($code));
2831
+ }
2832
+ /**
2833
+ * Enter description here...
2834
+ *
2835
+ * @param String|phpQuery $content
2836
+ * @link http://docs.jquery.com/Manipulation/replaceWith#content
2837
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2838
+ */
2839
+ public function replaceWith($content) {
2840
+ return $this->after($content)->remove();
2841
+ }
2842
+ /**
2843
+ * Enter description here...
2844
+ *
2845
+ * @param String $selector
2846
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2847
+ * @todo this works ?
2848
+ */
2849
+ public function replaceAll($selector) {
2850
+ foreach(phpQuery::pq($selector, $this->getDocumentID()) as $node)
2851
+ phpQuery::pq($node, $this->getDocumentID())
2852
+ ->after($this->_clone())
2853
+ ->remove();
2854
+ return $this;
2855
+ }
2856
+ /**
2857
+ * Enter description here...
2858
+ *
2859
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2860
+ */
2861
+ public function remove($selector = null) {
2862
+ $loop = $selector
2863
+ ? $this->filter($selector)->elements
2864
+ : $this->elements;
2865
+ foreach($loop as $node) {
2866
+ if (! $node->parentNode )
2867
+ continue;
2868
+ if (isset($node->tagName))
2869
+ $this->debug("Removing '{$node->tagName}'");
2870
+ $node->parentNode->removeChild($node);
2871
+ // Mutation event
2872
+ $event = new DOMEvent(array(
2873
+ 'target' => $node,
2874
+ 'type' => 'DOMNodeRemoved'
2875
+ ));
2876
+ phpQueryEvents::trigger($this->getDocumentID(),
2877
+ $event->type, array($event), $node
2878
+ );
2879
+ }
2880
+ return $this;
2881
+ }
2882
+ protected function markupEvents($newMarkup, $oldMarkup, $node) {
2883
+ if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) {
2884
+ $event = new DOMEvent(array(
2885
+ 'target' => $node,
2886
+ 'type' => 'change'
2887
+ ));
2888
+ phpQueryEvents::trigger($this->getDocumentID(),
2889
+ $event->type, array($event), $node
2890
+ );
2891
+ }
2892
+ }
2893
+ /**
2894
+ * jQuey difference
2895
+ *
2896
+ * @param $markup
2897
+ * @return unknown_type
2898
+ * @TODO trigger change event for textarea
2899
+ */
2900
+ public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2901
+ $args = func_get_args();
2902
+ if ($this->documentWrapper->isXML)
2903
+ return call_user_func_array(array($this, 'xml'), $args);
2904
+ else
2905
+ return call_user_func_array(array($this, 'html'), $args);
2906
+ }
2907
+ /**
2908
+ * jQuey difference
2909
+ *
2910
+ * @param $markup
2911
+ * @return unknown_type
2912
+ */
2913
+ public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2914
+ $args = func_get_args();
2915
+ if ($this->documentWrapper->isXML)
2916
+ return call_user_func_array(array($this, 'xmlOuter'), $args);
2917
+ else
2918
+ return call_user_func_array(array($this, 'htmlOuter'), $args);
2919
+ }
2920
+ /**
2921
+ * Enter description here...
2922
+ *
2923
+ * @param unknown_type $html
2924
+ * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2925
+ * @TODO force html result
2926
+ */
2927
+ public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2928
+ if (isset($html)) {
2929
+ // INSERT
2930
+ $nodes = $this->documentWrapper->import($html);
2931
+ $this->empty();
2932
+ foreach($this->stack(1) as $alreadyAdded => $node) {
2933
+ // for now, limit events for textarea
2934
+ if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
2935
+ $oldHtml = pq($node, $this->getDocumentID())->markup();
2936
+ foreach($nodes as $newNode) {
2937
+ $node->appendChild($alreadyAdded
2938
+ ? $newNode->cloneNode(true)
2939
+ : $newNode
2940
+ );
2941
+ }
2942
+ // for now, limit events for textarea
2943
+ if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
2944
+ $this->markupEvents($html, $oldHtml, $node);
2945
+ }
2946
+ return $this;
2947
+ } else {
2948
+ // FETCH
2949
+ $return = $this->documentWrapper->markup($this->elements, true);
2950
+ $args = func_get_args();
2951
+ foreach(array_slice($args, 1) as $callback) {
2952
+ $return = phpQuery::callbackRun($callback, array($return));
2953
+ }
2954
+ return $return;
2955
+ }
2956
+ }
2957
+ /**
2958
+ * @TODO force xml result
2959
+ */
2960
+ public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2961
+ $args = func_get_args();
2962
+ return call_user_func_array(array($this, 'html'), $args);
2963
+ }
2964
+ /**
2965
+ * Enter description here...
2966
+ * @TODO force html result
2967
+ *
2968
+ * @return String
2969
+ */
2970
+ public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2971
+ $markup = $this->documentWrapper->markup($this->elements);
2972
+ // pass thou callbacks
2973
+ $args = func_get_args();
2974
+ foreach($args as $callback) {
2975
+ $markup = phpQuery::callbackRun($callback, array($markup));
2976
+ }
2977
+ return $markup;
2978
+ }
2979
+ /**
2980
+ * @TODO force xml result
2981
+ */
2982
+ public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2983
+ $args = func_get_args();
2984
+ return call_user_func_array(array($this, 'htmlOuter'), $args);
2985
+ }
2986
+ public function __toString() {
2987
+ return $this->markupOuter();
2988
+ }
2989
+ /**
2990
+ * Just like html(), but returns markup with VALID (dangerous) PHP tags.
2991
+ *
2992
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2993
+ * @todo support returning markup with PHP tags when called without param
2994
+ */
2995
+ public function php($code = null) {
2996
+ return $this->markupPHP($code);
2997
+ }
2998
+ /**
2999
+ * Enter description here...
3000
+ *
3001
+ * @param $code
3002
+ * @return unknown_type
3003
+ */
3004
+ public function markupPHP($code = null) {
3005
+ return isset($code)
3006
+ ? $this->markup(phpQuery::php($code))
3007
+ : phpQuery::markupToPHP($this->markup());
3008
+ }
3009
+ /**
3010
+ * Enter description here...
3011
+ *
3012
+ * @param $code
3013
+ * @return unknown_type
3014
+ */
3015
+ public function markupOuterPHP() {
3016
+ return phpQuery::markupToPHP($this->markupOuter());
3017
+ }
3018
+ /**
3019
+ * Enter description here...
3020
+ *
3021
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3022
+ */
3023
+ public function children($selector = null) {
3024
+ $stack = array();
3025
+ foreach($this->stack(1) as $node) {
3026
+ // foreach($node->getElementsByTagName('*') as $newNode) {
3027
+ foreach($node->childNodes as $newNode) {
3028
+ if ($newNode->nodeType != 1)
3029
+ continue;
3030
+ if ($selector && ! $this->is($selector, $newNode))
3031
+ continue;
3032
+ if ($this->elementsContainsNode($newNode, $stack))
3033
+ continue;
3034
+ $stack[] = $newNode;
3035
+ }
3036
+ }
3037
+ $this->elementsBackup = $this->elements;
3038
+ $this->elements = $stack;
3039
+ return $this->newInstance();
3040
+ }
3041
+ /**
3042
+ * Enter description here...
3043
+ *
3044
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3045
+ */
3046
+ public function ancestors($selector = null) {
3047
+ return $this->children( $selector );
3048
+ }
3049
+ /**
3050
+ * Enter description here...
3051
+ *
3052
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3053
+ */
3054
+ public function append( $content) {
3055
+ return $this->insert($content, __FUNCTION__);
3056
+ }
3057
+ /**
3058
+ * Enter description here...
3059
+ *
3060
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3061
+ */
3062
+ public function appendPHP( $content) {
3063
+ return $this->insert("<php><!-- {$content} --></php>", 'append');
3064
+ }
3065
+ /**
3066
+ * Enter description here...
3067
+ *
3068
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3069
+ */
3070
+ public function appendTo( $seletor) {
3071
+ return $this->insert($seletor, __FUNCTION__);
3072
+ }
3073
+ /**
3074
+ * Enter description here...
3075
+ *
3076
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3077
+ */
3078
+ public function prepend( $content) {
3079
+ return $this->insert($content, __FUNCTION__);
3080
+ }
3081
+ /**
3082
+ * Enter description here...
3083
+ *
3084
+ * @todo accept many arguments, which are joined, arrays maybe also
3085
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3086
+ */
3087
+ public function prependPHP( $content) {
3088
+ return $this->insert("<php><!-- {$content} --></php>", 'prepend');
3089
+ }
3090
+ /**
3091
+ * Enter description here...
3092
+ *
3093
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3094
+ */
3095
+ public function prependTo( $seletor) {
3096
+ return $this->insert($seletor, __FUNCTION__);
3097
+ }
3098
+ /**
3099
+ * Enter description here...
3100
+ *
3101
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3102
+ */
3103
+ public function before($content) {
3104
+ return $this->insert($content, __FUNCTION__);
3105
+ }
3106
+ /**
3107
+ * Enter description here...
3108
+ *
3109
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3110
+ */
3111
+ public function beforePHP( $content) {
3112
+ return $this->insert("<php><!-- {$content} --></php>", 'before');
3113
+ }
3114
+ /**
3115
+ * Enter description here...
3116
+ *
3117
+ * @param String|phpQuery
3118
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3119
+ */
3120
+ public function insertBefore( $seletor) {
3121
+ return $this->insert($seletor, __FUNCTION__);
3122
+ }
3123
+ /**
3124
+ * Enter description here...
3125
+ *
3126
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3127
+ */
3128
+ public function after( $content) {
3129
+ return $this->insert($content, __FUNCTION__);
3130
+ }
3131
+ /**
3132
+ * Enter description here...
3133
+ *
3134
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3135
+ */
3136
+ public function afterPHP( $content) {
3137
+ return $this->insert("<php><!-- {$content} --></php>", 'after');
3138
+ }
3139
+ /**
3140
+ * Enter description here...
3141
+ *
3142
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3143
+ */
3144
+ public function insertAfter( $seletor) {
3145
+ return $this->insert($seletor, __FUNCTION__);
3146
+ }
3147
+ /**
3148
+ * Internal insert method. Don't use it.
3149
+ *
3150
+ * @param unknown_type $target
3151
+ * @param unknown_type $type
3152
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3153
+ * @access private
3154
+ */
3155
+ public function insert($target, $type) {
3156
+ $this->debug("Inserting data with '{$type}'");
3157
+ $to = false;
3158
+ switch( $type) {
3159
+ case 'appendTo':
3160
+ case 'prependTo':
3161
+ case 'insertBefore':
3162
+ case 'insertAfter':
3163
+ $to = true;
3164
+ }
3165
+ switch(gettype($target)) {
3166
+ case 'string':
3167
+ $insertFrom = $insertTo = array();
3168
+ if ($to) {
3169
+ // INSERT TO
3170
+ $insertFrom = $this->elements;
3171
+ if (phpQuery::isMarkup($target)) {
3172
+ // $target is new markup, import it
3173
+ $insertTo = $this->documentWrapper->import($target);
3174
+ // insert into selected element
3175
+ } else {
3176
+ // $tagret is a selector
3177
+ $thisStack = $this->elements;
3178
+ $this->toRoot();
3179
+ $insertTo = $this->find($target)->elements;
3180
+ $this->elements = $thisStack;
3181
+ }
3182
+ } else {
3183
+ // INSERT FROM
3184
+ $insertTo = $this->elements;
3185
+ $insertFrom = $this->documentWrapper->import($target);
3186
+ }
3187
+ break;
3188
+ case 'object':
3189
+ $insertFrom = $insertTo = array();
3190
+ // phpQuery
3191
+ if ($target instanceof self) {
3192
+ if ($to) {
3193
+ $insertTo = $target->elements;
3194
+ if ($this->documentFragment && $this->stackIsRoot())
3195
+ // get all body children
3196
+ // $loop = $this->find('body > *')->elements;
3197
+ // TODO test it, test it hard...
3198
+ // $loop = $this->newInstance($this->root)->find('> *')->elements;
3199
+ $loop = $this->root->childNodes;
3200
+ else
3201
+ $loop = $this->elements;
3202
+ // import nodes if needed
3203
+ $insertFrom = $this->getDocumentID() == $target->getDocumentID()
3204
+ ? $loop
3205
+ : $target->documentWrapper->import($loop);
3206
+ } else {
3207
+ $insertTo = $this->elements;
3208
+ if ( $target->documentFragment && $target->stackIsRoot() )
3209
+ // get all body children
3210
+ // $loop = $target->find('body > *')->elements;
3211
+ $loop = $target->root->childNodes;
3212
+ else
3213
+ $loop = $target->elements;
3214
+ // import nodes if needed
3215
+ $insertFrom = $this->getDocumentID() == $target->getDocumentID()
3216
+ ? $loop
3217
+ : $this->documentWrapper->import($loop);
3218
+ }
3219
+ // DOMNODE
3220
+ } elseif ($target instanceof DOMNODE) {
3221
+ // import node if needed
3222
+ // if ( $target->ownerDocument != $this->DOM )
3223
+ // $target = $this->DOM->importNode($target, true);
3224
+ if ( $to) {
3225
+ $insertTo = array($target);
3226
+ if ($this->documentFragment && $this->stackIsRoot())
3227
+ // get all body children
3228
+ $loop = $this->root->childNodes;
3229
+ // $loop = $this->find('body > *')->elements;
3230
+ else
3231
+ $loop = $this->elements;
3232
+ foreach($loop as $fromNode)
3233
+ // import nodes if needed
3234
+ $insertFrom[] = ! $fromNode->ownerDocument->isSameNode($target->ownerDocument)
3235
+ ? $target->ownerDocument->importNode($fromNode, true)
3236
+ : $fromNode;
3237
+ } else {
3238
+ // import node if needed
3239
+ if (! $target->ownerDocument->isSameNode($this->document))
3240
+ $target = $this->document->importNode($target, true);
3241
+ $insertTo = $this->elements;
3242
+ $insertFrom[] = $target;
3243
+ }
3244
+ }
3245
+ break;
3246
+ }
3247
+ phpQuery::debug("From ".count($insertFrom)."; To ".count($insertTo)." nodes");
3248
+ foreach($insertTo as $insertNumber => $toNode) {
3249
+ // we need static relative elements in some cases
3250
+ switch( $type) {
3251
+ case 'prependTo':
3252
+ case 'prepend':
3253
+ $firstChild = $toNode->firstChild;
3254
+ break;
3255
+ case 'insertAfter':
3256
+ case 'after':
3257
+ $nextSibling = $toNode->nextSibling;
3258
+ break;
3259
+ }
3260
+ foreach($insertFrom as $fromNode) {
3261
+ // clone if inserted already before
3262
+ $insert = $insertNumber
3263
+ ? $fromNode->cloneNode(true)
3264
+ : $fromNode;
3265
+ switch($type) {
3266
+ case 'appendTo':
3267
+ case 'append':
3268
+ // $toNode->insertBefore(
3269
+ // $fromNode,
3270
+ // $toNode->lastChild->nextSibling
3271
+ // );
3272
+ $toNode->appendChild($insert);
3273
+ $eventTarget = $insert;
3274
+ break;
3275
+ case 'prependTo':
3276
+ case 'prepend':
3277
+ $toNode->insertBefore(
3278
+ $insert,
3279
+ $firstChild
3280
+ );
3281
+ break;
3282
+ case 'insertBefore':
3283
+ case 'before':
3284
+ if (! $toNode->parentNode)
3285
+ throw new Exception("No parentNode, can't do {$type}()");
3286
+ else
3287
+ $toNode->parentNode->insertBefore(
3288
+ $insert,
3289
+ $toNode
3290
+ );
3291
+ break;
3292
+ case 'insertAfter':
3293
+ case 'after':
3294
+ if (! $toNode->parentNode)
3295
+ throw new Exception("No parentNode, can't do {$type}()");
3296
+ else
3297
+ $toNode->parentNode->insertBefore(
3298
+ $insert,
3299
+ $nextSibling
3300
+ );
3301
+ break;
3302
+ }
3303
+ // Mutation event
3304
+ $event = new DOMEvent(array(
3305
+ 'target' => $insert,
3306
+ 'type' => 'DOMNodeInserted'
3307
+ ));
3308
+ phpQueryEvents::trigger($this->getDocumentID(),
3309
+ $event->type, array($event), $insert
3310
+ );
3311
+ }
3312
+ }
3313
+ return $this;
3314
+ }
3315
+ /**
3316
+ * Enter description here...
3317
+ *
3318
+ * @return Int
3319
+ */
3320
+ public function index($subject) {
3321
+ $index = -1;
3322
+ $subject = $subject instanceof phpQueryObject
3323
+ ? $subject->elements[0]
3324
+ : $subject;
3325
+ foreach($this->newInstance() as $k => $node) {
3326
+ if ($node->isSameNode($subject))
3327
+ $index = $k;
3328
+ }
3329
+ return $index;
3330
+ }
3331
+ /**
3332
+ * Enter description here...
3333
+ *
3334
+ * @param unknown_type $start
3335
+ * @param unknown_type $end
3336
+ *
3337
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3338
+ * @testme
3339
+ */
3340
+ public function slice($start, $end = null) {
3341
+ // $last = count($this->elements)-1;
3342
+ // $end = $end
3343
+ // ? min($end, $last)
3344
+ // : $last;
3345
+ // if ($start < 0)
3346
+ // $start = $last+$start;
3347
+ // if ($start > $last)
3348
+ // return array();
3349
+ if ($end > 0)
3350
+ $end = $end-$start;
3351
+ return $this->newInstance(
3352
+ array_slice($this->elements, $start, $end)
3353
+ );
3354
+ }
3355
+ /**
3356
+ * Enter description here...
3357
+ *
3358
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3359
+ */
3360
+ public function reverse() {
3361
+ $this->elementsBackup = $this->elements;
3362
+ $this->elements = array_reverse($this->elements);
3363
+ return $this->newInstance();
3364
+ }
3365
+ /**
3366
+ * Return joined text content.
3367
+ * @return String
3368
+ */
3369
+ public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) {
3370
+ if (isset($text))
3371
+ return $this->html(htmlspecialchars($text));
3372
+ $args = func_get_args();
3373
+ $args = array_slice($args, 1);
3374
+ $return = '';
3375
+ foreach($this->elements as $node) {
3376
+ $text = $node->textContent;
3377
+ if (count($this->elements) > 1 && $text)
3378
+ $text .= "\n";
3379
+ foreach($args as $callback) {
3380
+ $text = phpQuery::callbackRun($callback, array($text));
3381
+ }
3382
+ $return .= $text;
3383
+ }
3384
+ return $return;
3385
+ }
3386
+ /**
3387
+ * Enter description here...
3388
+ *
3389
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3390
+ */
3391
+ public function plugin($class, $file = null) {
3392
+ phpQuery::plugin($class, $file);
3393
+ return $this;
3394
+ }
3395
+ /**
3396
+ * Deprecated, use $pq->plugin() instead.
3397
+ *
3398
+ * @deprecated
3399
+ * @param $class
3400
+ * @param $file
3401
+ * @return unknown_type
3402
+ */
3403
+ public static function extend($class, $file = null) {
3404
+ return $this->plugin($class, $file);
3405
+ }
3406
+ /**
3407
+ *
3408
+ * @access private
3409
+ * @param $method
3410
+ * @param $args
3411
+ * @return unknown_type
3412
+ */
3413
+ public function __call($method, $args) {
3414
+ $aliasMethods = array('clone', 'empty');
3415
+ if (isset(phpQuery::$extendMethods[$method])) {
3416
+ array_unshift($args, $this);
3417
+ return phpQuery::callbackRun(
3418
+ phpQuery::$extendMethods[$method], $args
3419
+ );
3420
+ } else if (isset(phpQuery::$pluginsMethods[$method])) {
3421
+ array_unshift($args, $this);
3422
+ $class = phpQuery::$pluginsMethods[$method];
3423
+ $realClass = "phpQueryObjectPlugin_$class";
3424
+ $return = call_user_func_array(
3425
+ array($realClass, $method),
3426
+ $args
3427
+ );
3428
+ // XXX deprecate ?
3429
+ return is_null($return)
3430
+ ? $this
3431
+ : $return;
3432
+ } else if (in_array($method, $aliasMethods)) {
3433
+ return call_user_func_array(array($this, '_'.$method), $args);
3434
+ } else
3435
+ throw new Exception("Method '{$method}' doesnt exist");
3436
+ }
3437
+ /**
3438
+ * Safe rename of next().
3439
+ *
3440
+ * Use it ONLY when need to call next() on an iterated object (in same time).
3441
+ * Normaly there is no need to do such thing ;)
3442
+ *
3443
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3444
+ * @access private
3445
+ */
3446
+ public function _next($selector = null) {
3447
+ return $this->newInstance(
3448
+ $this->getElementSiblings('nextSibling', $selector, true)
3449
+ );
3450
+ }
3451
+ /**
3452
+ * Use prev() and next().
3453
+ *
3454
+ * @deprecated
3455
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3456
+ * @access private
3457
+ */
3458
+ public function _prev($selector = null) {
3459
+ return $this->prev($selector);
3460
+ }
3461
+ /**
3462
+ * Enter description here...
3463
+ *
3464
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3465
+ */
3466
+ public function prev($selector = null) {
3467
+ return $this->newInstance(
3468
+ $this->getElementSiblings('previousSibling', $selector, true)
3469
+ );
3470
+ }
3471
+ /**
3472
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3473
+ * @todo
3474
+ */
3475
+ public function prevAll($selector = null) {
3476
+ return $this->newInstance(
3477
+ $this->getElementSiblings('previousSibling', $selector)
3478
+ );
3479
+ }
3480
+ /**
3481
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3482
+ * @todo FIXME: returns source elements insted of next siblings
3483
+ */
3484
+ public function nextAll($selector = null) {
3485
+ return $this->newInstance(
3486
+ $this->getElementSiblings('nextSibling', $selector)
3487
+ );
3488
+ }
3489
+ /**
3490
+ * @access private
3491
+ */
3492
+ protected function getElementSiblings($direction, $selector = null, $limitToOne = false) {
3493
+ $stack = array();
3494
+ $count = 0;
3495
+ foreach($this->stack() as $node) {
3496
+ $test = $node;
3497
+ while( isset($test->{$direction}) && $test->{$direction}) {
3498
+ $test = $test->{$direction};
3499
+ if (! $test instanceof DOMELEMENT)
3500
+ continue;
3501
+ $stack[] = $test;
3502
+ if ($limitToOne)
3503
+ break;
3504
+ }
3505
+ }
3506
+ if ($selector) {
3507
+ $stackOld = $this->elements;
3508
+ $this->elements = $stack;
3509
+ $stack = $this->filter($selector, true)->stack();
3510
+ $this->elements = $stackOld;
3511
+ }
3512
+ return $stack;
3513
+ }
3514
+ /**
3515
+ * Enter description here...
3516
+ *
3517
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3518
+ */
3519
+ public function siblings($selector = null) {
3520
+ $stack = array();
3521
+ $siblings = array_merge(
3522
+ $this->getElementSiblings('previousSibling', $selector),
3523
+ $this->getElementSiblings('nextSibling', $selector)
3524
+ );
3525
+ foreach($siblings as $node) {
3526
+ if (! $this->elementsContainsNode($node, $stack))
3527
+ $stack[] = $node;
3528
+ }
3529
+ return $this->newInstance($stack);
3530
+ }
3531
+ /**
3532
+ * Enter description here...
3533
+ *
3534
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3535
+ */
3536
+ public function not($selector = null) {
3537
+ if (is_string($selector))
3538
+ phpQuery::debug(array('not', $selector));
3539
+ else
3540
+ phpQuery::debug('not');
3541
+ $stack = array();
3542
+ if ($selector instanceof self || $selector instanceof DOMNODE) {
3543
+ foreach($this->stack() as $node) {
3544
+ if ($selector instanceof self) {
3545
+ $matchFound = false;
3546
+ foreach($selector->stack() as $notNode) {
3547
+ if ($notNode->isSameNode($node))
3548
+ $matchFound = true;
3549
+ }
3550
+ if (! $matchFound)
3551
+ $stack[] = $node;
3552
+ } else if ($selector instanceof DOMNODE) {
3553
+ if (! $selector->isSameNode($node))
3554
+ $stack[] = $node;
3555
+ } else {
3556
+ if (! $this->is($selector))
3557
+ $stack[] = $node;
3558
+ }
3559
+ }
3560
+ } else {
3561
+ $orgStack = $this->stack();
3562
+ $matched = $this->filter($selector, true)->stack();
3563
+ // $matched = array();
3564
+ // // simulate OR in filter() instead of AND 5y
3565
+ // foreach($this->parseSelector($selector) as $s) {
3566
+ // $matched = array_merge($matched,
3567
+ // $this->filter(array($s))->stack()
3568
+ // );
3569
+ // }
3570
+ foreach($orgStack as $node)
3571
+ if (! $this->elementsContainsNode($node, $matched))
3572
+ $stack[] = $node;
3573
+ }
3574
+ return $this->newInstance($stack);
3575
+ }
3576
+ /**
3577
+ * Enter description here...
3578
+ *
3579
+ * @param string|phpQueryObject
3580
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3581
+ */
3582
+ public function add($selector = null) {
3583
+ if (! $selector)
3584
+ return $this;
3585
+ $stack = array();
3586
+ $this->elementsBackup = $this->elements;
3587
+ $found = phpQuery::pq($selector, $this->getDocumentID());
3588
+ $this->merge($found->elements);
3589
+ return $this->newInstance();
3590
+ }
3591
+ /**
3592
+ * @access private
3593
+ */
3594
+ protected function merge() {
3595
+ foreach(func_get_args() as $nodes)
3596
+ foreach($nodes as $newNode )
3597
+ if (! $this->elementsContainsNode($newNode) )
3598
+ $this->elements[] = $newNode;
3599
+ }
3600
+ /**
3601
+ * @access private
3602
+ * TODO refactor to stackContainsNode
3603
+ */
3604
+ protected function elementsContainsNode($nodeToCheck, $elementsStack = null) {
3605
+ $loop = ! is_null($elementsStack)
3606
+ ? $elementsStack
3607
+ : $this->elements;
3608
+ foreach($loop as $node) {
3609
+ if ( $node->isSameNode( $nodeToCheck ) )
3610
+ return true;
3611
+ }
3612
+ return false;
3613
+ }
3614
+ /**
3615
+ * Enter description here...
3616
+ *
3617
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3618
+ */
3619
+ public function parent($selector = null) {
3620
+ $stack = array();
3621
+ foreach($this->elements as $node )
3622
+ if ( $node->parentNode && ! $this->elementsContainsNode($node->parentNode, $stack) )
3623
+ $stack[] = $node->parentNode;
3624
+ $this->elementsBackup = $this->elements;
3625
+ $this->elements = $stack;
3626
+ if ( $selector )
3627
+ $this->filter($selector, true);
3628
+ return $this->newInstance();
3629
+ }
3630
+ /**
3631
+ * Enter description here...
3632
+ *
3633
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3634
+ */
3635
+ public function parents($selector = null) {
3636
+ $stack = array();
3637
+ if (! $this->elements )
3638
+ $this->debug('parents() - stack empty');
3639
+ foreach($this->elements as $node) {
3640
+ $test = $node;
3641
+ while( $test->parentNode) {
3642
+ $test = $test->parentNode;
3643
+ if ($this->isRoot($test))
3644
+ break;
3645
+ if (! $this->elementsContainsNode($test, $stack)) {
3646
+ $stack[] = $test;
3647
+ continue;
3648
+ }
3649
+ }
3650
+ }
3651
+ $this->elementsBackup = $this->elements;
3652
+ $this->elements = $stack;
3653
+ if ( $selector )
3654
+ $this->filter($selector, true);
3655
+ return $this->newInstance();
3656
+ }
3657
+ /**
3658
+ * Internal stack iterator.
3659
+ *
3660
+ * @access private
3661
+ */
3662
+ public function stack($nodeTypes = null) {
3663
+ if (!isset($nodeTypes))
3664
+ return $this->elements;
3665
+ if (!is_array($nodeTypes))
3666
+ $nodeTypes = array($nodeTypes);
3667
+ $return = array();
3668
+ foreach($this->elements as $node) {
3669
+ if (in_array($node->nodeType, $nodeTypes))
3670
+ $return[] = $node;
3671
+ }
3672
+ return $return;
3673
+ }
3674
+ // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes
3675
+ protected function attrEvents($attr, $oldAttr, $oldValue, $node) {
3676
+ // skip events for XML documents
3677
+ if (! $this->isXHTML() && ! $this->isHTML())
3678
+ return;
3679
+ $event = null;
3680
+ // identify
3681
+ $isInputValue = $node->tagName == 'input'
3682
+ && (
3683
+ in_array($node->getAttribute('type'),
3684
+ array('text', 'password', 'hidden'))
3685
+ || !$node->getAttribute('type')
3686
+ );
3687
+ $isRadio = $node->tagName == 'input'
3688
+ && $node->getAttribute('type') == 'radio';
3689
+ $isCheckbox = $node->tagName == 'input'
3690
+ && $node->getAttribute('type') == 'checkbox';
3691
+ $isOption = $node->tagName == 'option';
3692
+ if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) {
3693
+ $event = new DOMEvent(array(
3694
+ 'target' => $node,
3695
+ 'type' => 'change'
3696
+ ));
3697
+ } else if (($isRadio || $isCheckbox) && $attr == 'checked' && (
3698
+ // check
3699
+ (! $oldAttr && $node->hasAttribute($attr))
3700
+ // un-check
3701
+ || (! $node->hasAttribute($attr) && $oldAttr)
3702
+ )) {
3703
+ $event = new DOMEvent(array(
3704
+ 'target' => $node,
3705
+ 'type' => 'change'
3706
+ ));
3707
+ } else if ($isOption && $node->parentNode && $attr == 'selected' && (
3708
+ // select
3709
+ (! $oldAttr && $node->hasAttribute($attr))
3710
+ // un-select
3711
+ || (! $node->hasAttribute($attr) && $oldAttr)
3712
+ )) {
3713
+ $event = new DOMEvent(array(
3714
+ 'target' => $node->parentNode,
3715
+ 'type' => 'change'
3716
+ ));
3717
+ }
3718
+ if ($event) {
3719
+ phpQueryEvents::trigger($this->getDocumentID(),
3720
+ $event->type, array($event), $node
3721
+ );
3722
+ }
3723
+ }
3724
+ public function attr($attr = null, $value = null) {
3725
+ foreach($this->stack(1) as $node) {
3726
+ if (! is_null($value)) {
3727
+ $loop = $attr == '*'
3728
+ ? $this->getNodeAttrs($node)
3729
+ : array($attr);
3730
+ foreach($loop as $a) {
3731
+ $oldValue = $node->getAttribute($a);
3732
+ $oldAttr = $node->hasAttribute($a);
3733
+ // TODO raises an error when charset other than UTF-8
3734
+ // while document's charset is also not UTF-8
3735
+ @$node->setAttribute($a, $value);
3736
+ $this->attrEvents($a, $oldAttr, $oldValue, $node);
3737
+ }
3738
+ } else if ($attr == '*') {
3739
+ // jQuery difference
3740
+ $return = array();
3741
+ foreach($node->attributes as $n => $v)
3742
+ $return[$n] = $v->value;
3743
+ return $return;
3744
+ } else
3745
+ return $node->hasAttribute($attr)
3746
+ ? $node->getAttribute($attr)
3747
+ : null;
3748
+ }
3749
+ return is_null($value)
3750
+ ? '' : $this;
3751
+ }
3752
+ /**
3753
+ * @access private
3754
+ */
3755
+ protected function getNodeAttrs($node) {
3756
+ $return = array();
3757
+ foreach($node->attributes as $n => $o)
3758
+ $return[] = $n;
3759
+ return $return;
3760
+ }
3761
+ /**
3762
+ * Enter description here...
3763
+ *
3764
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3765
+ * @todo check CDATA ???
3766
+ */
3767
+ public function attrPHP($attr, $code) {
3768
+ if (! is_null($code)) {
3769
+ $value = '<'.'?php '.$code.' ?'.'>';
3770
+ // TODO tempolary solution
3771
+ // http://code.google.com/p/phpquery/issues/detail?id=17
3772
+ // if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII')
3773
+ // $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES');
3774
+ }
3775
+ foreach($this->stack(1) as $node) {
3776
+ if (! is_null($code)) {
3777
+ // $attrNode = $this->DOM->createAttribute($attr);
3778
+ $node->setAttribute($attr, $value);
3779
+ // $attrNode->value = $value;
3780
+ // $node->appendChild($attrNode);
3781
+ } else if ( $attr == '*') {
3782
+ // jQuery diff
3783
+ $return = array();
3784
+ foreach($node->attributes as $n => $v)
3785
+ $return[$n] = $v->value;
3786
+ return $return;
3787
+ } else
3788
+ return $node->getAttribute($attr);
3789
+ }
3790
+ return $this;
3791
+ }
3792
+ /**
3793
+ * Enter description here...
3794
+ *
3795
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3796
+ */
3797
+ public function removeAttr($attr) {
3798
+ foreach($this->stack(1) as $node) {
3799
+ $loop = $attr == '*'
3800
+ ? $this->getNodeAttrs($node)
3801
+ : array($attr);
3802
+ foreach($loop as $a) {
3803
+ $oldValue = $node->getAttribute($a);
3804
+ $node->removeAttribute($a);
3805
+ $this->attrEvents($a, $oldValue, null, $node);
3806
+ }
3807
+ }
3808
+ return $this;
3809
+ }
3810
+ /**
3811
+ * Return form element value.
3812
+ *
3813
+ * @return String Fields value.
3814
+ */
3815
+ public function val($val = null) {
3816
+ if (! isset($val)) {
3817
+ if ($this->eq(0)->is('select')) {
3818
+ $selected = $this->eq(0)->find('option[selected=selected]');
3819
+ if ($selected->is('[value]'))
3820
+ return $selected->attr('value');
3821
+ else
3822
+ return $selected->text();
3823
+ } else if ($this->eq(0)->is('textarea'))
3824
+ return $this->eq(0)->markup();
3825
+ else
3826
+ return $this->eq(0)->attr('value');
3827
+ } else {
3828
+ $_val = null;
3829
+ foreach($this->stack(1) as $node) {
3830
+ $node = pq($node, $this->getDocumentID());
3831
+ if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) {
3832
+ $isChecked = in_array($node->attr('value'), $val)
3833
+ || in_array($node->attr('name'), $val);
3834
+ if ($isChecked)
3835
+ $node->attr('checked', 'checked');
3836
+ else
3837
+ $node->removeAttr('checked');
3838
+ } else if ($node->get(0)->tagName == 'select') {
3839
+ if (! isset($_val)) {
3840
+ $_val = array();
3841
+ if (! is_array($val))
3842
+ $_val = array((string)$val);
3843
+ else
3844
+ foreach($val as $v)
3845
+ $_val[] = $v;
3846
+ }
3847
+ foreach($node['option']->stack(1) as $option) {
3848
+ $option = pq($option, $this->getDocumentID());
3849
+ $selected = false;
3850
+ // XXX: workaround for string comparsion, see issue #96
3851
+ // http://code.google.com/p/phpquery/issues/detail?id=96
3852
+ $selected = is_null($option->attr('value'))
3853
+ ? in_array($option->markup(), $_val)
3854
+ : in_array($option->attr('value'), $_val);
3855
+ // $optionValue = $option->attr('value');
3856
+ // $optionText = $option->text();
3857
+ // $optionTextLenght = mb_strlen($optionText);
3858
+ // foreach($_val as $v)
3859
+ // if ($optionValue == $v)
3860
+ // $selected = true;
3861
+ // else if ($optionText == $v && $optionTextLenght == mb_strlen($v))
3862
+ // $selected = true;
3863
+ if ($selected)
3864
+ $option->attr('selected', 'selected');
3865
+ else
3866
+ $option->removeAttr('selected');
3867
+ }
3868
+ } else if ($node->get(0)->tagName == 'textarea')
3869
+ $node->markup($val);
3870
+ else
3871
+ $node->attr('value', $val);
3872
+ }
3873
+ }
3874
+ return $this;
3875
+ }
3876
+ /**
3877
+ * Enter description here...
3878
+ *
3879
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3880
+ */
3881
+ public function andSelf() {
3882
+ if ( $this->previous )
3883
+ $this->elements = array_merge($this->elements, $this->previous->elements);
3884
+ return $this;
3885
+ }
3886
+ /**
3887
+ * Enter description here...
3888
+ *
3889
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3890
+ */
3891
+ public function addClass( $className) {
3892
+ if (! $className)
3893
+ return $this;
3894
+ foreach($this->stack(1) as $node) {
3895
+ if (! $this->is(".$className", $node))
3896
+ $node->setAttribute(
3897
+ 'class',
3898
+ trim($node->getAttribute('class').' '.$className)
3899
+ );
3900
+ }
3901
+ return $this;
3902
+ }
3903
+ /**
3904
+ * Enter description here...
3905
+ *
3906
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3907
+ */
3908
+ public function addClassPHP( $className) {
3909
+ foreach($this->stack(1) as $node) {
3910
+ $classes = $node->getAttribute('class');
3911
+ $newValue = $classes
3912
+ ? $classes.' <'.'?php '.$className.' ?'.'>'
3913
+ : '<'.'?php '.$className.' ?'.'>';
3914
+ $node->setAttribute('class', $newValue);
3915
+ }
3916
+ return $this;
3917
+ }
3918
+ /**
3919
+ * Enter description here...
3920
+ *
3921
+ * @param string $className
3922
+ * @return bool
3923
+ */
3924
+ public function hasClass($className) {
3925
+ foreach($this->stack(1) as $node) {
3926
+ if ( $this->is(".$className", $node))
3927
+ return true;
3928
+ }
3929
+ return false;
3930
+ }
3931
+ /**
3932
+ * Enter description here...
3933
+ *
3934
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3935
+ */
3936
+ public function removeClass($className) {
3937
+ foreach($this->stack(1) as $node) {
3938
+ $classes = explode( ' ', $node->getAttribute('class'));
3939
+ if ( in_array($className, $classes)) {
3940
+ $classes = array_diff($classes, array($className));
3941
+ if ( $classes )
3942
+ $node->setAttribute('class', implode(' ', $classes));
3943
+ else
3944
+ $node->removeAttribute('class');
3945
+ }
3946
+ }
3947
+ return $this;
3948
+ }
3949
+ /**
3950
+ * Enter description here...
3951
+ *
3952
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3953
+ */
3954
+ public function toggleClass($className) {
3955
+ foreach($this->stack(1) as $node) {
3956
+ if ( $this->is( $node, '.'.$className ))
3957
+ $this->removeClass($className);
3958
+ else
3959
+ $this->addClass($className);
3960
+ }
3961
+ return $this;
3962
+ }
3963
+ /**
3964
+ * Proper name without underscore (just ->empty()) also works.
3965
+ *
3966
+ * Removes all child nodes from the set of matched elements.
3967
+ *
3968
+ * Example:
3969
+ * pq("p")._empty()
3970
+ *
3971
+ * HTML:
3972
+ * <p>Hello, <span>Person</span> <a href="#">and person</a></p>
3973
+ *
3974
+ * Result:
3975
+ * [ <p></p> ]
3976
+ *
3977
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3978
+ * @access private
3979
+ */
3980
+ public function _empty() {
3981
+ foreach($this->stack(1) as $node) {
3982
+ // thx to 'dave at dgx dot cz'
3983
+ $node->nodeValue = '';
3984
+ }
3985
+ return $this;
3986
+ }
3987
+ /**
3988
+ * Enter description here...
3989
+ *
3990
+ * @param array|string $callback Expects $node as first param, $index as second
3991
+ * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope)
3992
+ * @param array $arg1 Will ba passed as third and futher args to callback.
3993
+ * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on...
3994
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3995
+ */
3996
+ public function each($callback, $param1 = null, $param2 = null, $param3 = null) {
3997
+ $paramStructure = null;
3998
+ if (func_num_args() > 1) {
3999
+ $paramStructure = func_get_args();
4000
+ $paramStructure = array_slice($paramStructure, 1);
4001
+ }
4002
+ foreach($this->elements as $v)
4003
+ phpQuery::callbackRun($callback, array($v), $paramStructure);
4004
+ return $this;
4005
+ }
4006
+ /**
4007
+ * Run callback on actual object.
4008
+ *
4009
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4010
+ */
4011
+ public function callback($callback, $param1 = null, $param2 = null, $param3 = null) {
4012
+ $params = func_get_args();
4013
+ $params[0] = $this;
4014
+ phpQuery::callbackRun($callback, $params);
4015
+ return $this;
4016
+ }
4017
+ /**
4018
+ * Enter description here...
4019
+ *
4020
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4021
+ * @todo add $scope and $args as in each() ???
4022
+ */
4023
+ public function map($callback, $param1 = null, $param2 = null, $param3 = null) {
4024
+ // $stack = array();
4025
+ //// foreach($this->newInstance() as $node) {
4026
+ // foreach($this->newInstance() as $node) {
4027
+ // $result = call_user_func($callback, $node);
4028
+ // if ($result)
4029
+ // $stack[] = $result;
4030
+ // }
4031
+ $params = func_get_args();
4032
+ array_unshift($params, $this->elements);
4033
+ return $this->newInstance(
4034
+ call_user_func_array(array('phpQuery', 'map'), $params)
4035
+ // phpQuery::map($this->elements, $callback)
4036
+ );
4037
+ }
4038
+ /**
4039
+ * Enter description here...
4040
+ *
4041
+ * @param <type> $key
4042
+ * @param <type> $value
4043
+ */
4044
+ public function data($key, $value = null) {
4045
+ if (! isset($value)) {
4046
+ // TODO? implement specific jQuery behavior od returning parent values
4047
+ // is child which we look up doesn't exist
4048
+ return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID());
4049
+ } else {
4050
+ foreach($this as $node)
4051
+ phpQuery::data($node, $key, $value, $this->getDocumentID());
4052
+ return $this;
4053
+ }
4054
+ }
4055
+ /**
4056
+ * Enter description here...
4057
+ *
4058
+ * @param <type> $key
4059
+ */
4060
+ public function removeData($key) {
4061
+ foreach($this as $node)
4062
+ phpQuery::removeData($node, $key, $this->getDocumentID());
4063
+ return $this;
4064
+ }
4065
+ // INTERFACE IMPLEMENTATIONS
4066
+
4067
+ // ITERATOR INTERFACE
4068
+ /**
4069
+ * @access private
4070
+ */
4071
+ public function rewind(){
4072
+ $this->debug('iterating foreach');
4073
+ // phpQuery::selectDocument($this->getDocumentID());
4074
+ $this->elementsBackup = $this->elements;
4075
+ $this->elementsInterator = $this->elements;
4076
+ $this->valid = isset( $this->elements[0] )
4077
+ ? 1 : 0;
4078
+ // $this->elements = $this->valid
4079
+ // ? array($this->elements[0])
4080
+ // : array();
4081
+ $this->current = 0;
4082
+ }
4083
+ /**
4084
+ * @access private
4085
+ */
4086
+ public function current(){
4087
+ return $this->elementsInterator[ $this->current ];
4088
+ }
4089
+ /**
4090
+ * @access private
4091
+ */
4092
+ public function key(){
4093
+ return $this->current;
4094
+ }
4095
+ /**
4096
+ * Double-function method.
4097
+ *
4098
+ * First: main iterator interface method.
4099
+ * Second: Returning next sibling, alias for _next().
4100
+ *
4101
+ * Proper functionality is choosed automagicaly.
4102
+ *
4103
+ * @see phpQueryObject::_next()
4104
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4105
+ */
4106
+ public function next($cssSelector = null){
4107
+ // if ($cssSelector || $this->valid)
4108
+ // return $this->_next($cssSelector);
4109
+ $this->valid = isset( $this->elementsInterator[ $this->current+1 ] )
4110
+ ? true
4111
+ : false;
4112
+ if (! $this->valid && $this->elementsInterator) {
4113
+ $this->elementsInterator = null;
4114
+ } else if ($this->valid) {
4115
+ $this->current++;
4116
+ } else {
4117
+ return $this->_next($cssSelector);
4118
+ }
4119
+ }
4120
+ /**
4121
+ * @access private
4122
+ */
4123
+ public function valid(){
4124
+ return $this->valid;
4125
+ }
4126
+ // ITERATOR INTERFACE END
4127
+ // ARRAYACCESS INTERFACE
4128
+ /**
4129
+ * @access private
4130
+ */
4131
+ public function offsetExists($offset) {
4132
+ return $this->find($offset)->size() > 0;
4133
+ }
4134
+ /**
4135
+ * @access private
4136
+ */
4137
+ public function offsetGet($offset) {
4138
+ return $this->find($offset);
4139
+ }
4140
+ /**
4141
+ * @access private
4142
+ */
4143
+ public function offsetSet($offset, $value) {
4144
+ // $this->find($offset)->replaceWith($value);
4145
+ $this->find($offset)->html($value);
4146
+ }
4147
+ /**
4148
+ * @access private
4149
+ */
4150
+ public function offsetUnset($offset) {
4151
+ // empty
4152
+ throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML.");
4153
+ }
4154
+ // ARRAYACCESS INTERFACE END
4155
+ /**
4156
+ * Returns node's XPath.
4157
+ *
4158
+ * @param unknown_type $oneNode
4159
+ * @return string
4160
+ * @TODO use native getNodePath is avaible
4161
+ * @access private
4162
+ */
4163
+ protected function getNodeXpath($oneNode = null, $namespace = null) {
4164
+ $return = array();
4165
+ $loop = $oneNode
4166
+ ? array($oneNode)
4167
+ : $this->elements;
4168
+ // if ($namespace)
4169
+ // $namespace .= ':';
4170
+ foreach($loop as $node) {
4171
+ if ($node instanceof DOMDOCUMENT) {
4172
+ $return[] = '';
4173
+ continue;
4174
+ }
4175
+ $xpath = array();
4176
+ while(! ($node instanceof DOMDOCUMENT)) {
4177
+ $i = 1;
4178
+ $sibling = $node;
4179
+ while($sibling->previousSibling) {
4180
+ $sibling = $sibling->previousSibling;
4181
+ $isElement = $sibling instanceof DOMELEMENT;
4182
+ if ($isElement && $sibling->tagName == $node->tagName)
4183
+ $i++;
4184
+ }
4185
+ $xpath[] = $this->isXML()
4186
+ ? "*[local-name()='{$node->tagName}'][{$i}]"
4187
+ : "{$node->tagName}[{$i}]";
4188
+ $node = $node->parentNode;
4189
+ }
4190
+ $xpath = join('/', array_reverse($xpath));
4191
+ $return[] = '/'.$xpath;
4192
+ }
4193
+ return $oneNode
4194
+ ? $return[0]
4195
+ : $return;
4196
+ }
4197
+ // HELPERS
4198
+ public function whois($oneNode = null) {
4199
+ $return = array();
4200
+ $loop = $oneNode
4201
+ ? array( $oneNode )
4202
+ : $this->elements;
4203
+ foreach($loop as $node) {
4204
+ if (isset($node->tagName)) {
4205
+ $tag = in_array($node->tagName, array('php', 'js'))
4206
+ ? strtoupper($node->tagName)
4207
+ : $node->tagName;
4208
+ $return[] = $tag
4209
+ .($node->getAttribute('id')
4210
+ ? '#'.$node->getAttribute('id'):'')
4211
+ .($node->getAttribute('class')
4212
+ // ? '.'.join('.', split(' ', $node->getAttribute('class'))):'')
4213
+ ? '.'.join('.', preg_split('/ /', $node->getAttribute('class'))):'')
4214
+ .($node->getAttribute('name')
4215
+ ? '[name="'.$node->getAttribute('name').'"]':'')
4216
+ .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false
4217
+ ? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'')
4218
+ .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false
4219
+ ? '[value=PHP]':'')
4220
+ .($node->getAttribute('selected')
4221
+ ? '[selected]':'')
4222
+ .($node->getAttribute('checked')
4223
+ ? '[checked]':'')
4224
+ ;
4225
+ } else if ($node instanceof DOMTEXT) {
4226
+ if (trim($node->textContent))
4227
+ $return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15);
4228
+ } else {
4229
+
4230
+ }
4231
+ }
4232
+ return $oneNode && isset($return[0])
4233
+ ? $return[0]
4234
+ : $return;
4235
+ }
4236
+ /**
4237
+ * Dump htmlOuter and preserve chain. Usefull for debugging.
4238
+ *
4239
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4240
+ *
4241
+ */
4242
+ public function dump() {
4243
+ print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4244
+ $debug = phpQuery::$debug;
4245
+ phpQuery::$debug = false;
4246
+ // print __FILE__.':'.__LINE__."\n";
4247
+ var_dump($this->htmlOuter());
4248
+ return $this;
4249
+ }
4250
+ public function dumpWhois() {
4251
+ print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4252
+ $debug = phpQuery::$debug;
4253
+ phpQuery::$debug = false;
4254
+ // print __FILE__.':'.__LINE__."\n";
4255
+ var_dump('whois', $this->whois());
4256
+ phpQuery::$debug = $debug;
4257
+ return $this;
4258
+ }
4259
+ public function dumpLength() {
4260
+ print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4261
+ $debug = phpQuery::$debug;
4262
+ phpQuery::$debug = false;
4263
+ // print __FILE__.':'.__LINE__."\n";
4264
+ var_dump('length', $this->length());
4265
+ phpQuery::$debug = $debug;
4266
+ return $this;
4267
+ }
4268
+ public function dumpTree($html = true, $title = true) {
4269
+ $output = $title
4270
+ ? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : '';
4271
+ $debug = phpQuery::$debug;
4272
+ phpQuery::$debug = false;
4273
+ foreach($this->stack() as $node)
4274
+ // $output .= $this->__dumpTree($node);
4275
+ $output .= $this->ai_dumpTree($node);
4276
+ phpQuery::$debug = $debug;
4277
+ print $html
4278
+ ? nl2br(str_replace(' ', '&nbsp;', $output))
4279
+ : $output;
4280
+ return $this;
4281
+ }
4282
+ // private function __dumpTree($node, $intend = 0) {
4283
+ private function ai_dumpTree($node, $intend = 0) {
4284
+ $whois = $this->whois($node);
4285
+ $return = '';
4286
+ if ($whois)
4287
+ $return .= str_repeat(' - ', $intend).$whois."\n";
4288
+ if (isset($node->childNodes))
4289
+ foreach($node->childNodes as $chNode)
4290
+ // $return .= $this->__dumpTree($chNode, $intend+1);
4291
+ $return .= $this->ai_dumpTree($chNode, $intend+1);
4292
+ return $return;
4293
+ }
4294
+ /**
4295
+ * Dump htmlOuter and stop script execution. Usefull for debugging.
4296
+ *
4297
+ */
4298
+ public function dumpDie() {
4299
+ print __FILE__.':'.__LINE__;
4300
+ var_dump($this->htmlOuter());
4301
+ die();
4302
+ }
4303
+ }
4304
+
4305
+
4306
+ // -- Multibyte Compatibility functions ---------------------------------------
4307
+ // http://svn.iphonewebdev.com/lace/lib/mb_compat.php
4308
+
4309
+ /**
4310
+ * mb_internal_encoding()
4311
+ *
4312
+ * Included for mbstring pseudo-compatability.
4313
+ */
4314
+ if (!function_exists('mb_internal_encoding'))
4315
+ {
4316
+ function mb_internal_encoding($enc) {return true; }
4317
+ }
4318
+
4319
+ /**
4320
+ * mb_regex_encoding()
4321
+ *
4322
+ * Included for mbstring pseudo-compatability.
4323
+ */
4324
+ if (!function_exists('mb_regex_encoding'))
4325
+ {
4326
+ function mb_regex_encoding($enc) {return true; }
4327
+ }
4328
+
4329
+ /**
4330
+ * mb_strlen()
4331
+ *
4332
+ * Included for mbstring pseudo-compatability.
4333
+ */
4334
+ if (!function_exists('mb_strlen'))
4335
+ {
4336
+ function mb_strlen($str)
4337
+ {
4338
+ return strlen($str);
4339
+ }
4340
+ }
4341
+
4342
+ /**
4343
+ * mb_strpos()
4344
+ *
4345
+ * Included for mbstring pseudo-compatability.
4346
+ */
4347
+ if (!function_exists('mb_strpos'))
4348
+ {
4349
+ function mb_strpos($haystack, $needle, $offset=0)
4350
+ {
4351
+ return strpos($haystack, $needle, $offset);
4352
+ }
4353
+ }
4354
+ /**
4355
+ * mb_stripos()
4356
+ *
4357
+ * Included for mbstring pseudo-compatability.
4358
+ */
4359
+ if (!function_exists('mb_stripos'))
4360
+ {
4361
+ function mb_stripos($haystack, $needle, $offset=0)
4362
+ {
4363
+ return stripos($haystack, $needle, $offset);
4364
+ }
4365
+ }
4366
+
4367
+ /**
4368
+ * mb_substr()
4369
+ *
4370
+ * Included for mbstring pseudo-compatability.
4371
+ */
4372
+ if (!function_exists('mb_substr'))
4373
+ {
4374
+ function mb_substr($str, $start, $length=0)
4375
+ {
4376
+ return substr($str, $start, $length);
4377
+ }
4378
+ }
4379
+
4380
+ /**
4381
+ * mb_substr_count()
4382
+ *
4383
+ * Included for mbstring pseudo-compatability.
4384
+ */
4385
+ if (!function_exists('mb_substr_count'))
4386
+ {
4387
+ function mb_substr_count($haystack, $needle)
4388
+ {
4389
+ return substr_count($haystack, $needle);
4390
+ }
4391
+ }
4392
+
4393
+
4394
+ /**
4395
+ * Static namespace for phpQuery functions.
4396
+ *
4397
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
4398
+ * @package phpQuery
4399
+ */
4400
+ abstract class phpQuery {
4401
+ /**
4402
+ * XXX: Workaround for mbstring problems
4403
+ *
4404
+ * @var bool
4405
+ */
4406
+ public static $mbstringSupport = true;
4407
+ public static $debug = false;
4408
+ public static $documents = array();
4409
+ public static $defaultDocumentID = null;
4410
+ // public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"';
4411
+ /**
4412
+ * Applies only to HTML.
4413
+ *
4414
+ * @var unknown_type
4415
+ */
4416
+ public static $defaultDoctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
4417
+ "http://www.w3.org/TR/html4/loose.dtd">';
4418
+ public static $defaultCharset = 'UTF-8';
4419
+ /**
4420
+ * Static namespace for plugins.
4421
+ *
4422
+ * @var object
4423
+ */
4424
+ public static $plugins = array();
4425
+ /**
4426
+ * List of loaded plugins.
4427
+ *
4428
+ * @var unknown_type
4429
+ */
4430
+ public static $pluginsLoaded = array();
4431
+ public static $pluginsMethods = array();
4432
+ public static $pluginsStaticMethods = array();
4433
+ public static $extendMethods = array();
4434
+ /**
4435
+ * @TODO implement
4436
+ */
4437
+ public static $extendStaticMethods = array();
4438
+ /**
4439
+ * Hosts allowed for AJAX connections.
4440
+ * Dot '.' means $_SERVER['HTTP_HOST'] (if any).
4441
+ *
4442
+ * @var array
4443
+ */
4444
+ public static $ajaxAllowedHosts = array(
4445
+ '.'
4446
+ );
4447
+ /**
4448
+ * AJAX settings.
4449
+ *
4450
+ * @var array
4451
+ * XXX should it be static or not ?
4452
+ */
4453
+ public static $ajaxSettings = array(
4454
+ 'url' => '',//TODO
4455
+ 'global' => true,
4456
+ 'type' => "GET",
4457
+ 'timeout' => null,
4458
+ 'contentType' => "application/x-www-form-urlencoded",
4459
+ 'processData' => true,
4460
+ // 'async' => true,
4461
+ 'data' => null,
4462
+ 'username' => null,
4463
+ 'password' => null,
4464
+ 'accepts' => array(
4465
+ 'xml' => "application/xml, text/xml",
4466
+ 'html' => "text/html",
4467
+ 'script' => "text/javascript, application/javascript",
4468
+ 'json' => "application/json, text/javascript",
4469
+ 'text' => "text/plain",
4470
+ '_default' => "*/*"
4471
+ )
4472
+ );
4473
+ public static $lastModified = null;
4474
+ public static $active = 0;
4475
+ public static $dumpCount = 0;
4476
+ /**
4477
+ * Multi-purpose function.
4478
+ * Use pq() as shortcut.
4479
+ *
4480
+ * In below examples, $pq is any result of pq(); function.
4481
+ *
4482
+ * 1. Import markup into existing document (without any attaching):
4483
+ * - Import into selected document:
4484
+ * pq('<div/>') // DOESNT accept text nodes at beginning of input string !
4485
+ * - Import into document with ID from $pq->getDocumentID():
4486
+ * pq('<div/>', $pq->getDocumentID())
4487
+ * - Import into same document as DOMNode belongs to:
4488
+ * pq('<div/>', DOMNode)
4489
+ * - Import into document from phpQuery object:
4490
+ * pq('<div/>', $pq)
4491
+ *
4492
+ * 2. Run query:
4493
+ * - Run query on last selected document:
4494
+ * pq('div.myClass')
4495
+ * - Run query on document with ID from $pq->getDocumentID():
4496
+ * pq('div.myClass', $pq->getDocumentID())
4497
+ * - Run query on same document as DOMNode belongs to and use node(s)as root for query:
4498
+ * pq('div.myClass', DOMNode)
4499
+ * - Run query on document from phpQuery object
4500
+ * and use object's stack as root node(s) for query:
4501
+ * pq('div.myClass', $pq)
4502
+ *
4503
+ * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes
4504
+ * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root)
4505
+ *
4506
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false
4507
+ * phpQuery object or false in case of error.
4508
+ */
4509
+ public static function pq($arg1, $context = null) {
4510
+ if ($arg1 instanceof DOMNODE && ! isset($context)) {
4511
+ foreach(phpQuery::$documents as $documentWrapper) {
4512
+ $compare = $arg1 instanceof DOMDocument
4513
+ ? $arg1 : $arg1->ownerDocument;
4514
+ if ($documentWrapper->document->isSameNode($compare))
4515
+ $context = $documentWrapper->id;
4516
+ }
4517
+ }
4518
+ if (! $context) {
4519
+ $domId = self::$defaultDocumentID;
4520
+ if (! $domId)
4521
+ throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first.");
4522
+ // } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
4523
+ } else if (is_object($context) && $context instanceof phpQueryObject)
4524
+ $domId = $context->getDocumentID();
4525
+ else if ($context instanceof DOMDOCUMENT) {
4526
+ $domId = self::getDocumentID($context);
4527
+ if (! $domId) {
4528
+ //throw new Exception('Orphaned DOMDocument');
4529
+ $domId = self::newDocument($context)->getDocumentID();
4530
+ }
4531
+ } else if ($context instanceof DOMNODE) {
4532
+ $domId = self::getDocumentID($context);
4533
+ if (! $domId) {
4534
+ throw new Exception('Orphaned DOMNode');
4535
+ // $domId = self::newDocument($context->ownerDocument);
4536
+ }
4537
+ } else
4538
+ $domId = $context;
4539
+ if ($arg1 instanceof phpQueryObject) {
4540
+ // if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) {
4541
+ /**
4542
+ * Return $arg1 or import $arg1 stack if document differs:
4543
+ * pq(pq('<div/>'))
4544
+ */
4545
+ if ($arg1->getDocumentID() == $domId)
4546
+ return $arg1;
4547
+ $class = get_class($arg1);
4548
+ // support inheritance by passing old object to overloaded constructor
4549
+ $phpQuery = $class != 'phpQuery'
4550
+ ? new $class($arg1, $domId)
4551
+ : new phpQueryObject($domId);
4552
+ $phpQuery->elements = array();
4553
+ foreach($arg1->elements as $node)
4554
+ $phpQuery->elements[] = $phpQuery->document->importNode($node, true);
4555
+ return $phpQuery;
4556
+ } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) {
4557
+ /*
4558
+ * Wrap DOM nodes with phpQuery object, import into document when needed:
4559
+ * pq(array($domNode1, $domNode2))
4560
+ */
4561
+ $phpQuery = new phpQueryObject($domId);
4562
+ if (!($arg1 instanceof DOMNODELIST) && ! is_array($arg1))
4563
+ $arg1 = array($arg1);
4564
+ $phpQuery->elements = array();
4565
+ foreach($arg1 as $node) {
4566
+ $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT
4567
+ && ! $node->ownerDocument->isSameNode($phpQuery->document);
4568
+ $phpQuery->elements[] = $sameDocument
4569
+ ? $phpQuery->document->importNode($node, true)
4570
+ : $node;
4571
+ }
4572
+ return $phpQuery;
4573
+ } else if (self::isMarkup($arg1)) {
4574
+ /**
4575
+ * Import HTML:
4576
+ * pq('<div/>')
4577
+ */
4578
+ $phpQuery = new phpQueryObject($domId);
4579
+ return $phpQuery->newInstance(
4580
+ $phpQuery->documentWrapper->import($arg1)
4581
+ );
4582
+ } else {
4583
+ /**
4584
+ * Run CSS query:
4585
+ * pq('div.myClass')
4586
+ */
4587
+ $phpQuery = new phpQueryObject($domId);
4588
+ // if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
4589
+ if ($context && $context instanceof phpQueryObject)
4590
+ $phpQuery->elements = $context->elements;
4591
+ else if ($context && $context instanceof DOMNODELIST) {
4592
+ $phpQuery->elements = array();
4593
+ foreach($context as $node)
4594
+ $phpQuery->elements[] = $node;
4595
+ } else if ($context && $context instanceof DOMNODE)
4596
+ $phpQuery->elements = array($context);
4597
+ return $phpQuery->find($arg1);
4598
+ }
4599
+ }
4600
+ /**
4601
+ * Sets default document to $id. Document has to be loaded prior
4602
+ * to using this method.
4603
+ * $id can be retrived via getDocumentID() or getDocumentIDRef().
4604
+ *
4605
+ * @param unknown_type $id
4606
+ */
4607
+ public static function selectDocument($id) {
4608
+ $id = self::getDocumentID($id);
4609
+ self::debug("Selecting document '$id' as default one");
4610
+ self::$defaultDocumentID = self::getDocumentID($id);
4611
+ }
4612
+ /**
4613
+ * Returns document with id $id or last used as phpQueryObject.
4614
+ * $id can be retrived via getDocumentID() or getDocumentIDRef().
4615
+ * Chainable.
4616
+ *
4617
+ * @see phpQuery::selectDocument()
4618
+ * @param unknown_type $id
4619
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4620
+ */
4621
+ public static function getDocument($id = null) {
4622
+ if ($id)
4623
+ phpQuery::selectDocument($id);
4624
+ else
4625
+ $id = phpQuery::$defaultDocumentID;
4626
+ return new phpQueryObject($id);
4627
+ }
4628
+ /**
4629
+ * Creates new document from markup.
4630
+ * Chainable.
4631
+ *
4632
+ * @param unknown_type $markup
4633
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4634
+ */
4635
+ public static function newDocument($markup = null, $contentType = null) {
4636
+ if (! $markup)
4637
+ $markup = '';
4638
+ $documentID = phpQuery::createDocumentWrapper($markup, $contentType);
4639
+ return new phpQueryObject($documentID);
4640
+ }
4641
+ /**
4642
+ * Creates new document from markup.
4643
+ * Chainable.
4644
+ *
4645
+ * @param unknown_type $markup
4646
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4647
+ */
4648
+ public static function newDocumentHTML($markup = null, $charset = null) {
4649
+ $contentType = $charset
4650
+ ? ";charset=$charset"
4651
+ : '';
4652
+ return self::newDocument($markup, "text/html{$contentType}");
4653
+ }
4654
+ /**
4655
+ * Creates new document from markup.
4656
+ * Chainable.
4657
+ *
4658
+ * @param unknown_type $markup
4659
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4660
+ */
4661
+ public static function newDocumentXML($markup = null, $charset = null) {
4662
+ $contentType = $charset
4663
+ ? ";charset=$charset"
4664
+ : '';
4665
+ return self::newDocument($markup, "text/xml{$contentType}");
4666
+ }
4667
+ /**
4668
+ * Creates new document from markup.
4669
+ * Chainable.
4670
+ *
4671
+ * @param unknown_type $markup
4672
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4673
+ */
4674
+ public static function newDocumentXHTML($markup = null, $charset = null) {
4675
+ $contentType = $charset
4676
+ ? ";charset=$charset"
4677
+ : '';
4678
+ return self::newDocument($markup, "application/xhtml+xml{$contentType}");
4679
+ }
4680
+ /**
4681
+ * Creates new document from markup.
4682
+ * Chainable.
4683
+ *
4684
+ * @param unknown_type $markup
4685
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4686
+ */
4687
+ public static function newDocumentPHP($markup = null, $contentType = "text/html") {
4688
+ // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function)
4689
+ $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset);
4690
+ return self::newDocument($markup, $contentType);
4691
+ }
4692
+ public static function phpToMarkup($php, $charset = 'utf-8') {
4693
+ $regexes = array(
4694
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?>)([^\']*)\'@s',
4695
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?>)([^"]*)"@s',
4696
+ );
4697
+ foreach($regexes as $regex)
4698
+ while (preg_match($regex, $php, $matches)) {
4699
+ $php = preg_replace_callback(
4700
+ $regex,
4701
+ // create_function('$m, $charset = "'.$charset.'"',
4702
+ // 'return $m[1].$m[2]
4703
+ // .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
4704
+ // .$m[5].$m[2];'
4705
+ // ),
4706
+ array('phpQuery', '_phpToMarkupCallback'),
4707
+ $php
4708
+ );
4709
+ }
4710
+ $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s';
4711
+ //preg_match_all($regex, $php, $matches);
4712
+ //var_dump($matches);
4713
+ $php = preg_replace($regex, '\\1<php><!-- \\3 --></php>', $php);
4714
+ return $php;
4715
+ }
4716
+ public static function _phpToMarkupCallback($php, $charset = 'utf-8') {
4717
+ return $m[1].$m[2]
4718
+ .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
4719
+ .$m[5].$m[2];
4720
+ }
4721
+ public static function _markupToPHPCallback($m) {
4722
+ return "<"."?php ".htmlspecialchars_decode($m[1])." ?".">";
4723
+ }
4724
+ /**
4725
+ * Converts document markup containing PHP code generated by phpQuery::php()
4726
+ * into valid (executable) PHP code syntax.
4727
+ *
4728
+ * @param string|phpQueryObject $content
4729
+ * @return string PHP code.
4730
+ */
4731
+ public static function markupToPHP($content) {
4732
+ if ($content instanceof phpQueryObject)
4733
+ $content = $content->markupOuter();
4734
+ /* <php>...</php> to <?php...? > */
4735
+ $content = preg_replace_callback(
4736
+ '@<php>\s*<!--(.*?)-->\s*</php>@s',
4737
+ // create_function('$m',
4738
+ // 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";'
4739
+ // ),
4740
+ array('phpQuery', '_markupToPHPCallback'),
4741
+ $content
4742
+ );
4743
+ /* <node attr='< ?php ? >'> extra space added to save highlighters */
4744
+ $regexes = array(
4745
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^\']*)\'@s',
4746
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^"]*)"@s',
4747
+ );
4748
+ foreach($regexes as $regex)
4749
+ while (preg_match($regex, $content))
4750
+ $content = preg_replace_callback(
4751
+ $regex,
4752
+ create_function('$m',
4753
+ 'return $m[1].$m[2].$m[3]."<?php "
4754
+ .str_replace(
4755
+ array("%20", "%3E", "%09", "&#10;", "&#9;", "%7B", "%24", "%7D", "%22", "%5B", "%5D"),
4756
+ array(" ", ">", " ", "\n", " ", "{", "$", "}", \'"\', "[", "]"),
4757
+ htmlspecialchars_decode($m[4])
4758
+ )
4759
+ ." ?".">".$m[5].$m[2];'
4760
+ ),
4761
+ $content
4762
+ );
4763
+ return $content;
4764
+ }
4765
+ /**
4766
+ * Creates new document from file $file.
4767
+ * Chainable.
4768
+ *
4769
+ * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources.
4770
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4771
+ */
4772
+ public static function newDocumentFile($file, $contentType = null) {
4773
+ $documentID = self::createDocumentWrapper(
4774
+ file_get_contents($file), $contentType
4775
+ );
4776
+ return new phpQueryObject($documentID);
4777
+ }
4778
+ /**
4779
+ * Creates new document from markup.
4780
+ * Chainable.
4781
+ *
4782
+ * @param unknown_type $markup
4783
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4784
+ */
4785
+ public static function newDocumentFileHTML($file, $charset = null) {
4786
+ $contentType = $charset
4787
+ ? ";charset=$charset"
4788
+ : '';
4789
+ return self::newDocumentFile($file, "text/html{$contentType}");
4790
+ }
4791
+ /**
4792
+ * Creates new document from markup.
4793
+ * Chainable.
4794
+ *
4795
+ * @param unknown_type $markup
4796
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4797
+ */
4798
+ public static function newDocumentFileXML($file, $charset = null) {
4799
+ $contentType = $charset
4800
+ ? ";charset=$charset"
4801
+ : '';
4802
+ return self::newDocumentFile($file, "text/xml{$contentType}");
4803
+ }
4804
+ /**
4805
+ * Creates new document from markup.
4806
+ * Chainable.
4807
+ *
4808
+ * @param unknown_type $markup
4809
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4810
+ */
4811
+ public static function newDocumentFileXHTML($file, $charset = null) {
4812
+ $contentType = $charset
4813
+ ? ";charset=$charset"
4814
+ : '';
4815
+ return self::newDocumentFile($file, "application/xhtml+xml{$contentType}");
4816
+ }
4817
+ /**
4818
+ * Creates new document from markup.
4819
+ * Chainable.
4820
+ *
4821
+ * @param unknown_type $markup
4822
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4823
+ */
4824
+ public static function newDocumentFilePHP($file, $contentType = null) {
4825
+ return self::newDocumentPHP(file_get_contents($file), $contentType);
4826
+ }
4827
+ /**
4828
+ * Reuses existing DOMDocument object.
4829
+ * Chainable.
4830
+ *
4831
+ * @param $document DOMDocument
4832
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4833
+ * @TODO support DOMDocument
4834
+ */
4835
+ public static function loadDocument($document) {
4836
+ // TODO
4837
+ die('TODO loadDocument');
4838
+ }
4839
+ /**
4840
+ * Enter description here...
4841
+ *
4842
+ * @param unknown_type $html
4843
+ * @param unknown_type $domId
4844
+ * @return unknown New DOM ID
4845
+ * @todo support PHP tags in input
4846
+ * @todo support passing DOMDocument object from self::loadDocument
4847
+ */
4848
+ protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) {
4849
+ if (function_exists('domxml_open_mem'))
4850
+ throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled.");
4851
+ // $id = $documentID
4852
+ // ? $documentID
4853
+ // : md5(microtime());
4854
+ $document = null;
4855
+ if ($html instanceof DOMDOCUMENT) {
4856
+ if (self::getDocumentID($html)) {
4857
+ // document already exists in phpQuery::$documents, make a copy
4858
+ $document = clone $html;
4859
+ } else {
4860
+ // new document, add it to phpQuery::$documents
4861
+ $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
4862
+ }
4863
+ } else {
4864
+ $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
4865
+ }
4866
+ // $wrapper->id = $id;
4867
+ // bind document
4868
+ phpQuery::$documents[$wrapper->id] = $wrapper;
4869
+ // remember last loaded document
4870
+ phpQuery::selectDocument($wrapper->id);
4871
+ return $wrapper->id;
4872
+ }
4873
+ /**
4874
+ * Extend class namespace.
4875
+ *
4876
+ * @param string|array $target
4877
+ * @param array $source
4878
+ * @TODO support string $source
4879
+ * @return unknown_type
4880
+ */
4881
+ public static function extend($target, $source) {
4882
+ switch($target) {
4883
+ case 'phpQueryObject':
4884
+ $targetRef = &self::$extendMethods;
4885
+ $targetRef2 = &self::$pluginsMethods;
4886
+ break;
4887
+ case 'phpQuery':
4888
+ $targetRef = &self::$extendStaticMethods;
4889
+ $targetRef2 = &self::$pluginsStaticMethods;
4890
+ break;
4891
+ default:
4892
+ throw new Exception("Unsupported \$target type");
4893
+ }
4894
+ if (is_string($source))
4895
+ $source = array($source => $source);
4896
+ foreach($source as $method => $callback) {
4897
+ if (isset($targetRef[$method])) {
4898
+ // throw new Exception
4899
+ self::debug("Duplicate method '{$method}', can\'t extend '{$target}'");
4900
+ continue;
4901
+ }
4902
+ if (isset($targetRef2[$method])) {
4903
+ // throw new Exception
4904
+ self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}',"
4905
+ ." can\'t extend '{$target}'");
4906
+ continue;
4907
+ }
4908
+ $targetRef[$method] = $callback;
4909
+ }
4910
+ return true;
4911
+ }
4912
+ /**
4913
+ * Extend phpQuery with $class from $file.
4914
+ *
4915
+ * @param string $class Extending class name. Real class name can be prepended phpQuery_.
4916
+ * @param string $file Filename to include. Defaults to "{$class}.php".
4917
+ */
4918
+ public static function plugin($class, $file = null) {
4919
+ // TODO $class checked agains phpQuery_$class
4920
+ // if (strpos($class, 'phpQuery') === 0)
4921
+ // $class = substr($class, 8);
4922
+ if (in_array($class, self::$pluginsLoaded))
4923
+ return true;
4924
+ if (! $file)
4925
+ $file = $class.'.php';
4926
+ $objectClassExists = class_exists('phpQueryObjectPlugin_'.$class);
4927
+ $staticClassExists = class_exists('phpQueryPlugin_'.$class);
4928
+ if (! $objectClassExists && ! $staticClassExists)
4929
+ require_once($file);
4930
+ self::$pluginsLoaded[] = $class;
4931
+ // static methods
4932
+ if (class_exists('phpQueryPlugin_'.$class)) {
4933
+ $realClass = 'phpQueryPlugin_'.$class;
4934
+ $vars = get_class_vars($realClass);
4935
+ $loop = isset($vars['phpQueryMethods'])
4936
+ && ! is_null($vars['phpQueryMethods'])
4937
+ ? $vars['phpQueryMethods']
4938
+ : get_class_methods($realClass);
4939
+ foreach($loop as $method) {
4940
+ if ($method == '__initialize')
4941
+ continue;
4942
+ if (! is_callable(array($realClass, $method)))
4943
+ continue;
4944
+ if (isset(self::$pluginsStaticMethods[$method])) {
4945
+ throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsStaticMethods[$method]."'");
4946
+ return;
4947
+ }
4948
+ self::$pluginsStaticMethods[$method] = $class;
4949
+ }
4950
+ if (method_exists($realClass, '__initialize'))
4951
+ call_user_func_array(array($realClass, '__initialize'), array());
4952
+ }
4953
+ // object methods
4954
+ if (class_exists('phpQueryObjectPlugin_'.$class)) {
4955
+ $realClass = 'phpQueryObjectPlugin_'.$class;
4956
+ $vars = get_class_vars($realClass);
4957
+ $loop = isset($vars['phpQueryMethods'])
4958
+ && ! is_null($vars['phpQueryMethods'])
4959
+ ? $vars['phpQueryMethods']
4960
+ : get_class_methods($realClass);
4961
+ foreach($loop as $method) {
4962
+ if (! is_callable(array($realClass, $method)))
4963
+ continue;
4964
+ if (isset(self::$pluginsMethods[$method])) {
4965
+ throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsMethods[$method]."'");
4966
+ continue;
4967
+ }
4968
+ self::$pluginsMethods[$method] = $class;
4969
+ }
4970
+ }
4971
+ return true;
4972
+ }
4973
+ /**
4974
+ * Unloades all or specified document from memory.
4975
+ *
4976
+ * @param mixed $documentID @see phpQuery::getDocumentID() for supported types.
4977
+ */
4978
+ public static function unloadDocuments($id = null) {
4979
+ if (isset($id)) {
4980
+ if ($id = self::getDocumentID($id))
4981
+ unset(phpQuery::$documents[$id]);
4982
+ } else {
4983
+ foreach(phpQuery::$documents as $k => $v) {
4984
+ unset(phpQuery::$documents[$k]);
4985
+ }
4986
+ }
4987
+ }
4988
+ /**
4989
+ * Parses phpQuery object or HTML result against PHP tags and makes them active.
4990
+ *
4991
+ * @param phpQuery|string $content
4992
+ * @deprecated
4993
+ * @return string
4994
+ */
4995
+ public static function unsafePHPTags($content) {
4996
+ return self::markupToPHP($content);
4997
+ }
4998
+ public static function DOMNodeListToArray($DOMNodeList) {
4999
+ $array = array();
5000
+ if (! $DOMNodeList)
5001
+ return $array;
5002
+ foreach($DOMNodeList as $node)
5003
+ $array[] = $node;
5004
+ return $array;
5005
+ }
5006
+ /**
5007
+ * Checks if $input is HTML string, which has to start with '<'.
5008
+ *
5009
+ * @deprecated
5010
+ * @param String $input
5011
+ * @return Bool
5012
+ * @todo still used ?
5013
+ */
5014
+ public static function isMarkup($input) {
5015
+ return ! is_array($input) && substr(trim($input), 0, 1) == '<';
5016
+ }
5017
+ public static function debug($text) {
5018
+ if (self::$debug)
5019
+ print var_dump($text);
5020
+ }
5021
+ /**
5022
+ * Make an AJAX request.
5023
+ *
5024
+ * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions
5025
+ * Additional options are:
5026
+ * 'document' - document for global events, @see phpQuery::getDocumentID()
5027
+ * 'referer' - implemented
5028
+ * 'requested_with' - TODO; not implemented (X-Requested-With)
5029
+ * @return Zend_Http_Client
5030
+ * @link http://docs.jquery.com/Ajax/jQuery.ajax
5031
+ *
5032
+ * @TODO $options['cache']
5033
+ * @TODO $options['processData']
5034
+ * @TODO $options['xhr']
5035
+ * @TODO $options['data'] as string
5036
+ * @TODO XHR interface
5037
+ */
5038
+ public static function ajax($options = array(), $xhr = null) {
5039
+ $options = array_merge(
5040
+ self::$ajaxSettings, $options
5041
+ );
5042
+ $documentID = isset($options['document'])
5043
+ ? self::getDocumentID($options['document'])
5044
+ : null;
5045
+ if ($xhr) {
5046
+ // reuse existing XHR object, but clean it up
5047
+ $client = $xhr;
5048
+ // $client->setParameterPost(null);
5049
+ // $client->setParameterGet(null);
5050
+ $client->setAuth(false);
5051
+ $client->setHeaders("If-Modified-Since", null);
5052
+ $client->setHeaders("Referer", null);
5053
+ $client->resetParameters();
5054
+ } else {
5055
+ // create new XHR object
5056
+ require_once('Zend/Http/Client.php');
5057
+ $client = new Zend_Http_Client();
5058
+ $client->setCookieJar();
5059
+ }
5060
+ if (isset($options['timeout']))
5061
+ $client->setConfig(array(
5062
+ 'timeout' => $options['timeout'],
5063
+ ));
5064
+ // 'maxredirects' => 0,
5065
+ foreach(self::$ajaxAllowedHosts as $k => $host)
5066
+ if ($host == '.' && isset($_SERVER['HTTP_HOST']))
5067
+ self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST'];
5068
+ $host = parse_url($options['url'], PHP_URL_HOST);
5069
+ if (! in_array($host, self::$ajaxAllowedHosts)) {
5070
+ throw new Exception("Request not permitted, host '$host' not present in "
5071
+ ."phpQuery::\$ajaxAllowedHosts");
5072
+ }
5073
+ // JSONP
5074
+ $jsre = "/=\\?(&|$)/";
5075
+ if (isset($options['dataType']) && $options['dataType'] == 'jsonp') {
5076
+ $jsonpCallbackParam = $options['jsonp']
5077
+ ? $options['jsonp'] : 'callback';
5078
+ if (strtolower($options['type']) == 'get') {
5079
+ if (! preg_match($jsre, $options['url'])) {
5080
+ $sep = strpos($options['url'], '?')
5081
+ ? '&' : '?';
5082
+ $options['url'] .= "$sep$jsonpCallbackParam=?";
5083
+ }
5084
+ } else if ($options['data']) {
5085
+ $jsonp = false;
5086
+ foreach($options['data'] as $n => $v) {
5087
+ if ($v == '?')
5088
+ $jsonp = true;
5089
+ }
5090
+ if (! $jsonp) {
5091
+ $options['data'][$jsonpCallbackParam] = '?';
5092
+ }
5093
+ }
5094
+ $options['dataType'] = 'json';
5095
+ }
5096
+ if (isset($options['dataType']) && $options['dataType'] == 'json') {
5097
+ $jsonpCallback = 'json_'.md5(microtime());
5098
+ $jsonpData = $jsonpUrl = false;
5099
+ if ($options['data']) {
5100
+ foreach($options['data'] as $n => $v) {
5101
+ if ($v == '?')
5102
+ $jsonpData = $n;
5103
+ }
5104
+ }
5105
+ if (preg_match($jsre, $options['url']))
5106
+ $jsonpUrl = true;
5107
+ if ($jsonpData !== false || $jsonpUrl) {
5108
+ // remember callback name for httpData()
5109
+ $options['_jsonp'] = $jsonpCallback;
5110
+ if ($jsonpData !== false)
5111
+ $options['data'][$jsonpData] = $jsonpCallback;
5112
+ if ($jsonpUrl)
5113
+ $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']);
5114
+ }
5115
+ }
5116
+ $client->setUri($options['url']);
5117
+ $client->setMethod(strtoupper($options['type']));
5118
+ if (isset($options['referer']) && $options['referer'])
5119
+ $client->setHeaders('Referer', $options['referer']);
5120
+ $client->setHeaders(array(
5121
+ // 'content-type' => $options['contentType'],
5122
+ 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko'
5123
+ .'/2008122010 Firefox/3.0.5',
5124
+ // TODO custom charset
5125
+ 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
5126
+ // 'Connection' => 'keep-alive',
5127
+ // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
5128
+ 'Accept-Language' => 'en-us,en;q=0.5',
5129
+ ));
5130
+ if ($options['username'])
5131
+ $client->setAuth($options['username'], $options['password']);
5132
+ if (isset($options['ifModified']) && $options['ifModified'])
5133
+ $client->setHeaders("If-Modified-Since",
5134
+ self::$lastModified
5135
+ ? self::$lastModified
5136
+ : "Thu, 01 Jan 1970 00:00:00 GMT"
5137
+ );
5138
+ $client->setHeaders("Accept",
5139
+ isset($options['dataType'])
5140
+ && isset(self::$ajaxSettings['accepts'][ $options['dataType'] ])
5141
+ ? self::$ajaxSettings['accepts'][ $options['dataType'] ].", */*"
5142
+ : self::$ajaxSettings['accepts']['_default']
5143
+ );
5144
+ // TODO $options['processData']
5145
+ if ($options['data'] instanceof phpQueryObject) {
5146
+ $serialized = $options['data']->serializeArray($options['data']);
5147
+ $options['data'] = array();
5148
+ foreach($serialized as $r)
5149
+ $options['data'][ $r['name'] ] = $r['value'];
5150
+ }
5151
+ if (strtolower($options['type']) == 'get') {
5152
+ $client->setParameterGet($options['data']);
5153
+ } else if (strtolower($options['type']) == 'post') {
5154
+ $client->setEncType($options['contentType']);
5155
+ $client->setParameterPost($options['data']);
5156
+ }
5157
+ if (self::$active == 0 && $options['global'])
5158
+ phpQueryEvents::trigger($documentID, 'ajaxStart');
5159
+ self::$active++;
5160
+ // beforeSend callback
5161
+ if (isset($options['beforeSend']) && $options['beforeSend'])
5162
+ phpQuery::callbackRun($options['beforeSend'], array($client));
5163
+ // ajaxSend event
5164
+ if ($options['global'])
5165
+ phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options));
5166
+ if (phpQuery::$debug) {
5167
+ self::debug("{$options['type']}: {$options['url']}\n");
5168
+ self::debug("Options: <pre>".var_export($options, true)."</pre>\n");
5169
+ // if ($client->getCookieJar())
5170
+ // self::debug("Cookies: <pre>".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."</pre>\n");
5171
+ }
5172
+ // request
5173
+ $response = $client->request();
5174
+ if (phpQuery::$debug) {
5175
+ self::debug('Status: '.$response->getStatus().' / '.$response->getMessage());
5176
+ self::debug($client->getLastRequest());
5177
+ self::debug($response->getHeaders());
5178
+ }
5179
+ if ($response->isSuccessful()) {
5180
+ // XXX tempolary
5181
+ self::$lastModified = $response->getHeader('Last-Modified');
5182
+ $data = self::httpData($response->getBody(), $options['dataType'], $options);
5183
+ if (isset($options['success']) && $options['success'])
5184
+ phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options));
5185
+ if ($options['global'])
5186
+ phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options));
5187
+ } else {
5188
+ if (isset($options['error']) && $options['error'])
5189
+ phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage()));
5190
+ if ($options['global'])
5191
+ phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/$response->getMessage(), $options));
5192
+ }
5193
+ if (isset($options['complete']) && $options['complete'])
5194
+ phpQuery::callbackRun($options['complete'], array($client, $response->getStatus()));
5195
+ if ($options['global'])
5196
+ phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options));
5197
+ if ($options['global'] && ! --self::$active)
5198
+ phpQueryEvents::trigger($documentID, 'ajaxStop');
5199
+ return $client;
5200
+ // if (is_null($domId))
5201
+ // $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false;
5202
+ // return new phpQueryAjaxResponse($response, $domId);
5203
+ }
5204
+ protected static function httpData($data, $type, $options) {
5205
+ if (isset($options['dataFilter']) && $options['dataFilter'])
5206
+ $data = self::callbackRun($options['dataFilter'], array($data, $type));
5207
+ if (is_string($data)) {
5208
+ if ($type == "json") {
5209
+ if (isset($options['_jsonp']) && $options['_jsonp']) {
5210
+ $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data);
5211
+ }
5212
+ $data = self::parseJSON($data);
5213
+ }
5214
+ }
5215
+ return $data;
5216
+ }
5217
+ /**
5218
+ * Enter description here...
5219
+ *
5220
+ * @param array|phpQuery $data
5221
+ *
5222
+ */
5223
+ public static function param($data) {
5224
+ return http_build_query($data, null, '&');
5225
+ }
5226
+ public static function get($url, $data = null, $callback = null, $type = null) {
5227
+ if (!is_array($data)) {
5228
+ $callback = $data;
5229
+ $data = null;
5230
+ }
5231
+ // TODO some array_values on this shit
5232
+ return phpQuery::ajax(array(
5233
+ 'type' => 'GET',
5234
+ 'url' => $url,
5235
+ 'data' => $data,
5236
+ 'success' => $callback,
5237
+ 'dataType' => $type,
5238
+ ));
5239
+ }
5240
+ public static function post($url, $data = null, $callback = null, $type = null) {
5241
+ if (!is_array($data)) {
5242
+ $callback = $data;
5243
+ $data = null;
5244
+ }
5245
+ return phpQuery::ajax(array(
5246
+ 'type' => 'POST',
5247
+ 'url' => $url,
5248
+ 'data' => $data,
5249
+ 'success' => $callback,
5250
+ 'dataType' => $type,
5251
+ ));
5252
+ }
5253
+ public static function getJSON($url, $data = null, $callback = null) {
5254
+ if (!is_array($data)) {
5255
+ $callback = $data;
5256
+ $data = null;
5257
+ }
5258
+ // TODO some array_values on this shit
5259
+ return phpQuery::ajax(array(
5260
+ 'type' => 'GET',
5261
+ 'url' => $url,
5262
+ 'data' => $data,
5263
+ 'success' => $callback,
5264
+ 'dataType' => 'json',
5265
+ ));
5266
+ }
5267
+ public static function ajaxSetup($options) {
5268
+ self::$ajaxSettings = array_merge(
5269
+ self::$ajaxSettings,
5270
+ $options
5271
+ );
5272
+ }
5273
+ public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) {
5274
+ $loop = is_array($host1)
5275
+ ? $host1
5276
+ : func_get_args();
5277
+ foreach($loop as $host) {
5278
+ if ($host && ! in_array($host, phpQuery::$ajaxAllowedHosts)) {
5279
+ phpQuery::$ajaxAllowedHosts[] = $host;
5280
+ }
5281
+ }
5282
+ }
5283
+ public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) {
5284
+ $loop = is_array($url1)
5285
+ ? $url1
5286
+ : func_get_args();
5287
+ foreach($loop as $url)
5288
+ phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST));
5289
+ }
5290
+ /**
5291
+ * Returns JSON representation of $data.
5292
+ *
5293
+ * @static
5294
+ * @param mixed $data
5295
+ * @return string
5296
+ */
5297
+ public static function toJSON($data) {
5298
+ if (function_exists('json_encode'))
5299
+ return json_encode($data);
5300
+ require_once('Zend/Json/Encoder.php');
5301
+ return Zend_Json_Encoder::encode($data);
5302
+ }
5303
+ /**
5304
+ * Parses JSON into proper PHP type.
5305
+ *
5306
+ * @static
5307
+ * @param string $json
5308
+ * @return mixed
5309
+ */
5310
+ public static function parseJSON($json) {
5311
+ if (function_exists('json_decode')) {
5312
+ $return = json_decode(trim($json), true);
5313
+ // json_decode and UTF8 issues
5314
+ if (isset($return))
5315
+ return $return;
5316
+ }
5317
+ require_once('Zend/Json/Decoder.php');
5318
+ return Zend_Json_Decoder::decode($json);
5319
+ }
5320
+ /**
5321
+ * Returns source's document ID.
5322
+ *
5323
+ * @param $source DOMNode|phpQueryObject
5324
+ * @return string
5325
+ */
5326
+ public static function getDocumentID($source) {
5327
+ if ($source instanceof DOMDOCUMENT) {
5328
+ foreach(phpQuery::$documents as $id => $document) {
5329
+ if ($source->isSameNode($document->document))
5330
+ return $id;
5331
+ }
5332
+ } else if ($source instanceof DOMNODE) {
5333
+ foreach(phpQuery::$documents as $id => $document) {
5334
+ if ($source->ownerDocument->isSameNode($document->document))
5335
+ return $id;
5336
+ }
5337
+ } else if ($source instanceof phpQueryObject)
5338
+ return $source->getDocumentID();
5339
+ else if (is_string($source) && isset(phpQuery::$documents[$source]))
5340
+ return $source;
5341
+ }
5342
+ /**
5343
+ * Get DOMDocument object related to $source.
5344
+ * Returns null if such document doesn't exist.
5345
+ *
5346
+ * @param $source DOMNode|phpQueryObject|string
5347
+ * @return string
5348
+ */
5349
+ public static function getDOMDocument($source) {
5350
+ if ($source instanceof DOMDOCUMENT)
5351
+ return $source;
5352
+ $source = self::getDocumentID($source);
5353
+ return $source
5354
+ ? self::$documents[$id]['document']
5355
+ : null;
5356
+ }
5357
+
5358
+ // UTILITIES
5359
+ // http://docs.jquery.com/Utilities
5360
+
5361
+ /**
5362
+ *
5363
+ * @return unknown_type
5364
+ * @link http://docs.jquery.com/Utilities/jQuery.makeArray
5365
+ */
5366
+ public static function makeArray($obj) {
5367
+ $array = array();
5368
+ if (is_object($object) && $object instanceof DOMNODELIST) {
5369
+ foreach($object as $value)
5370
+ $array[] = $value;
5371
+ } else if (is_object($object) && ! ($object instanceof Iterator)) {
5372
+ foreach(get_object_vars($object) as $name => $value)
5373
+ $array[0][$name] = $value;
5374
+ } else {
5375
+ foreach($object as $name => $value)
5376
+ $array[0][$name] = $value;
5377
+ }
5378
+ return $array;
5379
+ }
5380
+ public static function inArray($value, $array) {
5381
+ return in_array($value, $array);
5382
+ }
5383
+ /**
5384
+ *
5385
+ * @param $object
5386
+ * @param $callback
5387
+ * @return unknown_type
5388
+ * @link http://docs.jquery.com/Utilities/jQuery.each
5389
+ */
5390
+ public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) {
5391
+ $paramStructure = null;
5392
+ if (func_num_args() > 2) {
5393
+ $paramStructure = func_get_args();
5394
+ $paramStructure = array_slice($paramStructure, 2);
5395
+ }
5396
+ if (is_object($object) && ! ($object instanceof Iterator)) {
5397
+ foreach(get_object_vars($object) as $name => $value)
5398
+ phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
5399
+ } else {
5400
+ foreach($object as $name => $value)
5401
+ phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
5402
+ }
5403
+ }
5404
+ /**
5405
+ *
5406
+ * @link http://docs.jquery.com/Utilities/jQuery.map
5407
+ */
5408
+ public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) {
5409
+ $result = array();
5410
+ $paramStructure = null;
5411
+ if (func_num_args() > 2) {
5412
+ $paramStructure = func_get_args();
5413
+ $paramStructure = array_slice($paramStructure, 2);
5414
+ }
5415
+ foreach($array as $v) {
5416
+ $vv = phpQuery::callbackRun($callback, array($v), $paramStructure);
5417
+ // $callbackArgs = $args;
5418
+ // foreach($args as $i => $arg) {
5419
+ // $callbackArgs[$i] = $arg instanceof CallbackParam
5420
+ // ? $v
5421
+ // : $arg;
5422
+ // }
5423
+ // $vv = call_user_func_array($callback, $callbackArgs);
5424
+ if (is_array($vv)) {
5425
+ foreach($vv as $vvv)
5426
+ $result[] = $vvv;
5427
+ } else if ($vv !== null) {
5428
+ $result[] = $vv;
5429
+ }
5430
+ }
5431
+ return $result;
5432
+ }
5433
+ /**
5434
+ *
5435
+ * @param $callback Callback
5436
+ * @param $params
5437
+ * @param $paramStructure
5438
+ * @return unknown_type
5439
+ */
5440
+ public static function callbackRun($callback, $params = array(), $paramStructure = null) {
5441
+ if (! $callback)
5442
+ return;
5443
+ if ($callback instanceof CallbackParameterToReference) {
5444
+ // TODO support ParamStructure to select which $param push to reference
5445
+ if (isset($params[0]))
5446
+ $callback->callback = $params[0];
5447
+ return true;
5448
+ }
5449
+ if ($callback instanceof Callback) {
5450
+ $paramStructure = $callback->params;
5451
+ $callback = $callback->callback;
5452
+ }
5453
+ if (! $paramStructure)
5454
+ return call_user_func_array($callback, $params);
5455
+ $p = 0;
5456
+ foreach($paramStructure as $i => $v) {
5457
+ $paramStructure[$i] = $v instanceof CallbackParam
5458
+ ? $params[$p++]
5459
+ : $v;
5460
+ }
5461
+ return call_user_func_array($callback, $paramStructure);
5462
+ }
5463
+ /**
5464
+ * Merge 2 phpQuery objects.
5465
+ * @param array $one
5466
+ * @param array $two
5467
+ * @protected
5468
+ * @todo node lists, phpQueryObject
5469
+ */
5470
+ public static function merge($one, $two) {
5471
+ $elements = $one->elements;
5472
+ foreach($two->elements as $node) {
5473
+ $exists = false;
5474
+ foreach($elements as $node2) {
5475
+ if ($node2->isSameNode($node))
5476
+ $exists = true;
5477
+ }
5478
+ if (! $exists)
5479
+ $elements[] = $node;
5480
+ }
5481
+ return $elements;
5482
+ // $one = $one->newInstance();
5483
+ // $one->elements = $elements;
5484
+ // return $one;
5485
+ }
5486
+ /**
5487
+ *
5488
+ * @param $array
5489
+ * @param $callback
5490
+ * @param $invert
5491
+ * @return unknown_type
5492
+ * @link http://docs.jquery.com/Utilities/jQuery.grep
5493
+ */
5494
+ public static function grep($array, $callback, $invert = false) {
5495
+ $result = array();
5496
+ foreach($array as $k => $v) {
5497
+ $r = call_user_func_array($callback, array($v, $k));
5498
+ if ($r === !(bool)$invert)
5499
+ $result[] = $v;
5500
+ }
5501
+ return $result;
5502
+ }
5503
+ public static function unique($array) {
5504
+ return array_unique($array);
5505
+ }
5506
+ /**
5507
+ *
5508
+ * @param $function
5509
+ * @return unknown_type
5510
+ * @TODO there are problems with non-static methods, second parameter pass it
5511
+ * but doesnt verify is method is really callable
5512
+ */
5513
+ public static function isFunction($function) {
5514
+ return is_callable($function);
5515
+ }
5516
+ public static function trim($str) {
5517
+ return trim($str);
5518
+ }
5519
+ /* PLUGINS NAMESPACE */
5520
+ /**
5521
+ *
5522
+ * @param $url
5523
+ * @param $callback
5524
+ * @param $param1
5525
+ * @param $param2
5526
+ * @param $param3
5527
+ * @return phpQueryObject
5528
+ */
5529
+ public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) {
5530
+ if (self::plugin('WebBrowser')) {
5531
+ $params = func_get_args();
5532
+ return self::callbackRun(array(self::$plugins, 'browserGet'), $params);
5533
+ } else {
5534
+ self::debug('WebBrowser plugin not available...');
5535
+ }
5536
+ }
5537
+ /**
5538
+ *
5539
+ * @param $url
5540
+ * @param $data
5541
+ * @param $callback
5542
+ * @param $param1
5543
+ * @param $param2
5544
+ * @param $param3
5545
+ * @return phpQueryObject
5546
+ */
5547
+ public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) {
5548
+ if (self::plugin('WebBrowser')) {
5549
+ $params = func_get_args();
5550
+ return self::callbackRun(array(self::$plugins, 'browserPost'), $params);
5551
+ } else {
5552
+ self::debug('WebBrowser plugin not available...');
5553
+ }
5554
+ }
5555
+ /**
5556
+ *
5557
+ * @param $ajaxSettings
5558
+ * @param $callback
5559
+ * @param $param1
5560
+ * @param $param2
5561
+ * @param $param3
5562
+ * @return phpQueryObject
5563
+ */
5564
+ public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) {
5565
+ if (self::plugin('WebBrowser')) {
5566
+ $params = func_get_args();
5567
+ return self::callbackRun(array(self::$plugins, 'browser'), $params);
5568
+ } else {
5569
+ self::debug('WebBrowser plugin not available...');
5570
+ }
5571
+ }
5572
+ /**
5573
+ *
5574
+ * @param $code
5575
+ * @return string
5576
+ */
5577
+ public static function php($code) {
5578
+ return self::code('php', $code);
5579
+ }
5580
+ /**
5581
+ *
5582
+ * @param $type
5583
+ * @param $code
5584
+ * @return string
5585
+ */
5586
+ public static function code($type, $code) {
5587
+ return "<$type><!-- ".trim($code)." --></$type>";
5588
+ }
5589
+
5590
+ public static function __callStatic($method, $params) {
5591
+ return call_user_func_array(
5592
+ array(phpQuery::$plugins, $method),
5593
+ $params
5594
+ );
5595
+ }
5596
+ protected static function dataSetupNode($node, $documentID) {
5597
+ // search are return if alredy exists
5598
+ foreach(phpQuery::$documents[$documentID]->dataNodes as $dataNode) {
5599
+ if ($node->isSameNode($dataNode))
5600
+ return $dataNode;
5601
+ }
5602
+ // if doesn't, add it
5603
+ phpQuery::$documents[$documentID]->dataNodes[] = $node;
5604
+ return $node;
5605
+ }
5606
+ protected static function dataRemoveNode($node, $documentID) {
5607
+ // search are return if alredy exists
5608
+ foreach(phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) {
5609
+ if ($node->isSameNode($dataNode)) {
5610
+ unset(self::$documents[$documentID]->dataNodes[$k]);
5611
+ unset(self::$documents[$documentID]->data[ $dataNode->dataID ]);
5612
+ }
5613
+ }
5614
+ }
5615
+ public static function data($node, $name, $data, $documentID = null) {
5616
+ if (! $documentID)
5617
+ // TODO check if this works
5618
+ $documentID = self::getDocumentID($node);
5619
+ $document = phpQuery::$documents[$documentID];
5620
+ $node = self::dataSetupNode($node, $documentID);
5621
+ if (! isset($node->dataID))
5622
+ $node->dataID = ++phpQuery::$documents[$documentID]->uuid;
5623
+ $id = $node->dataID;
5624
+ if (! isset($document->data[$id]))
5625
+ $document->data[$id] = array();
5626
+ if (! is_null($data))
5627
+ $document->data[$id][$name] = $data;
5628
+ if ($name) {
5629
+ if (isset($document->data[$id][$name]))
5630
+ return $document->data[$id][$name];
5631
+ } else
5632
+ return $id;
5633
+ }
5634
+ public static function removeData($node, $name, $documentID) {
5635
+ if (! $documentID)
5636
+ // TODO check if this works
5637
+ $documentID = self::getDocumentID($node);
5638
+ $document = phpQuery::$documents[$documentID];
5639
+ $node = self::dataSetupNode($node, $documentID);
5640
+ $id = $node->dataID;
5641
+ if ($name) {
5642
+ if (isset($document->data[$id][$name]))
5643
+ unset($document->data[$id][$name]);
5644
+ $name = null;
5645
+ // foreach($document->data[$id] as $name)
5646
+ // break;
5647
+ foreach($document->data[$id] as $name) {
5648
+ break;
5649
+ }
5650
+ if (! $name)
5651
+ self::removeData($node, $name, $documentID);
5652
+ } else {
5653
+ self::dataRemoveNode($node, $documentID);
5654
+ }
5655
+ }
5656
+ }
5657
+ /**
5658
+ * Plugins static namespace class.
5659
+ *
5660
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
5661
+ * @package phpQuery
5662
+ * @todo move plugin methods here (as statics)
5663
+ */
5664
+ class phpQueryPlugins {
5665
+ public function __call($method, $args) {
5666
+ if (isset(phpQuery::$extendStaticMethods[$method])) {
5667
+ $return = call_user_func_array(
5668
+ phpQuery::$extendStaticMethods[$method],
5669
+ $args
5670
+ );
5671
+ } else if (isset(phpQuery::$pluginsStaticMethods[$method])) {
5672
+ $class = phpQuery::$pluginsStaticMethods[$method];
5673
+ $realClass = "phpQueryPlugin_$class";
5674
+ $return = call_user_func_array(
5675
+ array($realClass, $method),
5676
+ $args
5677
+ );
5678
+ return isset($return)
5679
+ ? $return
5680
+ : $this;
5681
+ } else
5682
+ throw new Exception("Method '{$method}' doesnt exist");
5683
+ }
5684
+ }
5685
+ /**
5686
+ * Shortcut to phpQuery::pq($arg1, $context)
5687
+ * Chainable.
5688
+ *
5689
+ * @see phpQuery::pq()
5690
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
5691
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
5692
+ * @package phpQuery
5693
+ */
5694
+ function pq($arg1, $context = null) {
5695
+ $args = func_get_args();
5696
+ return call_user_func_array(
5697
+ array('phpQuery', 'pq'),
5698
+ $args
5699
+ );
5700
+ }
5701
+ // add plugins dir and Zend framework to include path
5702
+ set_include_path(
5703
+ get_include_path()
5704
+ .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/'
5705
+ .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/plugins/'
5706
+ );
5707
+ // why ? no __call nor __get for statics in php...
5708
+ // XXX __callStatic will be available in PHP 5.3
5709
+ phpQuery::$plugins = new phpQueryPlugins();
5710
+ // include bootstrap file (personal library config)
5711
+ if (file_exists(dirname(__FILE__).'/phpQuery/bootstrap.php'))
5712
+ require_once dirname(__FILE__).'/phpQuery/bootstrap.php';
includes/preview.php CHANGED
@@ -925,11 +925,11 @@ function generate_code_preview (
925
  var start_time = new Date().getTime();
926
 
927
  if (sticky) {
928
- spinner_horizontal_margin = create_spinner_sticky ("#spinner-horizontal-margin", "horizontal").spinner ("option", "min", - 600);
929
- spinner_vertical_margin = create_spinner_sticky ("#spinner-vertical-margin", "horizontal").spinner ("option", "min", - 600);
930
 
931
- spinner_horizontal_margin.spinner ("value", <?php echo $horizontal_margin; ?>);
932
- spinner_vertical_margin.spinner ("value", <?php echo $vertical_margin; ?>);
933
  } else {
934
  spinner_margin_top = create_spinner ("#spinner-margin-top", "margin-top", "horizontal").spinner ("option", "min", - $("#p1").outerHeight (true));
935
  spinner_margin_bottom = create_spinner ("#spinner-margin-bottom", "margin-bottom", "horizontal").spinner ("option", "min", - $("#p1").outerHeight (true));
925
  var start_time = new Date().getTime();
926
 
927
  if (sticky) {
928
+ spinner_horizontal_margin = create_spinner_sticky ("#spinner-horizontal-margin", "horizontal").spinner ("option", "min", - 600).spinner ("option", "max", 5000);
929
+ spinner_vertical_margin = create_spinner_sticky ("#spinner-vertical-margin", "horizontal").spinner ("option", "min", - 600).spinner ("option", "max", 5000);
930
 
931
+ spinner_horizontal_margin.spinner ("value", <?php echo $horizontal_margin == '' ? "''" : $horizontal_margin; ?>);
932
+ spinner_vertical_margin.spinner ("value", <?php echo $vertical_margin == '' ? "''" : $vertical_margin; ?>);
933
  } else {
934
  spinner_margin_top = create_spinner ("#spinner-margin-top", "margin-top", "horizontal").spinner ("option", "min", - $("#p1").outerHeight (true));
935
  spinner_margin_bottom = create_spinner ("#spinner-margin-bottom", "margin-bottom", "horizontal").spinner ("option", "min", - $("#p1").outerHeight (true));
js/ad-inserter.js CHANGED
@@ -1,4 +1,4 @@
1
- var javascript_version = "2.3.8";
2
  var ignore_key = true;
3
  var start = 1;
4
  var end = 16;
@@ -2043,7 +2043,7 @@ jQuery(document).ready(function($) {
2043
  }
2044
 
2045
  $('#process-php-' + tab).checkboxButton ();
2046
- $('#show-label-' + tab).checkboxButton ();
2047
 
2048
  $('#ai-misc-container-' + tab).tabs();
2049
  $('#ai-misc-tabs-' + tab).show();
1
+ var javascript_version = "2.3.9";
2
  var ignore_key = true;
3
  var start = 1;
4
  var end = 16;
2043
  }
2044
 
2045
  $('#process-php-' + tab).checkboxButton ();
2046
+ $('#disable-insertion-' + tab).checkboxButton ();
2047
 
2048
  $('#ai-misc-container-' + tab).tabs();
2049
  $('#ai-misc-tabs-' + tab).show();
readme.txt CHANGED
@@ -5,8 +5,8 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_i
5
  Tags: ads, adsense, header footer code, ad management, sticky fixed widgets, advanced geo targeting ads, amp adverts ad rotation, advertising manager, amazon banners ad injection, ad blocking detection, PHP Javascript HTML insertion, custom fields posts
6
  Requires at least: 4.0
7
  Tested up to: 4.9
8
- Requires PHP: 5.3
9
- Stable tag: 2.3.7
10
  License: GPLv3
11
 
12
  Insert and manage ads: Amazon, AdSense ads, banner rotation, sticky widget ads, shortcodes, AMP, PHP, HTML, CSS, form, tracking, footer, header code
@@ -15,7 +15,7 @@ Insert and manage ads: Amazon, AdSense ads, banner rotation, sticky widget ads,
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**.
21
  It's all about the settings.
@@ -56,21 +56,21 @@ Check <a href="https://affiliate-program.amazon.com/help/topic/t405" target="_bl
56
  * Syntax highlighting [editor](https://adinserter.pro/code-editing)
57
  * Code preview with visual CSS editor
58
  * Automatically inserts ads on posts and pages
59
- * Insert before or after post
60
- * Insert before or after content
61
- * Insert before or after paragraph
62
- * Insert before or after random paragraph
63
- * Insert before or after multiple paragraphs
64
- * Insert before or after comments
65
- * Insert before or after excerpt
66
- * Insert before or after any HTML element on page
67
- * Insert above header (after `<body>` tag)
68
- * Insert in footer (before `</body>` tag)
69
- * Insert at relative positions in posts
70
- * Insert between posts on blog pages (in-feed AdSense ads)
71
- * Insert between excerpts on blog pages
72
- * Insert between comments
73
- * Insert at custom hook positions (`do_action ()` WP function)
74
  * Clearance options to avoid insertion near images or headers
75
  * Insertion exceptions for posts and pages
76
  * Insert header (`<head>` section) and footer code
@@ -108,7 +108,7 @@ And Ad Inserter Pro - all-in-one <a href="http://adinserter.pro/" target="_blank
108
  * Ad impression and click tracking (works also with `<iframe>` Javascript ads like Google AdSense)
109
  * External tracking via Google Analytics or Piwik
110
  * A/B testing
111
- * Sticky ad positions with optional close button
112
  * Sticky sidebar ads (stick to the screen or to the content)
113
  * Sticky ad trigger (page scroll in % or px, HTML element visible)
114
  * Sticky ad animations (fade, slide, turn, flip, zoom)
@@ -289,16 +289,17 @@ Support the advancement of this plugin:
289
  = I have activated Ad Inserter. How can I use it? =
290
 
291
  1. After activation, click "Settings / Ad Inserter" to access the settings page
292
- 2. Put ad (or any other HTML/Javascript/PHP) code into the code window
293
- 3. Set automatic insertion option (for example: Before Post)
294
- 4. Enable at least one page type (for example: Posts, some insertion options don't work on all page types)
295
- 5. Save settings
296
- 6. Check also <a href="http://adinserter.pro/settings" target="_blank">common Ad Inserter settings</a>
297
- 7. Check inserted code on the posts
298
- 8. Ads are not showing? Check <a href="https://adinserter.pro/documentation#ads-not-displayed" target="_blank">troubleshooting guide</a> to find out what to check to fix the problem.
 
299
 
300
 
301
- = Does Ad Inserter insert any internal ads? =
302
 
303
  No revenue sharing and no such thing as "internal ads" or "our ads" on your website. Period. What you configure is what will be inserted (+ some internal scripts for plugin features).
304
  Ad Inserter is free, open source plugin and inserts only the code you configure (blocks 1 - 16, Header, Footer). The code you see is the code that will be inserted. Please check page source code before you make any conclusion.
@@ -386,6 +387,12 @@ You can also try with `[adinserter data='tag']', `[adinserter data='short-title'
386
  Check <a href="http://adinserter.pro/settings" target="_blank">common Ad Inserter settings</a>
387
 
388
 
 
 
 
 
 
 
389
  = I wish to show ads side by side but not in the same block. How do I do this? =
390
 
391
  Configure block 1 and 2 with ads using:
@@ -454,7 +461,8 @@ There are two possible approaches.
454
 
455
  1. Go to Ad Inserter settings page and define default insertion options for post/page.
456
  2. Enable automatic insertion for Posts/Pages (use default value after checkbox - blank selection means no individual exceptions).
457
- 3. Click on Lists, enter url (or space separated urls) for Urls, e.g. `/permalink-url`, and white-list or black-list it.
 
458
 
459
  For details check <a href="https://adinserter.pro/exceptions" target="_blank">Individual Post/Page Exceptions</a>.
460
 
@@ -644,6 +652,12 @@ AD CODE RIGHT
644
 
645
  == Changelog ==
646
 
 
 
 
 
 
 
647
  = 2.3.8 =
648
  - Added support rotation option shares
649
  - Added support for sticky ad settings and animations (Pro only)
@@ -737,6 +751,12 @@ For the changelog of earlier versions, please refer to the separate changelog.tx
737
 
738
  == Upgrade Notice ==
739
 
 
 
 
 
 
 
740
  = 2.3.8 =
741
  Added support rotation option shares;
742
  Added support for sticky ad settings and animations (Pro only);
5
  Tags: ads, adsense, header footer code, ad management, sticky fixed widgets, advanced geo targeting ads, amp adverts ad rotation, advertising manager, amazon banners ad injection, ad blocking detection, PHP Javascript HTML insertion, custom fields posts
6
  Requires at least: 4.0
7
  Tested up to: 4.9
8
+ Requires PHP: 5.6
9
+ Stable tag: 2.3.8
10
  License: GPLv3
11
 
12
  Insert and manage ads: Amazon, AdSense ads, banner rotation, sticky widget ads, shortcodes, AMP, PHP, HTML, CSS, form, tracking, footer, header 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 ads, opt-in forms, Javascript, CSS, HTML, PHP, analytics, tracking or advert code anywhere on the page.
19
 
20
  **Ad Inserter can insert ads where other plugins fail**.
21
  It's all about the settings.
56
  * Syntax highlighting [editor](https://adinserter.pro/code-editing)
57
  * Code preview with visual CSS editor
58
  * Automatically inserts ads on posts and pages
59
+ * Insert ads before or after post
60
+ * Insert ads before or after content
61
+ * Insert ads before or after paragraph
62
+ * Insert ads before or after random paragraph
63
+ * Insert ads before or after multiple paragraphs
64
+ * Insert ads before or after comments
65
+ * Insert ads before or after excerpt
66
+ * Insert ads before or after any HTML element on page
67
+ * Insert ads above the header (after `<body>` tag)
68
+ * Insert ads in the footer (before `</body>` tag)
69
+ * Insert ads at relative positions in posts
70
+ * Insert ads between posts on blog pages (in-feed AdSense ads)
71
+ * Insert ads between excerpts on blog pages
72
+ * Insert ads between comments
73
+ * Insert ads at custom hook positions (`do_action ()` WP function)
74
  * Clearance options to avoid insertion near images or headers
75
  * Insertion exceptions for posts and pages
76
  * Insert header (`<head>` section) and footer code
108
  * Ad impression and click tracking (works also with `<iframe>` Javascript ads like Google AdSense)
109
  * External tracking via Google Analytics or Piwik
110
  * A/B testing
111
+ * Sticky ads with optional close button
112
  * Sticky sidebar ads (stick to the screen or to the content)
113
  * Sticky ad trigger (page scroll in % or px, HTML element visible)
114
  * Sticky ad animations (fade, slide, turn, flip, zoom)
289
  = I have activated Ad Inserter. How can I use it? =
290
 
291
  1. After activation, click "Settings / Ad Inserter" to access the settings page
292
+ 2. There are 16 code blocks - each block means ad code at some position (or multiple positions in some cases)
293
+ 3. Put ad (or any other HTML/Javascript/PHP) code into the code window
294
+ 4. Set automatic insertion option (for example: Before Post)
295
+ 5. Enable at least one page type (for example: Posts, some insertion options don't work on all page types)
296
+ 6. Save settings
297
+ 7. Check also <a href="http://adinserter.pro/settings" target="_blank">common Ad Inserter settings</a>
298
+ 8. Check inserted code on the posts
299
+ 9. Ads are not showing? Check <a href="https://adinserter.pro/documentation#ads-not-displayed" target="_blank">troubleshooting guide</a> to find out what to check to fix the problem.
300
 
301
 
302
+ = Does Ad Inserter insert any internal ads? =
303
 
304
  No revenue sharing and no such thing as "internal ads" or "our ads" on your website. Period. What you configure is what will be inserted (+ some internal scripts for plugin features).
305
  Ad Inserter is free, open source plugin and inserts only the code you configure (blocks 1 - 16, Header, Footer). The code you see is the code that will be inserted. Please check page source code before you make any conclusion.
387
  Check <a href="http://adinserter.pro/settings" target="_blank">common Ad Inserter settings</a>
388
 
389
 
390
+ = What PHP versions are supported? =
391
+
392
+ Officially PHP 5.6 and higher versions up to PHP 7.2 are supported. However, Ad Inserter should work also with older PHP versions supported by WordPress.
393
+ Some plugins like PHP Compatibility Checker may report warnings with PHP 7.2 because they check also files that are loaded only for older PHP versions.
394
+
395
+
396
  = I wish to show ads side by side but not in the same block. How do I do this? =
397
 
398
  Configure block 1 and 2 with ads using:
461
 
462
  1. Go to Ad Inserter settings page and define default insertion options for post/page.
463
  2. Enable automatic insertion for Posts/Pages (use default value after checkbox - blank selection means no individual exceptions).
464
+ 3a. Click on Lists, enter post IDs (click on the button left of the list) and white-list or black-list them.
465
+ 3b. Click on Lists, enter url (or comma separated urls) for Urls, e.g. `/permalink-url`, and white-list or black-list it.
466
 
467
  For details check <a href="https://adinserter.pro/exceptions" target="_blank">Individual Post/Page Exceptions</a>.
468
 
652
 
653
  == Changelog ==
654
 
655
+ = 2.3.9 =
656
+ - Added option to easily disable insertion of individual code block
657
+ - Changes for compatibility with PHP 7.2
658
+ - Added non-interaction parameter to external tracking (Pro only)
659
+ - Few minor bug fixes, cosmetic changes and code improvements
660
+
661
  = 2.3.8 =
662
  - Added support rotation option shares
663
  - Added support for sticky ad settings and animations (Pro only)
751
 
752
  == Upgrade Notice ==
753
 
754
+ = 2.3.9 =
755
+ Added option to easily disable insertion of individual code block;
756
+ Changes for compatibility with PHP 7.2;
757
+ Added non-interaction parameter to external tracking (Pro only);
758
+ Few minor bug fixes, cosmetic changes and code improvements
759
+
760
  = 2.3.8 =
761
  Added support rotation option shares;
762
  Added support for sticky ad settings and animations (Pro only);
settings.php CHANGED
@@ -150,7 +150,7 @@ function generate_settings_form (){
150
  <button type="button" class="ai-top-button" style="display: none; margin: 0 10px 0 0; width: 62px; outline-color: transparent;" onclick="window.open('https://wordpress.org/support/plugin/ad-inserter')" title="<?php echo AD_INSERTER_NAME; ?> support forum">Support</button>
151
  <button type="button" class="ai-top-button" style="display: none; margin: 0 10px 0 0; width: 62px; outline-color: transparent;" onclick="window.open('https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=LHGZEMRTR7WB4')" title="Support Free Ad Inserter development. If you are making money with Ad Inserter consider donating some small amount. Even 1 dollar counts. Thank you!">Donate</button>
152
  <button type="button" class="ai-top-button" style="display: none; margin: 0 10px 0 0; width: 62px; outline-color: transparent;" onclick="window.open('https://wordpress.org/support/plugin/ad-inserter/reviews/')" title="If you like Ad Inserter and have a moment, please help me spread the word by reviewing the plugin on WordPres">Review</button>
153
- <button type="button" class="ai-top-button" style="display: none; margin: 0 10px 0 0; width: 62px; outline-color: transparent;" onclick="window.open('http://adinserter.pro/')" title="Need more code blocks, GEO targeting, impression and click tracking? Upgrade to Ad Inserter Pro">Go&nbsp;Pro</button>
154
  <button id="ai-list" type="button" class="ai-top-button" style="width: 62px; display: none; margin-right: 0px; outline-color: transparent;" title="Show list of all code blocks"><span>Blocks</span></button>
155
  </div>
156
 
@@ -287,12 +287,16 @@ function generate_settings_form (){
287
  $manual_php_function [$block] = $obj->get_enable_php_call() == AI_ENABLED;
288
  $manual [$block] = ($manual_widget [$block] && !empty ($sidebars_with_widget [$block])) || $manual_shortcode [$block] || $manual_php_function [$block];
289
 
 
 
290
  $style = "";
291
  $ad_name = "";
292
  $sidebars [$block] = "";
293
- if ($automatic && $manual [$block]) $style = "font-weight: bold; color: #c4f;";
294
- elseif ($automatic) $style = "font-weight: bold; color: #e44;";
295
- elseif ($manual [$block]) $style = "font-weight: bold; color: #66f;";
 
 
296
 
297
  if (!empty ($sidebars_with_widget [$block])) $sidebars [$block] = implode (", ", $sidebars_with_widget [$block]);
298
 
@@ -508,6 +512,13 @@ function generate_settings_form (){
508
  <label class="checkbox-button" for="process-php-<?php echo $block; ?>" title="Process PHP code in block"><span class="checkbox-icon icon-php<?php if ($obj->get_process_php () == AI_ENABLED) echo ' on'; ?>"></span></label>
509
  </span>
510
  <?php endif; ?>
 
 
 
 
 
 
 
511
  <?php if (function_exists ('ai_settings_top_buttons_2')) ai_settings_top_buttons_2 ($block, $obj, $default); ?>
512
  </div>
513
 
@@ -939,7 +950,7 @@ function generate_settings_form (){
939
  <tr>
940
  <td style="width: 70%; padding-bottom: 5px;">
941
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_POSTS, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
942
- <input type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_POSTS, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_display_settings_post(); ?>" id="display-posts-<?php echo $block; ?>" title="Enable or disable insertion on posts" <?php if ($obj->get_display_settings_post()==AI_ENABLED) echo 'checked '; ?> />
943
 
944
  <select style="margin: 0 0 0 10px;" id="enabled-on-which-posts-<?php echo $block; ?>" name="<?php echo AI_OPTION_ENABLED_ON_WHICH_POSTS, WP_FORM_FIELD_POSTFIX, $block; ?>" default="<?php echo $default->get_ad_enabled_on_which_posts(); ?>" style="width:160px" title="Individual post exceptions (if enabled here) can be configured in post editor. Leave blank for no individual post exceptions.">
945
  <option value="<?php echo AI_NO_INDIVIDUAL_EXCEPTIONS; ?>" <?php echo ($obj->get_ad_enabled_on_which_posts()==AI_NO_INDIVIDUAL_EXCEPTIONS) ? AD_SELECT_SELECTED : AD_EMPTY_VALUE; ?>><?php echo AI_TEXT_NO_INDIVIDUAL_EXCEPTIONS; ?></option>
@@ -962,20 +973,20 @@ function generate_settings_form (){
962
  </td>
963
  <td style="padding-left: 8px;">
964
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_HOMEPAGE, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
965
- <input id= "display-homepage-<?php echo $block; ?>" style="margin-left: 10px;" type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_HOMEPAGE, WP_FORM_FIELD_POSTFIX, $block; ?>" title="Enable or disable insertion on homepage: latest posts (including sub-pages), static page or theme homepage" value="1" default="<?php echo $default->get_display_settings_home(); ?>" <?php if ($obj->get_display_settings_home()==AI_ENABLED) echo 'checked '; ?> />
966
- <label for="display-homepage-<?php echo $block; ?>" title="Enable or disable insertion on homepage: latest posts (including sub-pages), static page or theme homepage">Homepage</label>
967
  </td>
968
  <td style="padding-left: 8px;">
969
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_CATEGORY_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
970
- <input id= "display-category-<?php echo $block; ?>" style="margin-left: 10px;" type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_CATEGORY_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" title="Enable or disable insertion on category blog pages (including sub-pages)" value="1" default="<?php echo $default->get_display_settings_category(); ?>" <?php if ($obj->get_display_settings_category()==AI_ENABLED) echo 'checked '; ?> />
971
- <label for="display-category-<?php echo $block; ?>" title="Enable or disable insertion on category blog pages (including sub-pages)">Category pages</label>
972
  </td>
973
  </tr>
974
 
975
  <tr>
976
  <td style="width: 70%">
977
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
978
- <input type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_display_settings_page(); ?>" id="display-pages-<?php echo $block; ?>" title="Enable or disable insertion on static pages" <?php if ($obj->get_display_settings_page()==AI_ENABLED) echo 'checked '; ?> />
979
 
980
  <select style="margin: 0 0 0 10px;" id="enabled-on-which-pages-<?php echo $block; ?>" name="<?php echo AI_OPTION_ENABLED_ON_WHICH_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" default="<?php echo $default->get_ad_enabled_on_which_pages(); ?>" style="width:160px" title="Individual static page exceptions (if enabled here) can be configured in page editor. Leave blank for no individual page exceptions.">
981
  <option value="<?php echo AI_NO_INDIVIDUAL_EXCEPTIONS; ?>" <?php echo ($obj->get_ad_enabled_on_which_pages()==AI_NO_INDIVIDUAL_EXCEPTIONS) ? AD_SELECT_SELECTED : AD_EMPTY_VALUE; ?>><?php echo AI_TEXT_NO_INDIVIDUAL_EXCEPTIONS; ?></option>
@@ -989,13 +1000,13 @@ function generate_settings_form (){
989
  </td>
990
  <td style="padding-left: 8px;">
991
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_SEARCH_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
992
- <input id= "display-search-<?php echo $block; ?>" style="margin-left: 10px;" type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_SEARCH_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" title="Enable or disable insertion on search blog pages" value="1" default="<?php echo $default->get_display_settings_search(); ?>" <?php if ($obj->get_display_settings_search()==AI_ENABLED) echo 'checked '; ?> />
993
- <label for="display-search-<?php echo $block; ?>" title="Enable or disable insertion on search blog pages">Search pages</label>
994
  </td>
995
  <td style="padding-left: 8px;">
996
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_ARCHIVE_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
997
- <input id= "display-archive-<?php echo $block; ?>" style="margin-left: 10px;" type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_ARCHIVE_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" title="Enable or disable insertion on tag or archive blog pages" value="1" default="<?php echo $default->get_display_settings_archive(); ?>" <?php if ($obj->get_display_settings_archive()==AI_ENABLED) echo 'checked '; ?> />
998
- <label for="display-archive-<?php echo $block; ?>" title="Enable or disable insertion on tag or archive blog pages">Tag / Archive pages</label>
999
  </td>
1000
  </tr>
1001
  </table>
@@ -1475,7 +1486,7 @@ function generate_settings_form (){
1475
  <td style="padding: 4px 10px 4px 0;">
1476
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_WIDGET, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1477
  <input id="enable-widget-<?php echo $block; ?>" type="checkbox" name="<?php echo AI_OPTION_ENABLE_WIDGET, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_widget(); ?>" <?php if ($obj->get_enable_widget () == AI_ENABLED) echo 'checked '; ?> />
1478
- <label for="enable-widget-<?php echo $block; ?>" title="Enable or disable widget for this code block">
1479
  Widget
1480
  </label>
1481
  </td>
@@ -1487,7 +1498,7 @@ function generate_settings_form (){
1487
  <td style="padding: 4px 10px 4px 0;">
1488
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_MANUAL, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1489
  <input type="checkbox" id="enable-shortcode-<?php echo $block; ?>" name="<?php echo AI_OPTION_ENABLE_MANUAL, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_manual(); ?>" <?php if ($obj->get_enable_manual () == AI_ENABLED) echo 'checked '; ?> />
1490
- <label for="enable-shortcode-<?php echo $block; ?>" title="Enable or disable shortcode for manual insertion of this code block in posts and pages">
1491
  Shortcode
1492
  </label>
1493
  </td>
@@ -1503,7 +1514,7 @@ function generate_settings_form (){
1503
  <td style="padding: 4px 10px 4px 0;">
1504
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_PHP_CALL, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1505
  <input id="enable-php-call-<?php echo $block; ?>" type="checkbox" name="<?php echo AI_OPTION_ENABLE_PHP_CALL, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_php_call(); ?>" <?php if ($manual_php_function [$block] == AI_ENABLED) echo 'checked '; ?> />
1506
- <label for="enable-php-call-<?php echo $block; ?>" title="Enable or disable PHP function call to insert this code block at any position in template file. If function is disabled for block it will return empty string.">
1507
  PHP function
1508
  </label>
1509
  </td>
@@ -1630,17 +1641,17 @@ function generate_settings_form (){
1630
  <td>
1631
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_AJAX, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1632
  <input style="margin-left: 10px;" id="enable-ajax-<?php echo $block; ?>" type="checkbox" name="<?php echo AI_OPTION_ENABLE_AJAX, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_ajax(); ?>" <?php if ($obj->get_enable_ajax () == AI_ENABLED) echo 'checked '; ?> />
1633
- <label for="enable-ajax-<?php echo $block; ?>" title="Enable or disable insertion for Ajax requests">Ajax requests</label>
1634
  </td>
1635
  <td>
1636
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_FEED, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1637
  <input style="margin-left: 10px;" id="enable-feed-<?php echo $block; ?>" type="checkbox" name="<?php echo AI_OPTION_ENABLE_FEED, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_feed(); ?>" <?php if ($obj->get_enable_feed () == AI_ENABLED) echo 'checked '; ?> />
1638
- <label for="enable-feed-<?php echo $block; ?>" title="Enable or disable insertion in RSS feeds">RSS Feed</label>
1639
  </td>
1640
  <td>
1641
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_404, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1642
  <input style="margin-left: 10px;" id="enable-404-<?php echo $block; ?>" type="checkbox" name="<?php echo AI_OPTION_ENABLE_404, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_404(); ?>" <?php if ($obj->get_enable_404 () == AI_ENABLED) echo 'checked '; ?> />
1643
- <label for="enable-404-<?php echo $block; ?>" title="Enable or disable insertion on page for Error 404: Page not found">Error 404 page</label>
1644
  </td>
1645
  <td>
1646
  </tr>
@@ -2045,10 +2056,10 @@ function generate_settings_form (){
2045
  <tr>
2046
  <td style="padding: 0 0 2px 0;">
2047
  <input type="hidden" name="hook-enabled-<?php echo $hook; ?>" value="0" />
2048
- <input type="checkbox" name="hook-enabled-<?php echo $hook; ?>" value="1" default="<?php echo AI_DISABLED; ?>" id="hook-enabled-<?php echo $hook; ?>" title="Enable or disable hook" <?php if (get_hook_enabled ($hook) == AI_ENABLED) echo 'checked '; ?> />
2049
  </td>
2050
  <td style="white-space: nowrap;">
2051
- <label for="hook-enabled-<?php echo $hook; ?>" title="Enable or disable hook">Hook <?php echo $hook; ?> name</label>
2052
  </td>
2053
  <td style="width: 25%;">
2054
  <input style="width: 100%;" title="Hook name for automatic insertion selection" type="text" name="hook-name-<?php echo $hook; ?>" default="" value="<?php echo get_hook_name ($hook); ?>" size="30" maxlength="80" />
@@ -2083,7 +2094,7 @@ function generate_settings_form (){
2083
 
2084
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_h'; ?>" value="0" />
2085
  <input type="checkbox" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_h'; ?>" id="enable-header" value="1" default="<?php echo $default->get_enable_manual(); ?>" <?php if ($adH->get_enable_manual () == AI_ENABLED) echo 'checked '; ?> style="display: none;" />
2086
- <label class="checkbox-button" style="margin-left: 10px;" for="enable-header" title="Enable or disable insertion of this code into HTML page header"><span class="checkbox-icon icon-enabled<?php if ($adH->get_enable_manual () == AI_ENABLED) echo ' on'; ?>"></span></label>
2087
 
2088
  <input type="hidden" name="<?php echo AI_OPTION_PROCESS_PHP, '_block_h'; ?>" value="0" />
2089
  <input type="checkbox" name="<?php echo AI_OPTION_PROCESS_PHP, '_block_h'; ?>" value="1" id="process-php-h" default="<?php echo $default->get_process_php (); ?>" <?php if ($adH->get_process_php () == AI_ENABLED) echo 'checked '; ?> style="display: none;" />
@@ -2125,7 +2136,7 @@ function generate_settings_form (){
2125
  <span style="float: right; margin-top: 2px;">
2126
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_404, '_block_h'; ?>" value="0" />
2127
  <input style="margin-left: 10px; margin-top: 1px;" type="checkbox" name="<?php echo AI_OPTION_ENABLE_404, '_block_h'; ?>" id="enable-header-404" value="1" default="<?php echo $default->get_enable_404(); ?>" <?php if ($adH->get_enable_404 () == AI_ENABLED) echo 'checked '; ?> />
2128
- <label for="enable-header-404" title="Enable or disable insertion of this code into HTML page header on page for Error 404: Page not found">Insert on Error 404 page</label>
2129
  </span>
2130
  </div>
2131
  </div>
@@ -2140,7 +2151,7 @@ function generate_settings_form (){
2140
 
2141
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_f'; ?>" value="0" />
2142
  <input type="checkbox" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_f'; ?>" id="enable-footer" value="1" default="<?php echo $default->get_enable_manual(); ?>" <?php if ($adF->get_enable_manual () == AI_ENABLED) echo 'checked '; ?> style="display: none;" />
2143
- <label class="checkbox-button" style="margin-left: 10px;" for="enable-footer" title="Enable or disable insertion of this code into HTML page footer"><span class="checkbox-icon icon-enabled<?php if ($adF->get_enable_manual () == AI_ENABLED) echo ' on'; ?>"></span></label>
2144
 
2145
  <input type="hidden" name="<?php echo AI_OPTION_PROCESS_PHP, '_block_f'; ?>" value="0" />
2146
  <input type="checkbox" name="<?php echo AI_OPTION_PROCESS_PHP, '_block_f'; ?>" value="1" id="process-php-f" default="<?php echo $default->get_process_php (); ?>" <?php if ($adF->get_process_php () == AI_ENABLED) echo 'checked '; ?> style="display: none;" />
@@ -2182,7 +2193,7 @@ function generate_settings_form (){
2182
  <span style="float: right; margin-top: 2px;">
2183
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_404, '_block_f'; ?>" value="0" />
2184
  <input style="margin-left: 10px; margin-top: 1px;" type="checkbox" name="<?php echo AI_OPTION_ENABLE_404, '_block_f'; ?>" id="enable-footer-404" value="1" default="<?php echo $default->get_enable_404(); ?>" <?php if ($adF->get_enable_404 () == AI_ENABLED) echo 'checked '; ?> />
2185
- <label for="enable-footer-404" title="Enable or disable insertion of this code into HTML page footer on page for Error 404: Page not found">Insert on Error 404 page</label>
2186
  </span>
2187
  </div>
2188
  </div>
@@ -2196,7 +2207,7 @@ function generate_settings_form (){
2196
  <div style="float: right;">
2197
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_a'; ?>" value="0" />
2198
  <input type="checkbox" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_a'; ?>" id="enable-adb-detection" value="1" default="<?php echo $default->get_enable_manual(); ?>" <?php if ($adA->get_enable_manual () == AI_ENABLED) echo 'checked '; ?> style="display: none;" />
2199
- <label class="checkbox-button" style="margin-left: 10px;" for="enable-adb-detection" title="Enable or disable detection of ad blocking"><span class="checkbox-icon icon-enabled<?php if ($adA->get_enable_manual () == AI_ENABLED) echo ' on'; ?>"></span></label>
2200
  </div>
2201
 
2202
  <div>
@@ -2347,7 +2358,7 @@ function generate_settings_form (){
2347
  <table class="ai-settings-table" style="width: 100%;">
2348
  <tr>
2349
  <td style="width: 30%;">
2350
- <label for="admin-toolbar-debugging" title="Enable or disable debugging functions in admin toolbar">Debugging functions in admin toolbar</label>
2351
  </td>
2352
  <td>
2353
  <input type="hidden" name="admin_toolbar_debugging" value="0" />
@@ -2812,7 +2823,9 @@ function code_block_list () {
2812
  $manual_shortcode = $obj->get_enable_manual() == AI_ENABLED;
2813
  $manual_php_function = $obj->get_enable_php_call() == AI_ENABLED;
2814
 
2815
- $block_used = $automatic_insertion || $manual_php_function || $manual_shortcode || $manual_widget && !empty ($sidebars_with_widget [$block]);
 
 
2816
 
2817
  if (!$show_all_blocks && !$block_used) continue;
2818
 
@@ -2850,7 +2863,8 @@ function code_block_list () {
2850
  <?php else: ?>
2851
  <td style="min-width: 120px; text-align: left; padding-left: 5px; max-width: 280px; white-space: nowrap; overflow: hidden;"><a href="<?php echo $edit_url; ?>" style="text-decoration: none; box-shadow: 0 0 0;"><?php echo $obj->wp_options [AI_OPTION_BLOCK_NAME]; ?></a></td>
2852
  <?php endif ?>
2853
- <td style="min-width: 180px; text-align: left; padding-left: 10px; max-width: 130px; white-space: nowrap; overflow: hidden; color: <?php echo $automatic_insertion ? '#666' : '#ccc'; ?>"><?php echo $obj->get_automatic_insertion_text(); ?></td>
 
2854
  <td style="min-width: 110px; text-align: left; padding-left: 10px; max-width: 80px; white-space: nowrap; overflow: hidden; color: <?php echo $block_used ? '#444' : '#ccc'; ?>"><?php echo $obj->get_alignment_type_text (); ?></td>
2855
  <td class="ai-dot" style="min-width: 15px; text-align: center; padding-left: 10px; vertical-align: top; color: <?php echo $manual_php_function ? '#8080ff' : '#ddd'; ?>;">&#9679;</td>
2856
  <td class="ai-dot" style="min-width: 15px; text-align: center; padding-left: 10px; vertical-align: top; color: <?php echo $manual_shortcode ? '#ff8b8b' : '#ddd'; ?>;">&#9679;</td>
@@ -2867,7 +2881,8 @@ function code_block_list () {
2867
  <tr>
2868
  <th style="text-align: left;">Block</th>
2869
  <th style="text-align: left; padding-left: 5px;">Name</th>
2870
- <th style="text-align: left; padding-left: 10px;">Automatic Insertion</th>
 
2871
  <th style="text-align: left; padding-left: 10px;">Alignment</th>
2872
  <th style="text-align: center; padding-left: 10px;" title="PHP function call">P</th>
2873
  <th style="text-align: center; padding-left: 10px;" title="Shortcode">S</th>
150
  <button type="button" class="ai-top-button" style="display: none; margin: 0 10px 0 0; width: 62px; outline-color: transparent;" onclick="window.open('https://wordpress.org/support/plugin/ad-inserter')" title="<?php echo AD_INSERTER_NAME; ?> support forum">Support</button>
151
  <button type="button" class="ai-top-button" style="display: none; margin: 0 10px 0 0; width: 62px; outline-color: transparent;" onclick="window.open('https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=LHGZEMRTR7WB4')" title="Support Free Ad Inserter development. If you are making money with Ad Inserter consider donating some small amount. Even 1 dollar counts. Thank you!">Donate</button>
152
  <button type="button" class="ai-top-button" style="display: none; margin: 0 10px 0 0; width: 62px; outline-color: transparent;" onclick="window.open('https://wordpress.org/support/plugin/ad-inserter/reviews/')" title="If you like Ad Inserter and have a moment, please help me spread the word by reviewing the plugin on WordPres">Review</button>
153
+ <button type="button" class="ai-top-button" style="display: none; margin: 0 10px 0 0; width: 62px; outline-color: transparent;" onclick="window.open('http://adinserter.pro/')" title="Need more code blocks, sticky ads, GEO targeting, impression and click tracking? Upgrade to Ad Inserter Pro">Go&nbsp;Pro</button>
154
  <button id="ai-list" type="button" class="ai-top-button" style="width: 62px; display: none; margin-right: 0px; outline-color: transparent;" title="Show list of all code blocks"><span>Blocks</span></button>
155
  </div>
156
 
287
  $manual_php_function [$block] = $obj->get_enable_php_call() == AI_ENABLED;
288
  $manual [$block] = ($manual_widget [$block] && !empty ($sidebars_with_widget [$block])) || $manual_shortcode [$block] || $manual_php_function [$block];
289
 
290
+ $disabled = $obj->get_disable_insertion ();
291
+
292
  $style = "";
293
  $ad_name = "";
294
  $sidebars [$block] = "";
295
+ if (!$disabled) {
296
+ if ($automatic && $manual [$block]) $style = "font-weight: bold; color: #c4f;";
297
+ elseif ($automatic) $style = "font-weight: bold; color: #e44;";
298
+ elseif ($manual [$block]) $style = "font-weight: bold; color: #66f;";
299
+ }
300
 
301
  if (!empty ($sidebars_with_widget [$block])) $sidebars [$block] = implode (", ", $sidebars_with_widget [$block]);
302
 
512
  <label class="checkbox-button" for="process-php-<?php echo $block; ?>" title="Process PHP code in block"><span class="checkbox-icon icon-php<?php if ($obj->get_process_php () == AI_ENABLED) echo ' on'; ?>"></span></label>
513
  </span>
514
  <?php endif; ?>
515
+
516
+ <span class="ai-toolbar-button ai-settings">
517
+ <input type="hidden" name="<?php echo AI_OPTION_DISABLE_INSERTION, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
518
+ <input type="checkbox" name="<?php echo AI_OPTION_DISABLE_INSERTION, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" id="disable-insertion-<?php echo $block; ?>" default="<?php echo $default->get_disable_insertion (); ?>" <?php if ($obj->get_disable_insertion () == AI_ENABLED) echo 'checked '; ?> style="display: none;" />
519
+ <label class="checkbox-button" for="disable-insertion-<?php echo $block; ?>" title="Disable insertion of this block"><span class="checkbox-icon icon-pause<?php if ($obj->get_disable_insertion () == AI_ENABLED) echo ' on'; ?>"></span></label>
520
+ </span>
521
+
522
  <?php if (function_exists ('ai_settings_top_buttons_2')) ai_settings_top_buttons_2 ($block, $obj, $default); ?>
523
  </div>
524
 
950
  <tr>
951
  <td style="width: 70%; padding-bottom: 5px;">
952
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_POSTS, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
953
+ <input type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_POSTS, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_display_settings_post(); ?>" id="display-posts-<?php echo $block; ?>" title="Enable insertion on posts" <?php if ($obj->get_display_settings_post()==AI_ENABLED) echo 'checked '; ?> />
954
 
955
  <select style="margin: 0 0 0 10px;" id="enabled-on-which-posts-<?php echo $block; ?>" name="<?php echo AI_OPTION_ENABLED_ON_WHICH_POSTS, WP_FORM_FIELD_POSTFIX, $block; ?>" default="<?php echo $default->get_ad_enabled_on_which_posts(); ?>" style="width:160px" title="Individual post exceptions (if enabled here) can be configured in post editor. Leave blank for no individual post exceptions.">
956
  <option value="<?php echo AI_NO_INDIVIDUAL_EXCEPTIONS; ?>" <?php echo ($obj->get_ad_enabled_on_which_posts()==AI_NO_INDIVIDUAL_EXCEPTIONS) ? AD_SELECT_SELECTED : AD_EMPTY_VALUE; ?>><?php echo AI_TEXT_NO_INDIVIDUAL_EXCEPTIONS; ?></option>
973
  </td>
974
  <td style="padding-left: 8px;">
975
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_HOMEPAGE, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
976
+ <input id= "display-homepage-<?php echo $block; ?>" style="margin-left: 10px;" type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_HOMEPAGE, WP_FORM_FIELD_POSTFIX, $block; ?>" title="Enable insertion on homepage: latest posts (including sub-pages), static page or theme homepage" value="1" default="<?php echo $default->get_display_settings_home(); ?>" <?php if ($obj->get_display_settings_home()==AI_ENABLED) echo 'checked '; ?> />
977
+ <label for="display-homepage-<?php echo $block; ?>" title="Enable insertion on homepage: latest posts (including sub-pages), static page or theme homepage">Homepage</label>
978
  </td>
979
  <td style="padding-left: 8px;">
980
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_CATEGORY_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
981
+ <input id= "display-category-<?php echo $block; ?>" style="margin-left: 10px;" type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_CATEGORY_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" title="Enable insertion on category blog pages (including sub-pages)" value="1" default="<?php echo $default->get_display_settings_category(); ?>" <?php if ($obj->get_display_settings_category()==AI_ENABLED) echo 'checked '; ?> />
982
+ <label for="display-category-<?php echo $block; ?>" title="Enable insertion on category blog pages (including sub-pages)">Category pages</label>
983
  </td>
984
  </tr>
985
 
986
  <tr>
987
  <td style="width: 70%">
988
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
989
+ <input type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_display_settings_page(); ?>" id="display-pages-<?php echo $block; ?>" title="Enable insertion on static pages" <?php if ($obj->get_display_settings_page()==AI_ENABLED) echo 'checked '; ?> />
990
 
991
  <select style="margin: 0 0 0 10px;" id="enabled-on-which-pages-<?php echo $block; ?>" name="<?php echo AI_OPTION_ENABLED_ON_WHICH_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" default="<?php echo $default->get_ad_enabled_on_which_pages(); ?>" style="width:160px" title="Individual static page exceptions (if enabled here) can be configured in page editor. Leave blank for no individual page exceptions.">
992
  <option value="<?php echo AI_NO_INDIVIDUAL_EXCEPTIONS; ?>" <?php echo ($obj->get_ad_enabled_on_which_pages()==AI_NO_INDIVIDUAL_EXCEPTIONS) ? AD_SELECT_SELECTED : AD_EMPTY_VALUE; ?>><?php echo AI_TEXT_NO_INDIVIDUAL_EXCEPTIONS; ?></option>
1000
  </td>
1001
  <td style="padding-left: 8px;">
1002
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_SEARCH_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1003
+ <input id= "display-search-<?php echo $block; ?>" style="margin-left: 10px;" type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_SEARCH_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" title="Enable insertion on search blog pages" value="1" default="<?php echo $default->get_display_settings_search(); ?>" <?php if ($obj->get_display_settings_search()==AI_ENABLED) echo 'checked '; ?> />
1004
+ <label for="display-search-<?php echo $block; ?>" title="Enable insertion on search blog pages">Search pages</label>
1005
  </td>
1006
  <td style="padding-left: 8px;">
1007
  <input type="hidden" name="<?php echo AI_OPTION_DISPLAY_ON_ARCHIVE_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1008
+ <input id= "display-archive-<?php echo $block; ?>" style="margin-left: 10px;" type="checkbox" name="<?php echo AI_OPTION_DISPLAY_ON_ARCHIVE_PAGES, WP_FORM_FIELD_POSTFIX, $block; ?>" title="Enable insertion on tag or archive blog pages" value="1" default="<?php echo $default->get_display_settings_archive(); ?>" <?php if ($obj->get_display_settings_archive()==AI_ENABLED) echo 'checked '; ?> />
1009
+ <label for="display-archive-<?php echo $block; ?>" title="Enable insertion on tag or archive blog pages">Tag / Archive pages</label>
1010
  </td>
1011
  </tr>
1012
  </table>
1486
  <td style="padding: 4px 10px 4px 0;">
1487
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_WIDGET, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1488
  <input id="enable-widget-<?php echo $block; ?>" type="checkbox" name="<?php echo AI_OPTION_ENABLE_WIDGET, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_widget(); ?>" <?php if ($obj->get_enable_widget () == AI_ENABLED) echo 'checked '; ?> />
1489
+ <label for="enable-widget-<?php echo $block; ?>" title="Enable widget for this block">
1490
  Widget
1491
  </label>
1492
  </td>
1498
  <td style="padding: 4px 10px 4px 0;">
1499
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_MANUAL, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1500
  <input type="checkbox" id="enable-shortcode-<?php echo $block; ?>" name="<?php echo AI_OPTION_ENABLE_MANUAL, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_manual(); ?>" <?php if ($obj->get_enable_manual () == AI_ENABLED) echo 'checked '; ?> />
1501
+ <label for="enable-shortcode-<?php echo $block; ?>" title="Enable shortcode for manual insertion of this block in posts and pages">
1502
  Shortcode
1503
  </label>
1504
  </td>
1514
  <td style="padding: 4px 10px 4px 0;">
1515
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_PHP_CALL, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1516
  <input id="enable-php-call-<?php echo $block; ?>" type="checkbox" name="<?php echo AI_OPTION_ENABLE_PHP_CALL, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_php_call(); ?>" <?php if ($manual_php_function [$block] == AI_ENABLED) echo 'checked '; ?> />
1517
+ <label for="enable-php-call-<?php echo $block; ?>" title="Enable PHP function call to insert this block at any position in template file. If function is disabled for block it will return empty string.">
1518
  PHP function
1519
  </label>
1520
  </td>
1641
  <td>
1642
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_AJAX, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1643
  <input style="margin-left: 10px;" id="enable-ajax-<?php echo $block; ?>" type="checkbox" name="<?php echo AI_OPTION_ENABLE_AJAX, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_ajax(); ?>" <?php if ($obj->get_enable_ajax () == AI_ENABLED) echo 'checked '; ?> />
1644
+ <label for="enable-ajax-<?php echo $block; ?>" title="Enable insertion for Ajax requests">Ajax requests</label>
1645
  </td>
1646
  <td>
1647
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_FEED, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1648
  <input style="margin-left: 10px;" id="enable-feed-<?php echo $block; ?>" type="checkbox" name="<?php echo AI_OPTION_ENABLE_FEED, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_feed(); ?>" <?php if ($obj->get_enable_feed () == AI_ENABLED) echo 'checked '; ?> />
1649
+ <label for="enable-feed-<?php echo $block; ?>" title="Enable insertion in RSS feeds">RSS Feed</label>
1650
  </td>
1651
  <td>
1652
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_404, WP_FORM_FIELD_POSTFIX, $block; ?>" value="0" />
1653
  <input style="margin-left: 10px;" id="enable-404-<?php echo $block; ?>" type="checkbox" name="<?php echo AI_OPTION_ENABLE_404, WP_FORM_FIELD_POSTFIX, $block; ?>" value="1" default="<?php echo $default->get_enable_404(); ?>" <?php if ($obj->get_enable_404 () == AI_ENABLED) echo 'checked '; ?> />
1654
+ <label for="enable-404-<?php echo $block; ?>" title="Enable insertion on page for Error 404: Page not found">Error 404 page</label>
1655
  </td>
1656
  <td>
1657
  </tr>
2056
  <tr>
2057
  <td style="padding: 0 0 2px 0;">
2058
  <input type="hidden" name="hook-enabled-<?php echo $hook; ?>" value="0" />
2059
+ <input type="checkbox" name="hook-enabled-<?php echo $hook; ?>" value="1" default="<?php echo AI_DISABLED; ?>" id="hook-enabled-<?php echo $hook; ?>" title="Enable hook" <?php if (get_hook_enabled ($hook) == AI_ENABLED) echo 'checked '; ?> />
2060
  </td>
2061
  <td style="white-space: nowrap;">
2062
+ <label for="hook-enabled-<?php echo $hook; ?>" title="Enable hook">Hook <?php echo $hook; ?> name</label>
2063
  </td>
2064
  <td style="width: 25%;">
2065
  <input style="width: 100%;" title="Hook name for automatic insertion selection" type="text" name="hook-name-<?php echo $hook; ?>" default="" value="<?php echo get_hook_name ($hook); ?>" size="30" maxlength="80" />
2094
 
2095
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_h'; ?>" value="0" />
2096
  <input type="checkbox" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_h'; ?>" id="enable-header" value="1" default="<?php echo $default->get_enable_manual(); ?>" <?php if ($adH->get_enable_manual () == AI_ENABLED) echo 'checked '; ?> style="display: none;" />
2097
+ <label class="checkbox-button" style="margin-left: 10px;" for="enable-header" title="Enable insertion of this code into HTML page header"><span class="checkbox-icon icon-enabled<?php if ($adH->get_enable_manual () == AI_ENABLED) echo ' on'; ?>"></span></label>
2098
 
2099
  <input type="hidden" name="<?php echo AI_OPTION_PROCESS_PHP, '_block_h'; ?>" value="0" />
2100
  <input type="checkbox" name="<?php echo AI_OPTION_PROCESS_PHP, '_block_h'; ?>" value="1" id="process-php-h" default="<?php echo $default->get_process_php (); ?>" <?php if ($adH->get_process_php () == AI_ENABLED) echo 'checked '; ?> style="display: none;" />
2136
  <span style="float: right; margin-top: 2px;">
2137
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_404, '_block_h'; ?>" value="0" />
2138
  <input style="margin-left: 10px; margin-top: 1px;" type="checkbox" name="<?php echo AI_OPTION_ENABLE_404, '_block_h'; ?>" id="enable-header-404" value="1" default="<?php echo $default->get_enable_404(); ?>" <?php if ($adH->get_enable_404 () == AI_ENABLED) echo 'checked '; ?> />
2139
+ <label for="enable-header-404" title="Enable insertion of this code into HTML page header on page for Error 404: Page not found">Insert on Error 404 page</label>
2140
  </span>
2141
  </div>
2142
  </div>
2151
 
2152
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_f'; ?>" value="0" />
2153
  <input type="checkbox" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_f'; ?>" id="enable-footer" value="1" default="<?php echo $default->get_enable_manual(); ?>" <?php if ($adF->get_enable_manual () == AI_ENABLED) echo 'checked '; ?> style="display: none;" />
2154
+ <label class="checkbox-button" style="margin-left: 10px;" for="enable-footer" title="Enable insertion of this code into HTML page footer"><span class="checkbox-icon icon-enabled<?php if ($adF->get_enable_manual () == AI_ENABLED) echo ' on'; ?>"></span></label>
2155
 
2156
  <input type="hidden" name="<?php echo AI_OPTION_PROCESS_PHP, '_block_f'; ?>" value="0" />
2157
  <input type="checkbox" name="<?php echo AI_OPTION_PROCESS_PHP, '_block_f'; ?>" value="1" id="process-php-f" default="<?php echo $default->get_process_php (); ?>" <?php if ($adF->get_process_php () == AI_ENABLED) echo 'checked '; ?> style="display: none;" />
2193
  <span style="float: right; margin-top: 2px;">
2194
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_404, '_block_f'; ?>" value="0" />
2195
  <input style="margin-left: 10px; margin-top: 1px;" type="checkbox" name="<?php echo AI_OPTION_ENABLE_404, '_block_f'; ?>" id="enable-footer-404" value="1" default="<?php echo $default->get_enable_404(); ?>" <?php if ($adF->get_enable_404 () == AI_ENABLED) echo 'checked '; ?> />
2196
+ <label for="enable-footer-404" title="Enable insertion of this code into HTML page footer on page for Error 404: Page not found">Insert on Error 404 page</label>
2197
  </span>
2198
  </div>
2199
  </div>
2207
  <div style="float: right;">
2208
  <input type="hidden" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_a'; ?>" value="0" />
2209
  <input type="checkbox" name="<?php echo AI_OPTION_ENABLE_MANUAL, '_block_a'; ?>" id="enable-adb-detection" value="1" default="<?php echo $default->get_enable_manual(); ?>" <?php if ($adA->get_enable_manual () == AI_ENABLED) echo 'checked '; ?> style="display: none;" />
2210
+ <label class="checkbox-button" style="margin-left: 10px;" for="enable-adb-detection" title="Enable detection of ad blocking"><span class="checkbox-icon icon-enabled<?php if ($adA->get_enable_manual () == AI_ENABLED) echo ' on'; ?>"></span></label>
2211
  </div>
2212
 
2213
  <div>
2358
  <table class="ai-settings-table" style="width: 100%;">
2359
  <tr>
2360
  <td style="width: 30%;">
2361
+ <label for="admin-toolbar-debugging" title="Enable debugging functions in admin toolbar">Debugging functions in admin toolbar</label>
2362
  </td>
2363
  <td>
2364
  <input type="hidden" name="admin_toolbar_debugging" value="0" />
2823
  $manual_shortcode = $obj->get_enable_manual() == AI_ENABLED;
2824
  $manual_php_function = $obj->get_enable_php_call() == AI_ENABLED;
2825
 
2826
+ $disabled = $obj->get_disable_insertion ();
2827
+
2828
+ $block_used = !$disabled && $automatic_insertion || $manual_php_function || $manual_shortcode || $manual_widget && !empty ($sidebars_with_widget [$block]);
2829
 
2830
  if (!$show_all_blocks && !$block_used) continue;
2831
 
2863
  <?php else: ?>
2864
  <td style="min-width: 120px; text-align: left; padding-left: 5px; max-width: 280px; white-space: nowrap; overflow: hidden;"><a href="<?php echo $edit_url; ?>" style="text-decoration: none; box-shadow: 0 0 0;"><?php echo $obj->wp_options [AI_OPTION_BLOCK_NAME]; ?></a></td>
2865
  <?php endif ?>
2866
+ <td style="min-width: 15px; text-align: center; padding-left: 10px; vertical-align: top; color: #f00;" title="Insertion disabled"><?php echo $disabled ? '&#10074;&#10074;' : ''; ?></td>
2867
+ <td style="min-width: 180px; text-align: left; padding-left: 5px; max-width: 130px; white-space: nowrap; overflow: hidden; color: <?php echo $automatic_insertion ? '#666' : '#ccc'; ?>"><?php echo $obj->get_automatic_insertion_text(); ?></td>
2868
  <td style="min-width: 110px; text-align: left; padding-left: 10px; max-width: 80px; white-space: nowrap; overflow: hidden; color: <?php echo $block_used ? '#444' : '#ccc'; ?>"><?php echo $obj->get_alignment_type_text (); ?></td>
2869
  <td class="ai-dot" style="min-width: 15px; text-align: center; padding-left: 10px; vertical-align: top; color: <?php echo $manual_php_function ? '#8080ff' : '#ddd'; ?>;">&#9679;</td>
2870
  <td class="ai-dot" style="min-width: 15px; text-align: center; padding-left: 10px; vertical-align: top; color: <?php echo $manual_shortcode ? '#ff8b8b' : '#ddd'; ?>;">&#9679;</td>
2881
  <tr>
2882
  <th style="text-align: left;">Block</th>
2883
  <th style="text-align: left; padding-left: 5px;">Name</th>
2884
+ <th style="text-align: left; padding-left: 10px;"></th>
2885
+ <th style="text-align: left; padding-left: 5px;">Automatic Insertion</th>
2886
  <th style="text-align: left; padding-left: 10px;">Alignment</th>
2887
  <th style="text-align: center; padding-left: 10px;" title="PHP function call">P</th>
2888
  <th style="text-align: center; padding-left: 10px;" title="Shortcode">S</th>