WP External Links (nofollow new tab seo) - Version 1.52

Version Description

= * Added filter hook wpel_internal_link * Fixed use_js option bug * Fixed bug loading non-existing stylesheet * Minified javascripts

Download this release

Release Info

Developer freelancephp
Plugin Icon 128x128 WP External Links (nofollow new tab seo)
Version 1.52
Comparing to
See all releases

Code changes from version 1.51 to 1.52

css/admin-wp-external-links.css DELETED
@@ -1,37 +0,0 @@
1
- /* Admin - WP External Links Plugin */
2
- .tooltip-help { text-decoration: none; }
3
-
4
- /* Tipsy Plugin */
5
- .tipsy {
6
- padding: 5px;
7
- /*
8
- opacity: 0.9;
9
- filter: alpha(opacity=90);
10
- background-repeat: no-repeat;
11
- background-image: url(../images/tipsy.gif);
12
- */
13
- }
14
- .tipsy-inner {
15
- padding: 5px 8px 4px 8px;
16
- color: white;
17
- max-width: 200px;
18
- text-align: center;
19
- text-shadow: 0 -1px 0 #333;
20
- /* background-position: top right;*/
21
- border-top:1px solid #808080;
22
- border-botom:1px solid #6d6d6d;
23
- -webkit-border-radius: 3px;
24
- -moz-border-radius: 3px;
25
- border-radius: 3px;
26
- background-color:#777;
27
- background-image:-ms-linear-gradient(bottom,#6d6d6d,#808080);
28
- background-image:-moz-linear-gradient(bottom,#6d6d6d,#808080);
29
- background-image:-o-linear-gradient(bottom,#6d6d6d,#808080);
30
- background-image:-webkit-gradient(linear,left bottom,left top,from(#6d6d6d),to(#808080));
31
- background-image:-webkit-linear-gradient(bottom,#6d6d6d,#808080);
32
- background-image:linear-gradient(bottom,#6d6d6d,#808080);
33
- }
34
- .tipsy-north { background-position: top center; }
35
- .tipsy-south { background-position: bottom center; }
36
- .tipsy-east { background-position: right center; }
37
- .tipsy-west { background-position: center bottom; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/class-admin-external-links.php CHANGED
@@ -1,639 +1,659 @@
1
- <?php defined( 'ABSPATH' ) OR die( 'No direct access.' );
2
- if ( ! class_exists( 'Admin_External_Links' ) ):
3
-
4
- /**
5
- * Class Admin_External_Links
6
- * @category WordPress Plugins
7
- */
8
- final class Admin_External_Links {
9
-
10
- /**
11
- * Options to be saved and their default values
12
- * @var array
13
- */
14
- public $save_options = array(
15
- 'meta' => array(
16
- 'version' => NULL,
17
- ),
18
- 'main' => array(
19
- 'target' => '_none',
20
- 'use_js' => 1,
21
- 'filter_page' => 1,
22
- 'filter_posts' => 1,
23
- 'filter_comments' => 1,
24
- 'filter_widgets' => 1,
25
- 'ignore' => 'http://twitter.com/share',
26
- ),
27
- 'seo' => array(
28
- 'external' => 1,
29
- 'nofollow' => 1,
30
- 'title' => '%title%',
31
- 'load_in_footer' => 1,
32
- ),
33
- 'style' => array(
34
- 'class_name' => 'ext-link',
35
- 'icon' => 0,
36
- 'image_no_icon' => 1,
37
- 'no_icon_class' => 'no-ext-icon',
38
- 'no_icon_same_window' => 0,
39
- ),
40
- 'extra' => array(
41
- 'fix_js' => 0,
42
- 'phpquery' => 0,
43
- 'filter_excl_sel' => '.excl-ext-link',
44
- ),
45
- 'screen' => array(
46
- 'menu_position' => NULL,
47
- ),
48
- );
49
-
50
- /**
51
- * Meta box page object
52
- * @var WP_Meta_Box_Page
53
- */
54
- public $meta_box_page = NULL;
55
-
56
- /**
57
- * Ajax form object
58
- * @var WP_Ajax_Option_Form
59
- */
60
- public $form = NULL;
61
- static public $staticForm = NULL;
62
-
63
-
64
- /**
65
- * Constructor
66
- */
67
- public function __construct() {
68
- $this->check_version_update();
69
-
70
- // set meta box page
71
- $this->meta_box_page = new WP_Meta_Box_Page_01();
72
-
73
- // set ajax forms (also used by front-end)
74
- $this->form = new WP_Option_Forms_01( WP_EXTERNAL_LINKS_KEY, $this->save_options );
75
- self::$staticForm = $this->form;
76
-
77
- // init admin
78
- add_action( 'admin_init', array( $this, 'admin_init' ) );
79
-
80
- if ( is_admin() ) {
81
- // set options for add_page_method
82
- $menu_pos = $this->form->set_current_option( 'screen' )->value( 'menu_position' );
83
-
84
- // init meta box page
85
- $this->meta_box_page->init(
86
- // settings
87
- array(
88
- 'page_title' => $this->__( 'WP External Links' ),
89
- 'menu_title' => $this->__( 'External Links' ),
90
- 'page_slug' => strtolower( WP_EXTERNAL_LINKS_KEY ),
91
- 'add_page_method' => ( ! empty( $menu_pos ) AND $menu_pos != 'admin.php' ) ? 'add_submenu_page' : 'add_menu_page',
92
- 'parent_slug' => ( ! empty( $menu_pos ) AND $menu_pos != 'admin.php' ) ? $menu_pos : NULL,
93
- 'column_widths' => array(
94
- 1 => array( 99 ),
95
- 2 => array( 69, 29 ),
96
- ),
97
- 'icon_url' => plugins_url( 'images/icon-wp-external-links-16.png', WP_EXTERNAL_LINKS_FILE ),
98
- ),
99
- // load callback
100
- array( $this, 'call_load_meta_box' )
101
- );
102
- }
103
- }
104
-
105
- /**
106
- * Initialize Admin
107
- */
108
- public function admin_init() {
109
- // set uninstall hook
110
- register_uninstall_hook( WP_EXTERNAL_LINKS_FILE, array( 'Admin_External_Links', 'call_uninstall' ) );
111
-
112
- // load text domain for translations
113
- load_plugin_textdomain( WP_EXTERNAL_LINKS_KEY, FALSE, dirname( plugin_basename( WP_EXTERNAL_LINKS_FILE ) ) . '/lang/' );
114
- }
115
-
116
- /**
117
- * Translate text in current domain
118
- * @param string $text
119
- * @return string
120
- */
121
- public function __( $text ) {
122
- return translate( $text, WP_EXTERNAL_LINKS_KEY );
123
- }
124
-
125
- /**
126
- * Translate text in current domain
127
- * @param string $text
128
- * @return string
129
- */
130
- public function _e( $text ) {
131
- echo translate( $text, WP_EXTERNAL_LINKS_KEY );
132
- }
133
-
134
- /**
135
- * Load meta box action
136
- */
137
- public function call_load_meta_box( $meta_box ) {
138
- // add filters
139
- $meta_box->add_title_filter( array( $this, 'call_page_title' ) )
140
- ->add_contextual_help_filter( array( $this, 'call_contextual_help' ) );
141
-
142
- // add meta boxes
143
- // add_meta_box( $title, $callback, $context = 'normal', $id = NULL, $priority = 'default', $callback_args = NULL )
144
- $meta_box->add_meta_box( $this->__( 'General Settings' ), array( $this, 'call_box_general_settings' ), 1 )
145
- ->add_meta_box( $this->__( 'SEO Settings' ), array( $this, 'call_box_seo_settings' ), 1 )
146
- ->add_meta_box( $this->__( 'Style Settings' ), array( $this, 'call_box_style_settings' ), 1 )
147
- ->add_meta_box( $this->__( 'Extra Settings' ), array( $this, 'call_box_extra_settings' ), 1 )
148
- ->add_meta_box( $this->__( 'Admin Settings' ), array( $this, 'call_box_admin_settings' ), 1 )
149
- //->add_meta_box( $this->__( 'About this Plugin' ), array( $this, 'call_box_about' ), 2 )
150
- ->add_meta_box( $this->__( 'Other Plugins' ), array( $this, 'call_box_other_plugins' ), 2 );
151
-
152
- // stylesheets
153
- wp_enqueue_style( 'wp-external-links', plugins_url( 'css/wp-external-links.css', WP_EXTERNAL_LINKS_FILE ), FALSE, WP_EXTERNAL_LINKS_VERSION );
154
- wp_enqueue_style( 'admin-wp-external-links', plugins_url( 'css/admin-wp-external-links.css', WP_EXTERNAL_LINKS_FILE ), FALSE, WP_EXTERNAL_LINKS_VERSION );
155
-
156
- // scripts
157
- wp_enqueue_script( 'admin-wp-external-links', plugins_url( '/js/admin-wp-external-links.js', WP_EXTERNAL_LINKS_FILE ), array( 'postbox', 'option-forms' ), WP_EXTERNAL_LINKS_VERSION );
158
- }
159
-
160
- /**
161
- * Contextual_help (callback)
162
- * @param string $content
163
- * @return string
164
- */
165
- public function call_contextual_help( $content ) {
166
- $help = '';
167
- $help .= $this->meta_box_page->get_ob_callback( array( $this, 'call_box_about' ) );
168
- return $help . $content;
169
- }
170
-
171
- /**
172
- * Add icon to page title
173
- * @return string
174
- */
175
- public function call_page_title( $title ) {
176
- // when updated set the update message
177
- if ( isset($_GET[ 'settings-updated' ]) && $_GET[ 'settings-updated' ] == 'true' ) {
178
- $title .= '<div class="updated settings-error" id="setting-error-settings_updated">'
179
- . '<p><strong>' . __( 'Settings saved.' ) .'</strong></p>'
180
- . '</div>';
181
- }
182
-
183
- $title = '<div class="icon32" id="icon-options-custom" style="background:url( '. plugins_url( 'images/icon-wp-external-links-32.png', WP_EXTERNAL_LINKS_FILE ) .' ) no-repeat 50% 50%"><br></div>'
184
- . $title;
185
-
186
- return $title;
187
- }
188
-
189
- /**
190
- * Meta Box: General Settings
191
- */
192
- public function call_box_general_settings() {
193
- echo $this->form->set_current_option( 'main' )->open_form();
194
- ?>
195
- <fieldset class="options">
196
- <table class="form-table">
197
- <tr>
198
- <th style="width:250px;"><?php $this->_e( 'Open external links in...' ) ?>
199
- <?php echo $this->tooltip_help( 'Specify the target (window or tab) for opening external links.' ) ?></th>
200
- <td class="target_external_links">
201
- <label><?php echo $this->form->radio( 'target', '_none', array( 'class' => 'field_target' ) ); ?>
202
- <span><?php $this->_e( 'Same window or tab (<code>_none</code>)' ) ?></span></label>
203
- <?php echo $this->tooltip_help( 'Open in current window or tab, when framed in the same frame.' ) ?>
204
- <br/>
205
- <label><?php echo $this->form->radio( 'target', '_blank', array( 'class' => 'field_target' ) ); ?>
206
- <span><?php $this->_e( 'New window or tab (<code>_blank</code>)' ) ?></span></label>
207
- <?php echo $this->tooltip_help( 'Open every external link in a new window or tab.' ) ?>
208
- <br style="margin-bottom:15px;" />
209
- <label><?php echo $this->form->radio( 'target', '_top', array( 'class' => 'field_target' ) ); ?>
210
- <span><?php $this->_e( 'Topmost frame (<code>_top</code>)' ) ?></span></label>
211
- <?php echo $this->tooltip_help( 'Open in current window or tab, when framed in the topmost frame.' ) ?>
212
- <br/>
213
- <label><?php echo $this->form->radio( 'target', '_new', array( 'class' => 'field_target' ) ); ?>
214
- <span><?php $this->_e( 'Seperate window or tab (<code>_new</code>)' ) ?></span></label>
215
- <?php echo $this->tooltip_help( 'Open new window the first time and use this window for each external link.' ) ?>
216
- </td>
217
- </tr>
218
- <tr>
219
- <th style="width:250px;"><?php $this->_e( 'Apply plugin settings on...' ) ?>
220
- <?php echo $this->tooltip_help( 'Choose contents for applying settings to external links.' ) ?></th>
221
- <td>
222
- <label><?php echo $this->form->checkbox( 'filter_page', 1 ); ?>
223
- <span><?php $this->_e( 'All contents' ) ?></span> <span class="description"><?php $this->_e('(the whole <code>&lt;body&gt;</code>)') ?></span></label>
224
- <br/>&nbsp;&nbsp;<label><?php echo $this->form->checkbox( 'filter_posts', 1 ); ?>
225
- <span><?php $this->_e( 'Post contents' ) ?></span></label>
226
- <br/>&nbsp;&nbsp;<label><?php echo $this->form->checkbox( 'filter_comments', 1 ); ?>
227
- <span><?php $this->_e( 'Comments' ) ?></span></label>
228
- <br/>&nbsp;&nbsp;<label><?php echo $this->form->checkbox( 'filter_widgets', 1 ); ?>
229
- <span><?php
230
- if ( self::check_widget_content_filter() ):
231
- $this->_e( 'All widgets' );
232
- echo $this->tooltip_help( 'Applied to all widgets by using the "widget_content" filter of the Widget Logic plugin' );
233
- else:
234
- $this->_e( 'All text widgets' );
235
- echo $this->tooltip_help( 'Only the text widget will be applied. To apply to all widget you should select "All contents" option.' );
236
- endif;
237
- ?></span></label>
238
- </td>
239
- </tr>
240
- <tr>
241
- <th><?php $this->_e( 'Ignore links (URL) containing...' ) ?>
242
- <?php echo $this->tooltip_help( 'This plugin will completely ignore links that contain one of the given texts in the URL. Use enter to seperate each text. This check is not case sensitive.' ) ?></th>
243
- <td><label><?php echo $this->form->textarea( 'ignore' ); ?>
244
- <span class="description"><?php _e( 'Be as specific as you want, f.e.: <code>twitter.com</code> or <code>https://twitter.com</code>. Seperate each by an enter.' ) ?></span></label>
245
- </td>
246
- </tr>
247
- </table>
248
- </fieldset>
249
-
250
- <?php
251
- echo $this->form->submit();
252
- echo $this->form->close_form();
253
- }
254
-
255
- /**
256
- * Meta Box: SEO Settings
257
- */
258
- public function call_box_seo_settings() {
259
- echo $this->form->set_current_option( 'seo' )->open_form();
260
- ?>
261
- <fieldset class="options">
262
- <table class="form-table">
263
- <tr>
264
- <th style="width:250px;"><?php $this->_e( 'Add to <code>rel</code>-attribute' ) ?>
265
- <?php echo $this->tooltip_help( 'Set values for the "rel"-atribute of external links.' ) ?></th>
266
- <td><label><?php echo $this->form->checkbox( 'nofollow', 1 ); ?>
267
- <span><?php $this->_e( 'Add <code>"nofollow"</code>' ) ?></span></label>
268
- <?php echo $this->tooltip_help( 'Add "nofollow" to the "rel"-attribute of external links (unless link already has "follow").' ) ?>
269
- <br/>
270
- <label><?php echo $this->form->checkbox( 'external', 1 ); ?>
271
- <span><?php $this->_e( 'Add <code>"external"</code>' ) ?></span></label>
272
- <?php echo $this->tooltip_help( 'Add "external" to the "rel"-attribute of external links.' ) ?>
273
- </td>
274
- </tr>
275
- <tr>
276
- <th><?php $this->_e( 'Set <code>title</code>-attribute' ) ?>
277
- <?php echo $this->tooltip_help( 'Set title attribute for external links. Use %title% for the original title value.' ) ?></th>
278
- <td><label><?php echo $this->form->text( 'title' ); ?>
279
- <br/><span class="description"><?php _e( 'Use <code>%title%</code> for the original title value.' ) ?></span></label></td>
280
- </tr>
281
- <tr>
282
- <th><?php $this->_e( 'Use JavaScript method' ) ?>
283
- <?php echo $this->tooltip_help( 'Enable this option to use the JavaScript method for opening links, which prevents adding target attribute in the HTML code.' ) ?></label>
284
- </th>
285
- <td>
286
- <label><?php echo $this->form->checkbox( 'use_js', 1, array( 'class' => 'field_use_js' ) ); ?>
287
- <span><?php $this->_e( 'Use JavaScript for opening links' ) ?></span> <span class="description"><?php $this->_e( '(valid xhtml strict)' ) ?></span>
288
- <br/>
289
- &nbsp;&nbsp;<label><?php echo $this->form->checkbox( 'load_in_footer', 1, array( 'class' => 'load_in_footer' ) ); ?>
290
- <span><?php $this->_e( 'Load JS file in footer' ) ?></span>
291
- </td>
292
- </tr>
293
- </table>
294
- </fieldset>
295
- <?php
296
- echo $this->form->submit();
297
- echo $this->form->close_form();
298
- }
299
-
300
- /**
301
- * Meta Box: Style Settings
302
- */
303
- public function call_box_style_settings() {
304
- echo $this->form->set_current_option( 'style' )->open_form();
305
- ?>
306
- <fieldset class="options">
307
- <table class="form-table">
308
- <tr>
309
- <th style="width:250px;"><?php $this->_e( 'Set icon for external link' ) ?>
310
- <?php echo $this->tooltip_help( 'Set an icon that wll be shown for external links. See example on the right side.' ) ?></th>
311
- <td>
312
- <div>
313
- <div style="width:15%;float:left">
314
- <label><?php echo $this->form->radio( 'icon', 0 ); ?>
315
- <span><?php $this->_e( 'No icon' ) ?></span></label>
316
- <?php for ( $x = 1; $x <= 20; $x++ ): ?>
317
- <br/>
318
- <label title="<?php echo sprintf( $this->__( 'Icon %1$s: choose this icon to show for all external links or add the class \'ext-icon-%1$s\' to a specific link.' ), $x ) ?>">
319
- <?php echo $this->form->radio( 'icon', $x ); ?>
320
- <img src="<?php echo plugins_url('images/ext-icons/ext-icon-'. $x .'.png', WP_EXTERNAL_LINKS_FILE) ?>" /></label>
321
- <?php if ( $x % 5 == 0 ): ?>
322
- </div>
323
- <div style="width:15%;float:left">
324
- <?php endif; ?>
325
- <?php endfor; ?>
326
- </div>
327
- <div style="width:29%;float:left;"><span class="description"><?php $this->_e( 'Example:' ) ?></span>
328
- <br/><img src="<?php echo plugins_url( 'images/link-icon-example.png', WP_EXTERNAL_LINKS_FILE ) ?>" />
329
- </div>
330
- <br style="clear:both" />
331
- </div>
332
- </td>
333
- </tr>
334
- <tr>
335
- <th><?php $this->_e( 'Skip images' ) ?>
336
- <?php echo $this->tooltip_help( 'Don\'t show icon for external links containing images.' ) ?></th>
337
- <td><label><?php echo $this->form->checkbox( 'image_no_icon', 1 ); ?>
338
- <span><?php $this->_e( 'No icon for extenal links with images' ) ?></span></label>
339
- </td>
340
- </tr>
341
- <tr>
342
- <th style="width:250px;"><?php $this->_e( 'Set no-icon class' ) ?>
343
- <?php echo $this->tooltip_help( 'Set this class for links, that should not have the external link icon.' ) ?></th>
344
- <td><label><?php echo $this->form->text( 'no_icon_class', array( 'class' => '' ) ); ?></label>
345
- <br/><label><?php echo $this->form->checkbox( 'no_icon_same_window', 1 ); ?>
346
- <span><?php $this->_e( 'Always open links with no-icon class in same window or tab' ) ?></span></label>
347
- <?php echo $this->tooltip_help( 'When enabled external links containing the no-icon class will always be opened in the current window or tab. No matter which target is set.' ) ?>
348
- </td>
349
- </tr>
350
- <tr>
351
- <th><?php $this->_e( 'Add to <code>class</code>-attribute' ) ?>
352
- <?php echo $this->tooltip_help( 'Add one or more extra classes to the external links, seperated by a space. It is optional, else just leave field blank.' ) ?></th>
353
- <td><label><?php echo $this->form->text( 'class_name' ); ?></label></td>
354
- </tr>
355
- </table>
356
- </fieldset>
357
- <?php
358
- echo $this->form->submit();
359
- echo $this->form->close_form();
360
- }
361
-
362
- /**
363
- * Meta Box: Extra Settings
364
- */
365
- public function call_box_extra_settings() {
366
- echo $this->form->set_current_option( 'extra' )->open_form();
367
- ?>
368
- <fieldset class="options">
369
- <table class="form-table">
370
- <tr>
371
- <th style="width:250px;"><?php $this->_e( 'Solving problems' ) ?>
372
- <?php echo $this->tooltip_help( 'Some options to try when a problem occurs. These options can also cause other problems, so be carefull.' ) ?></th>
373
- <td><label><?php echo $this->form->checkbox( 'fix_js', 1 ); ?>
374
- <span><?php $this->_e( 'Replacing <code>&lt;/a&gt;</code> with <code>&lt;\/a&gt;</code> in JavaScript code.' ) ?></span></label>
375
- <?php echo $this->tooltip_help( 'By replacing </a> with <\/a> in JavaScript code these tags will not be processed by the plugin.' ) ?>
376
- </td>
377
- </tr>
378
- <tr>
379
- <th style="width:250px;"><?php $this->_e( 'Use phpQuery library' ) ?>
380
- <?php echo $this->tooltip_help( 'Using phpQuery library for manipulating links. This option is experimental.' ) ?></th>
381
- <td><label><?php echo $this->form->checkbox( 'phpquery', 1 ); ?>
382
- <span><?php $this->_e( 'Use phpQuery for parsing document.' ) ?></span>
383
- <span class="description">(Test it first!)</span></label>
384
- </td>
385
- </tr>
386
- <tr class="filter_excl_sel" <?php echo ( $this->form->value( 'phpquery' ) ) ? '' : 'style="display:none;"'; ?>>
387
- <th><?php $this->_e( 'Do NOT apply settings on...' ) ?>
388
- <?php echo $this->tooltip_help( 'The external links of these selection will be excluded for the settings of this plugin. Define the selection by using CSS selectors.' ) ?></th>
389
- <td><label><?php echo $this->form->textarea( 'filter_excl_sel' ); ?>
390
- <span class="description"><?php _e( 'Define selection by using CSS selectors, f.e.: <code>.excl-ext-link, .entry-title, #comments-title</code> (look <a href="http://code.google.com/p/phpquery/wiki/Selectors" target="_blank">here</a> for available selectors).' ) ?></span></label>
391
- </td>
392
- </tr>
393
- </table>
394
- </fieldset>
395
- <?php
396
- echo $this->form->submit();
397
- echo $this->form->close_form();
398
- }
399
-
400
- /**
401
- * Meta Box: Extra Settings
402
- */
403
- public function call_box_admin_settings() {
404
- echo $this->form->set_current_option( 'screen' )->open_form();
405
- ?>
406
- <fieldset class="options">
407
- <table class="form-table">
408
- <tr>
409
- <th><?php $this->_e('Admin menu position') ?>
410
- <?php echo $this->tooltip_help( 'Change the menu position of this plugin in "Screen Options".' ) ?></th>
411
- <td><label>
412
- <?php
413
- echo $this->form->select( 'menu_position', array(
414
- 'admin.php' => 'Main menu',
415
- 'index.php' => $this->__( 'Subitem of Dashboard' ),
416
- 'edit.php' => $this->__( 'Subitem of Posts' ),
417
- 'upload.php' => $this->__( 'Subitem of Media' ),
418
- 'link-manager.php' => $this->__( 'Subitem of Links' ),
419
- 'edit.php?post_type=page' => $this->__( 'Subitem of Pages' ),
420
- 'edit-comments.php' => $this->__( 'Subitem of Comments' ),
421
- 'themes.php' => $this->__( 'Subitem of Appearance' ),
422
- 'plugins.php' => $this->__( 'Subitem of Plugins' ),
423
- 'users.php' => $this->__( 'Subitem of Users' ),
424
- 'tools.php' => $this->__( 'Subitem of Tools' ),
425
- 'options-general.php' => $this->__( 'Subitem of Settings' ),
426
- ));
427
- ?>
428
- </label></td>
429
- </tr>
430
- </table>
431
- </fieldset>
432
- <?php
433
- echo $this->form->submit();
434
- echo $this->form->close_form();
435
- }
436
-
437
- /**
438
- * Meta Box: About...
439
- */
440
- public function call_box_about() {
441
- ?>
442
- <h4><img src="<?php echo plugins_url( 'images/icon-wp-external-links-16.png', WP_EXTERNAL_LINKS_FILE ) ?>" width="16" height="16" /> <?php $this->_e( 'WP External Links' ) ?></h4>
443
- <div>
444
- <p><?php printf( $this->__( 'Current version: <strong>%1$s</strong>' ), WP_EXTERNAL_LINKS_VERSION ) ?></p>
445
- <p><?php $this->_e( 'Manage external links on your site: open in new window/tab, set link icon, add "external", add "nofollow" and more.' ) ?></p>
446
- <p><a href="http://www.freelancephp.net/contact/" target="_blank"><?php $this->_e( 'Questions or suggestions?' ) ?></a></p>
447
- <p><?php $this->_e( 'If you like this plugin please send your rating at WordPress.org.' ) ?></p>
448
- <p><?php _e( 'More info' ) ?>: <a href="http://wordpress.org/extend/plugins/wp-external-links/" target="_blank">WordPress.org</a> | <a href="http://www.freelancephp.net/wp-external-links-plugin/" target="_blank">FreelancePHP.net</a></p>
449
- </div>
450
- <?php
451
- }
452
-
453
- /**
454
- * Meta Box: Other Plugins
455
- */
456
- public function call_box_other_plugins() {
457
- ?>
458
- <h4><img src="<?php echo plugins_url( 'images/icon-email-encoder-bundle-16.png', WP_EXTERNAL_LINKS_FILE ); ?>" width="16" height="16" /> Email Encoder Bundle</h4>
459
- <div>
460
- <?php if ( is_plugin_active( 'email-encoder-bundle/email-encoder-bundle.php' ) ): ?>
461
- <p><?php $this->_e( 'This plugin is already activated.' ) ?> <a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/options-general.php?page=email-encoder-bundle/email-encoder-bundle.php"><?php $this->_e( 'Settings' ) ?></a></p>
462
- <?php elseif( file_exists( WP_PLUGIN_DIR . '/email-encoder-bundle/email-encoder-bundle.php' ) ): ?>
463
- <p><a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/plugins.php?plugin_status=inactive"><?php $this->_e( 'Activate this plugin.' ) ?></a></p>
464
- <?php else: ?>
465
- <p><a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/plugin-install.php?tab=search&type=term&s=Email+Encoder+Bundle+freelancephp&plugin-search-input=Search+Plugins"><?php $this->_e( 'Get this plugin now' ) ?></a></p>
466
- <?php endif; ?>
467
-
468
- <p><?php $this->_e( 'Protect email addresses on your site from spambots and being used for spamming by using one of the encoding methods.' ) ?></p>
469
- <p><?php _e( 'More info' ) ?>: <a href="http://wordpress.org/extend/plugins/email-encoder-bundle/" target="_blank">WordPress.org</a> | <a href="http://www.freelancephp.net/email-encoder-php-class-wp-plugin/" target="_blank">FreelancePHP.net</a></p>
470
- </div>
471
-
472
- <?php echo $this->hr(); ?>
473
-
474
- <h4><img src="<?php echo plugins_url( 'images/icon-wp-mailto-links-16.png', WP_EXTERNAL_LINKS_FILE ); ?>" width="16" height="16" /> WP Mailto Links</h4>
475
- <div>
476
- <?php if ( is_plugin_active( 'wp-mailto-links/wp-mailto-links.php' ) ): ?>
477
- <p><?php $this->_e( 'This plugin is already activated.' ) ?> <a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/options-general.php?page=wp-mailto-links/wp-mailto-links.php"><?php $this->_e( 'Settings' ) ?></a></p>
478
- <?php elseif( file_exists( WP_PLUGIN_DIR . '/wp-mailto-links/wp-mailto-links.php' ) ): ?>
479
- <p><a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/plugins.php?plugin_status=inactive"><?php $this->_e( 'Activate this plugin.' ) ?></a></p>
480
- <?php else: ?>
481
- <p><a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/plugin-install.php?tab=search&type=term&s=WP+Mailto+Links+freelancephp&plugin-search-input=Search+Plugins"><?php $this->_e( 'Get this plugin now' ) ?></a></p>
482
- <?php endif; ?>
483
-
484
- <p><?php $this->_e( 'Manage mailto links on your site and protect emails from spambots, set mail icon and more.' ) ?></p>
485
- <p><?php _e( 'More info' ) ?>: <a href="http://wordpress.org/extend/plugins/wp-mailto-links/" target="_blank">WordPress.org</a> | <a href="http://www.freelancephp.net/wp-mailto-links-plugin/" target="_blank">FreelancePHP.net</a></p>
486
- </div>
487
- <?php
488
- }
489
-
490
- /**
491
- * Activation callback
492
- */
493
- public function check_version_update() {
494
- // check for version
495
- $meta = get_option( 'wp_external_links-meta' );
496
- if ( $meta[ 'version' ] == WP_EXTERNAL_LINKS_VERSION )
497
- return;
498
-
499
- // set new version
500
- $meta[ 'version' ] = WP_EXTERNAL_LINKS_VERSION;
501
- update_option( 'wp_external_links-meta', $meta );
502
-
503
- // check for upgrading saved options to v1.00
504
- $old_options = get_option( 'WP_External_Links_options' );
505
-
506
- if ( ! empty( $old_options ) ) {
507
- $new_options = $this->save_options;
508
-
509
- $new_options[ 'main' ][ 'target' ] = $old_options[ 'target' ];
510
- $new_options[ 'main' ][ 'use_js' ] = $old_options[ 'use_js' ];
511
- $new_options[ 'main' ][ 'filter_page' ] = $old_options[ 'filter_whole_page' ];
512
- $new_options[ 'main' ][ 'filter_posts' ] = $old_options[ 'filter_posts' ];
513
- $new_options[ 'main' ][ 'filter_comments' ] = $old_options[ 'filter_comments' ];
514
- $new_options[ 'main' ][ 'filter_widgets' ] = $old_options[ 'filter_widgets' ];
515
- $new_options[ 'seo' ][ 'external' ] = $old_options[ 'external' ];
516
- $new_options[ 'seo' ][ 'nofollow' ] = $old_options[ 'nofollow' ];
517
- $new_options[ 'style' ][ 'class_name' ] = $old_options[ 'class_name' ];
518
- $new_options[ 'style' ][ 'icon' ] = $old_options[ 'icon' ];
519
- $new_options[ 'style' ][ 'no_icon_class' ] = $old_options[ 'no_icon_class' ];
520
- $new_options[ 'style' ][ 'no_icon_same_window' ] = $old_options[ 'no_icon_same_window' ];
521
-
522
- // save new format option values
523
- update_option( 'wp_external_links-main', $new_options[ 'main' ] );
524
- update_option( 'wp_external_links-seo', $new_options[ 'seo' ] );
525
- update_option( 'wp_external_links-style', $new_options[ 'style' ] );
526
-
527
- // delete old format option values
528
- delete_option( 'WP_External_Links_options' );
529
- }
530
-
531
- // upgrade to v1.20
532
- $upgrade_main = get_option( 'wp_external_links-main' );
533
-
534
- if ( ! isset( $upgrade_main[ 'ignore' ] ) ) {
535
- $upgrade_main[ 'ignore' ] = $this->save_options[ 'main' ][ 'ignore' ];
536
- update_option( 'wp_external_links-main', $upgrade_main );
537
- }
538
-
539
- // upgrade to v1.30
540
- if ( WP_EXTERNAL_LINKS_VERSION == '1.30' ) {
541
- $new_options = $this->save_options;
542
- $general = get_option( 'wp_external_links-general' );
543
- $style = get_option( 'wp_external_links-style' );
544
-
545
- if ( isset( $general[ 'target' ] ) ) $new_options[ 'main' ][ 'target' ] = $general[ 'target' ];
546
- $new_options[ 'main' ][ 'use_js' ] = ( isset( $general[ 'use_js' ] ) ) ? $general[ 'use_js' ] : 0;
547
- $new_options[ 'main' ][ 'filter_page' ] = ( isset( $general[ 'filter_page' ] ) ) ? $general[ 'filter_page' ] : 0;
548
- $new_options[ 'main' ][ 'filter_posts' ] = ( isset( $general[ 'filter_posts' ] ) ) ? $general[ 'filter_posts' ] : 0;
549
- $new_options[ 'main' ][ 'filter_comments' ] = ( isset( $general[ 'filter_comments' ] ) ) ? $general[ 'filter_comments' ] : 0;
550
- $new_options[ 'main' ][ 'filter_widgets' ] = ( isset( $general[ 'filter_widgets' ] ) ) ? $general[ 'filter_widgets' ] : 0;
551
- if ( isset( $general[ 'ignore' ] ) ) $new_options[ 'main' ][ 'ignore' ] = $general[ 'ignore' ];
552
-
553
- $new_options[ 'seo' ][ 'external' ] = ( isset( $general[ 'external' ] ) ) ? $general[ 'external' ] : 0;
554
- $new_options[ 'seo' ][ 'nofollow' ] = ( isset( $general[ 'nofollow' ] ) ) ? $general[ 'nofollow' ] : 0;
555
- if ( isset( $general[ 'title' ] ) ) $new_options[ 'seo' ][ 'title' ] = $general[ 'title' ];
556
-
557
- if ( isset( $general[ 'class_name' ] ) ) $new_options[ 'style' ][ 'class_name' ] = $general[ 'class_name' ];
558
-
559
- if ( isset( $style[ 'icon' ] ) ) $new_options[ 'style' ][ 'icon' ] = $style[ 'icon' ];
560
- if ( isset( $style[ 'no_icon_class' ] ) ) $new_options[ 'style' ][ 'no_icon_class' ] = $style[ 'no_icon_class' ];
561
- $new_options[ 'style' ][ 'no_icon_same_window' ] = ( isset( $style[ 'no_icon_same_window' ] ) ) ? $style[ 'no_icon_same_window' ] : 0;
562
-
563
- $new_options[ 'extra' ][ 'fix_js' ] = ( isset( $general[ 'fix_js' ] ) ) ? $general[ 'fix_js' ] : 0;
564
- $new_options[ 'extra' ][ 'phpquery' ] = ( isset( $general[ 'phpquery' ] ) ) ? $general[ 'phpquery' ] : 0;
565
- if ( isset( $general[ 'filter_excl_sel' ] ) ) $new_options[ 'extra' ][ 'filter_excl_sel' ] = $general[ 'filter_excl_sel' ];
566
-
567
- // save new format option values
568
- update_option( 'wp_external_links-main', $new_options[ 'main' ] );
569
- update_option( 'wp_external_links-seo', $new_options[ 'seo' ] );
570
- update_option( 'wp_external_links-style', $new_options[ 'style' ] );
571
- update_option( 'wp_external_links-extra', $new_options[ 'extra' ] );
572
-
573
- // delete old format
574
- delete_option( 'wp_external_links-general' );
575
- }
576
- }
577
-
578
- /**
579
- * Method for test purpuses
580
- */
581
- public function __options($values = null) {
582
- if (class_exists('Test_WP_Mailto_Links') && constant('WP_DEBUG') === true) {
583
- if ($values !== null) {
584
- $this->set_options($values);
585
- }
586
-
587
- return $this->options;
588
- }
589
- }
590
-
591
- /**
592
- * Uninstall callback
593
- */
594
- static public function call_uninstall() {
595
- self::$staticForm->delete_options();
596
- }
597
-
598
- /**
599
- * Set tooltip help
600
- * @param string $text
601
- * @return string
602
- */
603
- public function tooltip_help( $text ) {
604
- $text = $this->__( $text );
605
- $text = htmlentities( $text );
606
-
607
- $html = '<a href="#" class="tooltip-help" title="'. $text .'"><sup>(?)</sup></a>';
608
- return $html;
609
- }
610
-
611
- /**
612
- * Get html seperator
613
- * @return string
614
- */
615
- protected function hr() {
616
- return '<hr style="border:1px solid #FFF; border-top:1px solid #EEE;" />';
617
- }
618
-
619
-
620
- /**
621
- * Check if widget_content filter is available (Widget Logic Plugin)
622
- * @return boolean
623
- * @static
624
- */
625
- public static function check_widget_content_filter() {
626
- // set widget_content filter of Widget Logic plugin
627
- $widget_logic_opts = get_option( 'widget_logic' );
628
-
629
- if ( function_exists( 'widget_logic_expand_control' ) AND is_array( $widget_logic_opts ) AND key_exists( 'widget_logic-options-filter', $widget_logic_opts ) )
630
- return ( $widget_logic_opts[ 'widget_logic-options-filter' ] == 'checked' );
631
-
632
- return FALSE;
633
- }
634
-
635
- } // End Admin_External_Links Class
636
-
637
- endif;
638
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
639
  /* ommit PHP closing tag, to prevent unwanted whitespace at the end of the parts generated by the included files */
1
+ <?php defined( 'ABSPATH' ) OR die( 'No direct access.' );
2
+ if ( ! class_exists( 'Admin_External_Links' ) ):
3
+
4
+ /**
5
+ * Class Admin_External_Links
6
+ * @category WordPress Plugins
7
+ */
8
+ final class Admin_External_Links {
9
+
10
+ /**
11
+ * Options to be saved and their default values
12
+ * @var array
13
+ */
14
+ public $save_options = array(
15
+ 'meta' => array(
16
+ 'version' => NULL,
17
+ ),
18
+ 'main' => array(
19
+ 'target' => '_none',
20
+ 'filter_page' => 1,
21
+ 'filter_posts' => 1,
22
+ 'filter_comments' => 1,
23
+ 'filter_widgets' => 1,
24
+ 'ignore' => '//twitter.com/share',
25
+ ),
26
+ 'seo' => array(
27
+ 'external' => 1,
28
+ 'nofollow' => 1,
29
+ 'title' => '%title%',
30
+ 'use_js' => 1,
31
+ 'load_in_footer' => 1,
32
+ ),
33
+ 'style' => array(
34
+ 'class_name' => 'ext-link',
35
+ 'icon' => 0,
36
+ 'image_no_icon' => 1,
37
+ 'no_icon_class' => 'no-ext-icon',
38
+ 'no_icon_same_window' => 0,
39
+ ),
40
+ 'extra' => array(
41
+ 'fix_js' => 0,
42
+ 'phpquery' => 0,
43
+ 'filter_excl_sel' => '.excl-ext-link',
44
+ ),
45
+ 'screen' => array(
46
+ 'menu_position' => NULL,
47
+ ),
48
+ );
49
+
50
+ /**
51
+ * Meta box page object
52
+ * @var WP_Meta_Box_Page
53
+ */
54
+ public $meta_box_page = NULL;
55
+
56
+ /**
57
+ * Ajax form object
58
+ * @var WP_Ajax_Option_Form
59
+ */
60
+ public $form = NULL;
61
+ static public $staticForm = NULL;
62
+
63
+
64
+ /**
65
+ * Constructor
66
+ */
67
+ public function __construct() {
68
+ $this->check_version_update();
69
+
70
+ // set meta box page
71
+ $this->meta_box_page = new WP_Meta_Box_Page_01();
72
+
73
+ // set ajax forms (also used by front-end)
74
+ $this->form = new WP_Option_Forms_01( WP_EXTERNAL_LINKS_KEY, $this->save_options );
75
+ self::$staticForm = $this->form;
76
+
77
+ // init admin
78
+ add_action( 'admin_init', array( $this, 'admin_init' ) );
79
+
80
+ if ( is_admin() ) {
81
+ // set options for add_page_method
82
+ $menu_pos = $this->form->set_current_option( 'screen' )->value( 'menu_position' );
83
+
84
+ // init meta box page
85
+ $this->meta_box_page->init(
86
+ // settings
87
+ array(
88
+ 'page_title' => $this->__( 'WP External Links' ),
89
+ 'menu_title' => $this->__( 'External Links' ),
90
+ 'page_slug' => strtolower( WP_EXTERNAL_LINKS_KEY ),
91
+ 'add_page_method' => ( ! empty( $menu_pos ) AND $menu_pos != 'admin.php' ) ? 'add_submenu_page' : 'add_menu_page',
92
+ 'parent_slug' => ( ! empty( $menu_pos ) AND $menu_pos != 'admin.php' ) ? $menu_pos : NULL,
93
+ 'column_widths' => array(
94
+ 1 => array( 99 ),
95
+ 2 => array( 69, 29 ),
96
+ ),
97
+ 'icon_url' => plugins_url( 'images/icon-wp-external-links-16.png', WP_EXTERNAL_LINKS_FILE ),
98
+ ),
99
+ // load callback
100
+ array( $this, 'call_load_meta_box' )
101
+ );
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Initialize Admin
107
+ */
108
+ public function admin_init() {
109
+ // set uninstall hook
110
+ register_uninstall_hook( WP_EXTERNAL_LINKS_FILE, array( 'Admin_External_Links', 'call_uninstall' ) );
111
+
112
+ // load text domain for translations
113
+ load_plugin_textdomain( WP_EXTERNAL_LINKS_KEY, FALSE, dirname( plugin_basename( WP_EXTERNAL_LINKS_FILE ) ) . '/lang/' );
114
+ }
115
+
116
+ /**
117
+ * Add to head of Admin page
118
+ */
119
+ public function admin_head() {
120
+ echo <<< style
121
+ <style type="text/css">
122
+ /* WP External Links */
123
+ .postbox-container { margin-left:1%; }
124
+ .tooltip-help { text-decoration: none; }
125
+ .tipsy { padding: 5px; }
126
+ .tipsy-inner { padding: 5px 8px 4px 8px; color: white; max-width: 200px; text-align: center; text-shadow: 0 -1px 0 #333;
127
+ border-top:1px solid #808080; border-botom:1px solid #6d6d6d; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px;
128
+ background-color:#777; background-image:-ms-linear-gradient(bottom,#6d6d6d,#808080); background-image:-moz-linear-gradient(bottom,#6d6d6d,#808080); background-image:-o-linear-gradient(bottom,#6d6d6d,#808080); background-image:-webkit-gradient(linear,left bottom,left top,from(#6d6d6d),to(#808080)); background-image:-webkit-linear-gradient(bottom,#6d6d6d,#808080); background-image:linear-gradient(bottom,#6d6d6d,#808080);
129
+ }
130
+ .tipsy-north { background-position: top center; }
131
+ .tipsy-south { background-position: bottom center; }
132
+ .tipsy-east { background-position: right center; }
133
+ .tipsy-west { background-position: center bottom; }
134
+ <style>
135
+ style;
136
+ }
137
+
138
+ /**
139
+ * Translate text in current domain
140
+ * @param string $text
141
+ * @return string
142
+ */
143
+ public function __( $text ) {
144
+ return translate( $text, WP_EXTERNAL_LINKS_KEY );
145
+ }
146
+
147
+ /**
148
+ * Translate text in current domain
149
+ * @param string $text
150
+ * @return string
151
+ */
152
+ public function _e( $text ) {
153
+ echo translate( $text, WP_EXTERNAL_LINKS_KEY );
154
+ }
155
+
156
+ /**
157
+ * Load meta box action
158
+ */
159
+ public function call_load_meta_box( $meta_box ) {
160
+ add_action( 'admin_head', array($this, 'admin_head') );
161
+
162
+ // add filters
163
+ $meta_box->add_title_filter( array( $this, 'call_page_title' ) )
164
+ ->add_contextual_help_filter( array( $this, 'call_contextual_help' ) );
165
+
166
+ // add meta boxes
167
+ // add_meta_box( $title, $callback, $context = 'normal', $id = NULL, $priority = 'default', $callback_args = NULL )
168
+ $meta_box->add_meta_box( $this->__( 'General Settings' ), array( $this, 'call_box_general_settings' ), 1 )
169
+ ->add_meta_box( $this->__( 'SEO Settings' ), array( $this, 'call_box_seo_settings' ), 1 )
170
+ ->add_meta_box( $this->__( 'Style Settings' ), array( $this, 'call_box_style_settings' ), 1 )
171
+ ->add_meta_box( $this->__( 'Extra Settings' ), array( $this, 'call_box_extra_settings' ), 1 )
172
+ ->add_meta_box( $this->__( 'Admin Settings' ), array( $this, 'call_box_admin_settings' ), 1 )
173
+ //->add_meta_box( $this->__( 'About this Plugin' ), array( $this, 'call_box_about' ), 2 )
174
+ ->add_meta_box( $this->__( 'Other Plugins' ), array( $this, 'call_box_other_plugins' ), 2 );
175
+
176
+ // scripts
177
+ wp_enqueue_script( 'admin-wp-external-links', plugins_url( '/js/admin-wp-external-links.js', WP_EXTERNAL_LINKS_FILE ), array( 'postbox' ), WP_EXTERNAL_LINKS_VERSION );
178
+ }
179
+
180
+ /**
181
+ * Contextual_help (callback)
182
+ * @param string $content
183
+ * @return string
184
+ */
185
+ public function call_contextual_help( $content ) {
186
+ $help = '';
187
+ $help .= $this->meta_box_page->get_ob_callback( array( $this, 'call_box_about' ) );
188
+ return $help . $content;
189
+ }
190
+
191
+ /**
192
+ * Add icon to page title
193
+ * @return string
194
+ */
195
+ public function call_page_title( $title ) {
196
+ // when updated set the update message
197
+ if ( isset($_GET[ 'settings-updated' ]) && $_GET[ 'settings-updated' ] == 'true' ) {
198
+ $title .= '<div class="updated settings-error" id="setting-error-settings_updated">'
199
+ . '<p><strong>' . __( 'Settings saved.' ) .'</strong></p>'
200
+ . '</div>';
201
+ }
202
+
203
+ $title = '<div class="icon32" id="icon-options-custom" style="background:url( '. plugins_url( 'images/icon-wp-external-links-32.png', WP_EXTERNAL_LINKS_FILE ) .' ) no-repeat 50% 50%"><br></div>'
204
+ . $title;
205
+
206
+ return $title;
207
+ }
208
+
209
+ /**
210
+ * Meta Box: General Settings
211
+ */
212
+ public function call_box_general_settings() {
213
+ echo $this->form->set_current_option( 'main' )->open_form();
214
+ ?>
215
+ <fieldset class="options">
216
+ <table class="form-table">
217
+ <tr>
218
+ <th style="width:250px;"><?php $this->_e( 'Open external links in...' ) ?>
219
+ <?php echo $this->tooltip_help( 'Specify the target (window or tab) for opening external links.' ) ?></th>
220
+ <td class="target_external_links">
221
+ <label><?php echo $this->form->radio( 'target', '_none', array( 'class' => 'field_target' ) ); ?>
222
+ <span><?php $this->_e( 'Same window or tab (<code>_none</code>)' ) ?></span></label>
223
+ <?php echo $this->tooltip_help( 'Open in current window or tab, when framed in the same frame.' ) ?>
224
+ <br/>
225
+ <label><?php echo $this->form->radio( 'target', '_blank', array( 'class' => 'field_target' ) ); ?>
226
+ <span><?php $this->_e( 'New window or tab (<code>_blank</code>)' ) ?></span></label>
227
+ <?php echo $this->tooltip_help( 'Open every external link in a new window or tab.' ) ?>
228
+ <br style="margin-bottom:15px;" />
229
+ <label><?php echo $this->form->radio( 'target', '_top', array( 'class' => 'field_target' ) ); ?>
230
+ <span><?php $this->_e( 'Topmost frame (<code>_top</code>)' ) ?></span></label>
231
+ <?php echo $this->tooltip_help( 'Open in current window or tab, when framed in the topmost frame.' ) ?>
232
+ <br/>
233
+ <label><?php echo $this->form->radio( 'target', '_new', array( 'class' => 'field_target' ) ); ?>
234
+ <span><?php $this->_e( 'Seperate window or tab (<code>_new</code>)' ) ?></span></label>
235
+ <?php echo $this->tooltip_help( 'Open new window the first time and use this window for each external link.' ) ?>
236
+ </td>
237
+ </tr>
238
+ <tr>
239
+ <th style="width:250px;"><?php $this->_e( 'Apply plugin settings on...' ) ?>
240
+ <?php echo $this->tooltip_help( 'Choose contents for applying settings to external links.' ) ?></th>
241
+ <td>
242
+ <label><?php echo $this->form->checkbox( 'filter_page', 1 ); ?>
243
+ <span><?php $this->_e( 'All contents' ) ?></span> <span class="description"><?php $this->_e('(the whole <code>&lt;body&gt;</code>)') ?></span></label>
244
+ <br/>&nbsp;&nbsp;<label><?php echo $this->form->checkbox( 'filter_posts', 1 ); ?>
245
+ <span><?php $this->_e( 'Post contents' ) ?></span></label>
246
+ <br/>&nbsp;&nbsp;<label><?php echo $this->form->checkbox( 'filter_comments', 1 ); ?>
247
+ <span><?php $this->_e( 'Comments' ) ?></span></label>
248
+ <br/>&nbsp;&nbsp;<label><?php echo $this->form->checkbox( 'filter_widgets', 1 ); ?>
249
+ <span><?php
250
+ if ( self::check_widget_content_filter() ):
251
+ $this->_e( 'All widgets' );
252
+ echo $this->tooltip_help( 'Applied to all widgets by using the "widget_content" filter of the Widget Logic plugin' );
253
+ else:
254
+ $this->_e( 'All text widgets' );
255
+ echo $this->tooltip_help( 'Only the text widget will be applied. To apply to all widget you should select "All contents" option.' );
256
+ endif;
257
+ ?></span></label>
258
+ </td>
259
+ </tr>
260
+ <tr>
261
+ <th><?php $this->_e( 'Ignore links (URL) containing...' ) ?>
262
+ <?php echo $this->tooltip_help( 'This plugin will completely ignore links that contain one of the given texts in the URL. Use enter to seperate each text. This check is not case sensitive.' ) ?></th>
263
+ <td><label><?php echo $this->form->textarea( 'ignore' ); ?>
264
+ <span class="description"><?php _e( 'Be as specific as you want, f.e.: <code>twitter.com</code> or <code>https://twitter.com</code>. Seperate each by an enter.' ) ?></span></label>
265
+ </td>
266
+ </tr>
267
+ </table>
268
+ </fieldset>
269
+
270
+ <?php
271
+ echo $this->form->submit();
272
+ echo $this->form->close_form();
273
+ }
274
+
275
+ /**
276
+ * Meta Box: SEO Settings
277
+ */
278
+ public function call_box_seo_settings() {
279
+ echo $this->form->set_current_option( 'seo' )->open_form();
280
+ ?>
281
+ <fieldset class="options">
282
+ <table class="form-table">
283
+ <tr>
284
+ <th style="width:250px;"><?php $this->_e( 'Add to <code>rel</code>-attribute' ) ?>
285
+ <?php echo $this->tooltip_help( 'Set values for the "rel"-atribute of external links.' ) ?></th>
286
+ <td><label><?php echo $this->form->checkbox( 'nofollow', 1 ); ?>
287
+ <span><?php $this->_e( 'Add <code>"nofollow"</code>' ) ?></span></label>
288
+ <?php echo $this->tooltip_help( 'Add "nofollow" to the "rel"-attribute of external links (unless link already has "follow").' ) ?>
289
+ <br/>
290
+ <label><?php echo $this->form->checkbox( 'external', 1 ); ?>
291
+ <span><?php $this->_e( 'Add <code>"external"</code>' ) ?></span></label>
292
+ <?php echo $this->tooltip_help( 'Add "external" to the "rel"-attribute of external links.' ) ?>
293
+ </td>
294
+ </tr>
295
+ <tr>
296
+ <th><?php $this->_e( 'Set <code>title</code>-attribute' ) ?>
297
+ <?php echo $this->tooltip_help( 'Set title attribute for external links. Use %title% for the original title value.' ) ?></th>
298
+ <td><label><?php echo $this->form->text( 'title' ); ?>
299
+ <br/><span class="description"><?php _e( 'Use <code>%title%</code> for the original title value.' ) ?></span></label></td>
300
+ </tr>
301
+ <tr>
302
+ <th><?php $this->_e( 'Use JavaScript method' ) ?>
303
+ <?php echo $this->tooltip_help( 'Enable this option to use the JavaScript method for opening links, which prevents adding target attribute in the HTML code.' ) ?></label>
304
+ </th>
305
+ <td>
306
+ <label><?php echo $this->form->checkbox( 'use_js', 1, array( 'class' => 'field_use_js' ) ); ?>
307
+ <span><?php $this->_e( 'Use JavaScript for opening links' ) ?></span> <span class="description"><?php $this->_e( '(valid xhtml strict)' ) ?></span>
308
+ <br/>
309
+ &nbsp;&nbsp;<label><?php echo $this->form->checkbox( 'load_in_footer', 1, array( 'class' => 'load_in_footer' ) ); ?>
310
+ <span><?php $this->_e( 'Load JS file in footer' ) ?></span>
311
+ </td>
312
+ </tr>
313
+ </table>
314
+ </fieldset>
315
+ <?php
316
+ echo $this->form->submit();
317
+ echo $this->form->close_form();
318
+ }
319
+
320
+ /**
321
+ * Meta Box: Style Settings
322
+ */
323
+ public function call_box_style_settings() {
324
+ echo $this->form->set_current_option( 'style' )->open_form();
325
+ ?>
326
+ <fieldset class="options">
327
+ <table class="form-table">
328
+ <tr>
329
+ <th style="width:250px;"><?php $this->_e( 'Set icon for external link' ) ?>
330
+ <?php echo $this->tooltip_help( 'Set an icon that wll be shown for external links. See example on the right side.' ) ?></th>
331
+ <td>
332
+ <div>
333
+ <div style="width:15%;float:left">
334
+ <label><?php echo $this->form->radio( 'icon', 0 ); ?>
335
+ <span><?php $this->_e( 'No icon' ) ?></span></label>
336
+ <?php for ( $x = 1; $x <= 20; $x++ ): ?>
337
+ <br/>
338
+ <label title="<?php echo sprintf( $this->__( 'Icon %1$s: choose this icon to show for all external links or add the class \'ext-icon-%1$s\' to a specific link.' ), $x ) ?>">
339
+ <?php echo $this->form->radio( 'icon', $x ); ?>
340
+ <img src="<?php echo plugins_url('images/ext-icons/ext-icon-'. $x .'.png', WP_EXTERNAL_LINKS_FILE) ?>" /></label>
341
+ <?php if ( $x % 5 == 0 ): ?>
342
+ </div>
343
+ <div style="width:15%;float:left">
344
+ <?php endif; ?>
345
+ <?php endfor; ?>
346
+ </div>
347
+ <div style="width:29%;float:left;"><span class="description"><?php $this->_e( 'Example:' ) ?></span>
348
+ <br/><img src="<?php echo plugins_url( 'images/link-icon-example.png', WP_EXTERNAL_LINKS_FILE ) ?>" />
349
+ </div>
350
+ <br style="clear:both" />
351
+ </div>
352
+ </td>
353
+ </tr>
354
+ <tr>
355
+ <th><?php $this->_e( 'Skip images' ) ?>
356
+ <?php echo $this->tooltip_help( 'Don\'t show icon for external links containing images.' ) ?></th>
357
+ <td><label><?php echo $this->form->checkbox( 'image_no_icon', 1 ); ?>
358
+ <span><?php $this->_e( 'No icon for extenal links with images' ) ?></span></label>
359
+ </td>
360
+ </tr>
361
+ <tr>
362
+ <th style="width:250px;"><?php $this->_e( 'Set no-icon class' ) ?>
363
+ <?php echo $this->tooltip_help( 'Set this class for links, that should not have the external link icon.' ) ?></th>
364
+ <td><label><?php echo $this->form->text( 'no_icon_class', array( 'class' => '' ) ); ?></label>
365
+ <br/><label><?php echo $this->form->checkbox( 'no_icon_same_window', 1 ); ?>
366
+ <span><?php $this->_e( 'Always open links with no-icon class in same window or tab' ) ?></span></label>
367
+ <?php echo $this->tooltip_help( 'When enabled external links containing the no-icon class will always be opened in the current window or tab. No matter which target is set.' ) ?>
368
+ </td>
369
+ </tr>
370
+ <tr>
371
+ <th><?php $this->_e( 'Add to <code>class</code>-attribute' ) ?>
372
+ <?php echo $this->tooltip_help( 'Add one or more extra classes to the external links, seperated by a space. It is optional, else just leave field blank.' ) ?></th>
373
+ <td><label><?php echo $this->form->text( 'class_name' ); ?></label></td>
374
+ </tr>
375
+ </table>
376
+ </fieldset>
377
+ <?php
378
+ echo $this->form->submit();
379
+ echo $this->form->close_form();
380
+ }
381
+
382
+ /**
383
+ * Meta Box: Extra Settings
384
+ */
385
+ public function call_box_extra_settings() {
386
+ echo $this->form->set_current_option( 'extra' )->open_form();
387
+ ?>
388
+ <fieldset class="options">
389
+ <table class="form-table">
390
+ <tr>
391
+ <th style="width:250px;"><?php $this->_e( 'Solving problems' ) ?>
392
+ <?php echo $this->tooltip_help( 'Some options to try when a problem occurs. These options can also cause other problems, so be carefull.' ) ?></th>
393
+ <td><label><?php echo $this->form->checkbox( 'fix_js', 1 ); ?>
394
+ <span><?php $this->_e( 'Replacing <code>&lt;/a&gt;</code> with <code>&lt;\/a&gt;</code> in JavaScript code.' ) ?></span></label>
395
+ <?php echo $this->tooltip_help( 'By replacing </a> with <\/a> in JavaScript code these tags will not be processed by the plugin.' ) ?>
396
+ </td>
397
+ </tr>
398
+ <tr>
399
+ <th style="width:250px;"><?php $this->_e( 'Use phpQuery library' ) ?>
400
+ <?php echo $this->tooltip_help( 'Using phpQuery library for manipulating links. This option is experimental.' ) ?></th>
401
+ <td><label><?php echo $this->form->checkbox( 'phpquery', 1 ); ?>
402
+ <span><?php $this->_e( 'Use phpQuery for parsing document.' ) ?></span>
403
+ <span class="description">(Test it first!)</span></label>
404
+ </td>
405
+ </tr>
406
+ <tr class="filter_excl_sel" <?php echo ( $this->form->value( 'phpquery' ) ) ? '' : 'style="display:none;"'; ?>>
407
+ <th><?php $this->_e( 'Do NOT apply settings on...' ) ?>
408
+ <?php echo $this->tooltip_help( 'The external links of these selection will be excluded for the settings of this plugin. Define the selection by using CSS selectors.' ) ?></th>
409
+ <td><label><?php echo $this->form->textarea( 'filter_excl_sel' ); ?>
410
+ <span class="description"><?php _e( 'Define selection by using CSS selectors, f.e.: <code>.excl-ext-link, .entry-title, #comments-title</code> (look <a href="http://code.google.com/p/phpquery/wiki/Selectors" target="_blank">here</a> for available selectors).' ) ?></span></label>
411
+ </td>
412
+ </tr>
413
+ </table>
414
+ </fieldset>
415
+ <?php
416
+ echo $this->form->submit();
417
+ echo $this->form->close_form();
418
+ }
419
+
420
+ /**
421
+ * Meta Box: Extra Settings
422
+ */
423
+ public function call_box_admin_settings() {
424
+ echo $this->form->set_current_option( 'screen' )->open_form();
425
+ ?>
426
+ <fieldset class="options">
427
+ <table class="form-table">
428
+ <tr>
429
+ <th><?php $this->_e('Admin menu position') ?>
430
+ <?php echo $this->tooltip_help( 'Change the menu position of this plugin in "Screen Options".' ) ?></th>
431
+ <td><label>
432
+ <?php
433
+ echo $this->form->select( 'menu_position', array(
434
+ 'admin.php' => 'Main menu',
435
+ 'index.php' => $this->__( 'Subitem of Dashboard' ),
436
+ 'edit.php' => $this->__( 'Subitem of Posts' ),
437
+ 'upload.php' => $this->__( 'Subitem of Media' ),
438
+ 'link-manager.php' => $this->__( 'Subitem of Links' ),
439
+ 'edit.php?post_type=page' => $this->__( 'Subitem of Pages' ),
440
+ 'edit-comments.php' => $this->__( 'Subitem of Comments' ),
441
+ 'themes.php' => $this->__( 'Subitem of Appearance' ),
442
+ 'plugins.php' => $this->__( 'Subitem of Plugins' ),
443
+ 'users.php' => $this->__( 'Subitem of Users' ),
444
+ 'tools.php' => $this->__( 'Subitem of Tools' ),
445
+ 'options-general.php' => $this->__( 'Subitem of Settings' ),
446
+ ));
447
+ ?>
448
+ </label></td>
449
+ </tr>
450
+ </table>
451
+ </fieldset>
452
+ <?php
453
+ echo $this->form->submit();
454
+ echo $this->form->close_form();
455
+ }
456
+
457
+ /**
458
+ * Meta Box: About...
459
+ */
460
+ public function call_box_about() {
461
+ ?>
462
+ <h4><img src="<?php echo plugins_url( 'images/icon-wp-external-links-16.png', WP_EXTERNAL_LINKS_FILE ) ?>" width="16" height="16" /> <?php $this->_e( 'WP External Links' ) ?></h4>
463
+ <div>
464
+ <p><?php printf( $this->__( 'Current version: <strong>%1$s</strong>' ), WP_EXTERNAL_LINKS_VERSION ) ?></p>
465
+ <p><?php $this->_e( 'Manage external links on your site: open in new window/tab, set link icon, add "external", add "nofollow" and more.' ) ?></p>
466
+ <p><a href="http://www.freelancephp.net/contact/" target="_blank"><?php $this->_e( 'Questions or suggestions?' ) ?></a></p>
467
+ <p><?php $this->_e( 'If you like this plugin please send your rating at WordPress.org.' ) ?></p>
468
+ <p><?php _e( 'More info' ) ?>: <a href="http://wordpress.org/extend/plugins/wp-external-links/" target="_blank">WordPress.org</a> | <a href="http://www.freelancephp.net/wp-external-links-plugin/" target="_blank">FreelancePHP.net</a></p>
469
+ </div>
470
+ <?php
471
+ }
472
+
473
+ /**
474
+ * Meta Box: Other Plugins
475
+ */
476
+ public function call_box_other_plugins() {
477
+ ?>
478
+ <h4><img src="<?php echo plugins_url( 'images/icon-email-encoder-bundle-16.png', WP_EXTERNAL_LINKS_FILE ); ?>" width="16" height="16" /> Email Encoder Bundle</h4>
479
+ <div>
480
+ <?php if ( is_plugin_active( 'email-encoder-bundle/email-encoder-bundle.php' ) ): ?>
481
+ <p><?php $this->_e( 'This plugin is already activated.' ) ?> <a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/options-general.php?page=email-encoder-bundle/email-encoder-bundle.php"><?php $this->_e( 'Settings' ) ?></a></p>
482
+ <?php elseif( file_exists( WP_PLUGIN_DIR . '/email-encoder-bundle/email-encoder-bundle.php' ) ): ?>
483
+ <p><a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/plugins.php?plugin_status=inactive"><?php $this->_e( 'Activate this plugin.' ) ?></a></p>
484
+ <?php else: ?>
485
+ <p><a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/plugin-install.php?tab=search&type=term&s=Email+Encoder+Bundle+freelancephp&plugin-search-input=Search+Plugins"><?php $this->_e( 'Get this plugin now' ) ?></a></p>
486
+ <?php endif; ?>
487
+
488
+ <p><?php $this->_e( 'Protect email addresses on your site from spambots and being used for spamming by using one of the encoding methods.' ) ?></p>
489
+ <p><?php _e( 'More info' ) ?>: <a href="http://wordpress.org/extend/plugins/email-encoder-bundle/" target="_blank">WordPress.org</a> | <a href="http://www.freelancephp.net/email-encoder-php-class-wp-plugin/" target="_blank">FreelancePHP.net</a></p>
490
+ </div>
491
+
492
+ <?php echo $this->hr(); ?>
493
+
494
+ <h4><img src="<?php echo plugins_url( 'images/icon-wp-mailto-links-16.png', WP_EXTERNAL_LINKS_FILE ); ?>" width="16" height="16" /> WP Mailto Links</h4>
495
+ <div>
496
+ <?php if ( is_plugin_active( 'wp-mailto-links/wp-mailto-links.php' ) ): ?>
497
+ <p><?php $this->_e( 'This plugin is already activated.' ) ?> <a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/options-general.php?page=wp-mailto-links/wp-mailto-links.php"><?php $this->_e( 'Settings' ) ?></a></p>
498
+ <?php elseif( file_exists( WP_PLUGIN_DIR . '/wp-mailto-links/wp-mailto-links.php' ) ): ?>
499
+ <p><a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/plugins.php?plugin_status=inactive"><?php $this->_e( 'Activate this plugin.' ) ?></a></p>
500
+ <?php else: ?>
501
+ <p><a href="<?php echo get_bloginfo( 'url' ) ?>/wp-admin/plugin-install.php?tab=search&type=term&s=WP+Mailto+Links+freelancephp&plugin-search-input=Search+Plugins"><?php $this->_e( 'Get this plugin now' ) ?></a></p>
502
+ <?php endif; ?>
503
+
504
+ <p><?php $this->_e( 'Manage mailto links on your site and protect emails from spambots, set mail icon and more.' ) ?></p>
505
+ <p><?php _e( 'More info' ) ?>: <a href="http://wordpress.org/extend/plugins/wp-mailto-links/" target="_blank">WordPress.org</a> | <a href="http://www.freelancephp.net/wp-mailto-links-plugin/" target="_blank">FreelancePHP.net</a></p>
506
+ </div>
507
+ <?php
508
+ }
509
+
510
+ /**
511
+ * Activation callback
512
+ */
513
+ public function check_version_update() {
514
+ // check for version
515
+ $meta = get_option( 'wp_external_links-meta' );
516
+ if ( $meta[ 'version' ] == WP_EXTERNAL_LINKS_VERSION )
517
+ return;
518
+
519
+ // set new version
520
+ $meta[ 'version' ] = WP_EXTERNAL_LINKS_VERSION;
521
+ update_option( 'wp_external_links-meta', $meta );
522
+
523
+ // check for upgrading saved options to v1.00
524
+ $old_options = get_option( 'WP_External_Links_options' );
525
+
526
+ if ( ! empty( $old_options ) ) {
527
+ $new_options = $this->save_options;
528
+
529
+ $new_options[ 'main' ][ 'target' ] = $old_options[ 'target' ];
530
+ $new_options[ 'main' ][ 'filter_page' ] = $old_options[ 'filter_whole_page' ];
531
+ $new_options[ 'main' ][ 'filter_posts' ] = $old_options[ 'filter_posts' ];
532
+ $new_options[ 'main' ][ 'filter_comments' ] = $old_options[ 'filter_comments' ];
533
+ $new_options[ 'main' ][ 'filter_widgets' ] = $old_options[ 'filter_widgets' ];
534
+ $new_options[ 'seo' ][ 'external' ] = $old_options[ 'external' ];
535
+ $new_options[ 'seo' ][ 'nofollow' ] = $old_options[ 'nofollow' ];
536
+ $new_options[ 'seo' ][ 'use_js' ] = $old_options[ 'use_js' ];
537
+ $new_options[ 'style' ][ 'class_name' ] = $old_options[ 'class_name' ];
538
+ $new_options[ 'style' ][ 'icon' ] = $old_options[ 'icon' ];
539
+ $new_options[ 'style' ][ 'no_icon_class' ] = $old_options[ 'no_icon_class' ];
540
+ $new_options[ 'style' ][ 'no_icon_same_window' ] = $old_options[ 'no_icon_same_window' ];
541
+
542
+ // save new format option values
543
+ update_option( 'wp_external_links-main', $new_options[ 'main' ] );
544
+ update_option( 'wp_external_links-seo', $new_options[ 'seo' ] );
545
+ update_option( 'wp_external_links-style', $new_options[ 'style' ] );
546
+
547
+ // delete old format option values
548
+ delete_option( 'WP_External_Links_options' );
549
+ }
550
+
551
+ // upgrade to v1.20
552
+ $upgrade_main = get_option( 'wp_external_links-main' );
553
+
554
+ if ( ! isset( $upgrade_main[ 'ignore' ] ) ) {
555
+ $upgrade_main[ 'ignore' ] = $this->save_options[ 'main' ][ 'ignore' ];
556
+ update_option( 'wp_external_links-main', $upgrade_main );
557
+ }
558
+
559
+ // upgrade to v1.30
560
+ if ( WP_EXTERNAL_LINKS_VERSION == '1.30' ) {
561
+ $new_options = $this->save_options;
562
+ $general = get_option( 'wp_external_links-general' );
563
+ $style = get_option( 'wp_external_links-style' );
564
+
565
+ if ( isset( $general[ 'target' ] ) ) $new_options[ 'main' ][ 'target' ] = $general[ 'target' ];
566
+ $new_options[ 'main' ][ 'filter_page' ] = ( isset( $general[ 'filter_page' ] ) ) ? $general[ 'filter_page' ] : 0;
567
+ $new_options[ 'main' ][ 'filter_posts' ] = ( isset( $general[ 'filter_posts' ] ) ) ? $general[ 'filter_posts' ] : 0;
568
+ $new_options[ 'main' ][ 'filter_comments' ] = ( isset( $general[ 'filter_comments' ] ) ) ? $general[ 'filter_comments' ] : 0;
569
+ $new_options[ 'main' ][ 'filter_widgets' ] = ( isset( $general[ 'filter_widgets' ] ) ) ? $general[ 'filter_widgets' ] : 0;
570
+ if ( isset( $general[ 'ignore' ] ) ) $new_options[ 'main' ][ 'ignore' ] = $general[ 'ignore' ];
571
+
572
+ $new_options[ 'seo' ][ 'external' ] = ( isset( $general[ 'external' ] ) ) ? $general[ 'external' ] : 0;
573
+ $new_options[ 'seo' ][ 'nofollow' ] = ( isset( $general[ 'nofollow' ] ) ) ? $general[ 'nofollow' ] : 0;
574
+ $new_options[ 'seo' ][ 'use_js' ] = ( isset( $general[ 'use_js' ] ) ) ? $general[ 'use_js' ] : 0;
575
+ if ( isset( $general[ 'title' ] ) ) $new_options[ 'seo' ][ 'title' ] = $general[ 'title' ];
576
+
577
+ if ( isset( $general[ 'class_name' ] ) ) $new_options[ 'style' ][ 'class_name' ] = $general[ 'class_name' ];
578
+
579
+ if ( isset( $style[ 'icon' ] ) ) $new_options[ 'style' ][ 'icon' ] = $style[ 'icon' ];
580
+ if ( isset( $style[ 'no_icon_class' ] ) ) $new_options[ 'style' ][ 'no_icon_class' ] = $style[ 'no_icon_class' ];
581
+ $new_options[ 'style' ][ 'no_icon_same_window' ] = ( isset( $style[ 'no_icon_same_window' ] ) ) ? $style[ 'no_icon_same_window' ] : 0;
582
+
583
+ $new_options[ 'extra' ][ 'fix_js' ] = ( isset( $general[ 'fix_js' ] ) ) ? $general[ 'fix_js' ] : 0;
584
+ $new_options[ 'extra' ][ 'phpquery' ] = ( isset( $general[ 'phpquery' ] ) ) ? $general[ 'phpquery' ] : 0;
585
+ if ( isset( $general[ 'filter_excl_sel' ] ) ) $new_options[ 'extra' ][ 'filter_excl_sel' ] = $general[ 'filter_excl_sel' ];
586
+
587
+ // save new format option values
588
+ update_option( 'wp_external_links-main', $new_options[ 'main' ] );
589
+ update_option( 'wp_external_links-seo', $new_options[ 'seo' ] );
590
+ update_option( 'wp_external_links-style', $new_options[ 'style' ] );
591
+ update_option( 'wp_external_links-extra', $new_options[ 'extra' ] );
592
+
593
+ // delete old format
594
+ delete_option( 'wp_external_links-general' );
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Method for test purpuses
600
+ */
601
+ public function __options($values = null) {
602
+ if (class_exists('Test_WP_Mailto_Links') && constant('WP_DEBUG') === true) {
603
+ if ($values !== null) {
604
+ $this->set_options($values);
605
+ }
606
+
607
+ return $this->options;
608
+ }
609
+ }
610
+
611
+ /**
612
+ * Uninstall callback
613
+ */
614
+ static public function call_uninstall() {
615
+ self::$staticForm->delete_options();
616
+ }
617
+
618
+ /**
619
+ * Set tooltip help
620
+ * @param string $text
621
+ * @return string
622
+ */
623
+ public function tooltip_help( $text ) {
624
+ $text = $this->__( $text );
625
+ $text = htmlentities( $text );
626
+
627
+ $html = '<a href="#" class="tooltip-help" title="'. $text .'"><sup>(?)</sup></a>';
628
+ return $html;
629
+ }
630
+
631
+ /**
632
+ * Get html seperator
633
+ * @return string
634
+ */
635
+ protected function hr() {
636
+ return '<hr style="border:1px solid #FFF; border-top:1px solid #EEE;" />';
637
+ }
638
+
639
+
640
+ /**
641
+ * Check if widget_content filter is available (Widget Logic Plugin)
642
+ * @return boolean
643
+ * @static
644
+ */
645
+ public static function check_widget_content_filter() {
646
+ // set widget_content filter of Widget Logic plugin
647
+ $widget_logic_opts = get_option( 'widget_logic' );
648
+
649
+ if ( function_exists( 'widget_logic_expand_control' ) AND is_array( $widget_logic_opts ) AND key_exists( 'widget_logic-options-filter', $widget_logic_opts ) )
650
+ return ( $widget_logic_opts[ 'widget_logic-options-filter' ] == 'checked' );
651
+
652
+ return FALSE;
653
+ }
654
+
655
+ } // End Admin_External_Links Class
656
+
657
+ endif;
658
+
659
  /* ommit PHP closing tag, to prevent unwanted whitespace at the end of the parts generated by the included files */
includes/class-wp-external-links.php CHANGED
@@ -1,594 +1,604 @@
1
- <?php defined( 'ABSPATH' ) OR die( 'No direct access.' );
2
- if ( ! class_exists( 'WP_External_Links' ) ):
3
-
4
- /**
5
- * Class WP_External_Links
6
- * @package WordPress
7
- * @since
8
- * @category WordPress Plugins
9
- */
10
- final class WP_External_Links {
11
-
12
- /**
13
- * Admin object
14
- * @var Admin_External_Links
15
- */
16
- public $admin = NULL;
17
-
18
- /**
19
- * Array of ignored links
20
- * @var type
21
- */
22
- private $ignored = array();
23
-
24
-
25
- /**
26
- * Constructor
27
- */
28
- public function __construct() {
29
- // set admin object
30
- $this->admin = new Admin_External_Links();
31
-
32
- // add actions
33
- add_action( 'wp', array( $this, 'call_wp' ) );
34
- }
35
-
36
- /**
37
- * Quick helper method for getting saved option values
38
- * @param string $key
39
- * @return mixed
40
- */
41
- public function get_opt( $key ) {
42
- $lookup = $this->admin->save_options;
43
-
44
- foreach ( $lookup as $option_name => $values ) {
45
- $value = $this->admin->form->value( $key, '___NONE___', $option_name );
46
-
47
- if ($value !== '___NONE___')
48
- return $value;
49
- }
50
-
51
- throw new Exception('Option with key "' . $key . '" does not exist.');
52
- }
53
-
54
- /**
55
- * wp callback
56
- */
57
- public function call_wp() {
58
- if ( ! is_admin() && ! is_feed() ) {
59
- // add wp_head for setting js vars and css style
60
- add_action( 'wp_head', array( $this, 'call_wp_head' ) );
61
-
62
- // set js file
63
- if ( $this->get_opt( 'use_js' ) )
64
- wp_enqueue_script( 'wp-external-links', plugins_url( 'js/wp-external-links.js', WP_EXTERNAL_LINKS_FILE ), array( 'jquery' ), WP_EXTERNAL_LINKS_VERSION, (bool) $this->get_opt( 'load_in_footer' ) );
65
-
66
- // filters
67
- if ( $this->get_opt( 'filter_page' ) ) {
68
- // filter body
69
- ob_start( array( $this, 'call_filter_content' ) );
70
-
71
- // set ob flush
72
- add_action('wp_footer', array($this, 'callback_flush_buffer'), 10000);
73
-
74
- } else {
75
- // set filter priority
76
- $priority = 1000000000;
77
-
78
- // content
79
- if ( $this->get_opt( 'filter_posts' ) ) {
80
- add_filter( 'the_title', array( $this, 'call_filter_content' ), $priority );
81
- add_filter( 'the_content', array( $this, 'call_filter_content' ), $priority );
82
- add_filter( 'get_the_excerpt', array( $this, 'call_filter_content' ), $priority );
83
- // redundant:
84
- //add_filter( 'the_excerpt', array( $this, 'call_filter_content' ), $priority );
85
- }
86
-
87
- // comments
88
- if ( $this->get_opt( 'filter_comments' ) ) {
89
- add_filter( 'get_comment_text', array( $this, 'call_filter_content' ), $priority );
90
- // redundant:
91
- //add_filter( 'comment_text', array( $this, 'call_filter_content' ), $priority );
92
-
93
- add_filter( 'comment_excerpt', array( $this, 'call_filter_content' ), $priority );
94
- // redundant:
95
- //add_filter( 'get_comment_excerpt', array( $this, 'call_filter_content' ), $priority );
96
-
97
- add_filter( 'comment_url', array( $this, 'call_filter_content' ), $priority );
98
- add_filter( 'get_comment_author_url', array( $this, 'call_filter_content' ), $priority );
99
- add_filter( 'get_comment_author_link', array( $this, 'call_filter_content' ), $priority );
100
- add_filter( 'get_comment_author_url_link', array( $this, 'call_filter_content' ), $priority );
101
- }
102
-
103
- // widgets
104
- if ( $this->get_opt( 'filter_widgets' ) ) {
105
- if ( $this->admin->check_widget_content_filter() ) {
106
- // only if Widget Logic plugin is installed and 'widget_content' option is activated
107
- add_filter( 'widget_content', array( $this, 'call_filter_content' ), $priority );
108
- } else {
109
- // filter text widgets
110
- add_filter( 'widget_title', array( $this, 'call_filter_content' ), $priority );
111
- add_filter( 'widget_text', array( $this, 'call_filter_content' ), $priority );
112
- }
113
- }
114
- }
115
- }
116
-
117
- // hook
118
- do_action('wpel_ready', array($this, 'call_filter_content'), $this);
119
- }
120
-
121
- /**
122
- * End output buffer
123
- */
124
- public function callback_flush_buffer() {
125
- ob_end_flush();
126
- }
127
-
128
- /**
129
- * wp_head callback
130
- */
131
- public function call_wp_head() {
132
- // set ignored
133
- $ignored = $this->get_opt( 'ignore' );
134
- $ignored = trim( $ignored );
135
- $ignored = explode( "\n", $ignored );
136
- $ignored = array_map( 'trim', $ignored );
137
- $ignored = array_map( 'strtolower', $ignored );
138
- $this->ignored = $ignored;
139
-
140
- $icon = $this->get_opt('icon');
141
-
142
- if ($icon) {
143
- $padding = ($icon < 20) ? 15 : 12;
144
- ?>
145
- <style type="text/css" media="screen">
146
- /* WP External Links Plugin */
147
- .ext-icon-<?php echo $icon ?> { background:url(<?php echo plugins_url('/images/ext-icons/ext-icon-' . $icon . '.png', WP_EXTERNAL_LINKS_FILE) ?>) no-repeat 100% 50%; padding-right:<?php echo $padding ?>px; }';
148
- </style>
149
- <?php
150
- }
151
-
152
- if ( $this->get_opt( 'use_js' ) AND $this->get_opt( 'target' ) != '_none' ):
153
- // set exclude class
154
- $excludeClass = ( $this->get_opt( 'no_icon_same_window' ) AND $this->get_opt( 'no_icon_class' ) )
155
- ? $this->get_opt( 'no_icon_class' )
156
- : '';
157
- ?>
158
- <script type="text/javascript">/* <![CDATA[ */
159
- /* WP External Links Plugin */
160
- var wpExtLinks = { baseUrl: '<?php echo get_bloginfo( 'wpurl' ) ?>', target: '<?php echo $this->get_opt( 'target' ) ?>', excludeClass: '<?php echo $excludeClass ?>' };
161
- /* ]]> */</script>
162
- <?php
163
- endif;
164
- }
165
-
166
- /**
167
- * Filter content
168
- * @param string $content
169
- * @return string
170
- */
171
- public function call_filter_content( $content ) {
172
- if ( $this->get_opt( 'fix_js' ) ) {
173
- // fix js problem by replacing </a> by <\/a>
174
- $content = preg_replace_callback( '/<script([^>]*)>(.*?)<\/script[^>]*>/is', array( $this, 'call_fix_js' ), $content );
175
- }
176
-
177
- if ( $this->get_opt( 'phpquery' ) ) {
178
- // Include phpQuery
179
- if ( ! class_exists( 'phpQuery' ) ) {
180
- require_once( 'phpQuery.php' );
181
- }
182
-
183
- return $this->filter_phpquery( $content );
184
- } else {
185
- return $this->filter( $content );
186
- }
187
- }
188
-
189
- /**
190
- * Fix </a> in JavaScript blocks (callback for regexp)
191
- * @param array $matches Result of a preg call in filter_content()
192
- * @return string Clean code
193
- */
194
- public function call_fix_js( $matches ) {
195
- return str_replace( '</a>', '<\/a>', $matches[ 0 ] );
196
- }
197
-
198
- /**
199
- * Check if link is external
200
- * @param string $href
201
- * @param string $rel
202
- * @return boolean
203
- */
204
- private function is_external( $href, $rel ) {
205
- return ( isset( $href ) AND ( strpos( $rel, 'external' ) !== FALSE
206
- OR ( strpos( $href, strtolower( get_bloginfo( 'wpurl' ) ) ) === FALSE )
207
- AND ( substr( $href, 0, 7 ) == 'http://'
208
- OR substr( $href, 0, 8 ) == 'https://'
209
- OR substr( $href, 0, 6 ) == 'ftp://'
210
- OR substr( $href, 0, 2 ) == '//' ) ) );
211
- }
212
-
213
- /**
214
- * Is an ignored link
215
- * @param string $href
216
- * @return boolean
217
- */
218
- private function is_ignored( $href ) {
219
- // check if this links should be ignored
220
- for ( $x = 0, $count = count($this->ignored); $x < $count; $x++ ) {
221
- if ( strrpos( $href, $this->ignored[ $x ] ) !== FALSE )
222
- return TRUE;
223
- }
224
-
225
- return FALSE;
226
- }
227
-
228
- /**
229
- * Filter content
230
- * @param string $content
231
- * @return string
232
- */
233
- private function filter( $content ) {
234
- // replace links
235
- $content = preg_replace_callback( '/<a[^A-Za-z](.*?)>(.*?)<\/a[\s+]*>/is', array( $this, 'call_parse_link' ), $content );
236
-
237
- // remove style when no icon classes are found
238
- if ( strpos( $content, 'ext-icon-' ) === FALSE ) {
239
- // remove style with id wp-external-links-css
240
- $content = preg_replace( '/<link ([^>]*)wp-external-links-css([^>]*)\/>[\s+]*/i', '', $content );
241
- }
242
-
243
- return $content;
244
- }
245
-
246
- /**
247
- * Parse an attributes string into an array. If the string starts with a tag,
248
- * then the attributes on the first tag are parsed. This parses via a manual
249
- * loop and is designed to be safer than using DOMDocument.
250
- *
251
- * @param string|* $attrs
252
- * @return array
253
- *
254
- * @example parse_attrs( 'src="example.jpg" alt="example"' )
255
- * @example parse_attrs( '<img src="example.jpg" alt="example">' )
256
- * @example parse_attrs( '<a href="example"></a>' )
257
- * @example parse_attrs( '<a href="example">' )
258
- *
259
- * @link http://dev.airve.com/demo/speed_tests/php/parse_attrs.php
260
- */
261
- private function parse_attrs ($attrs) {
262
- if ( ! is_scalar($attrs) )
263
- return (array) $attrs;
264
-
265
- $attrs = str_split( trim($attrs) );
266
-
267
- if ( '<' === $attrs[0] ) # looks like a tag so strip the tagname
268
- while ( $attrs && ! ctype_space($attrs[0]) && $attrs[0] !== '>' )
269
- array_shift($attrs);
270
-
271
- $arr = array(); # output
272
- $name = ''; # for the current attr being parsed
273
- $value = ''; # for the current attr being parsed
274
- $mode = 0; # whether current char is part of the name (-), the value (+), or neither (0)
275
- $stop = false; # delimiter for the current $value being parsed
276
- $space = ' '; # a single space
277
-
278
- foreach ( $attrs as $j => $curr ) {
279
- if ( $mode < 0 ) {# name
280
- if ( '=' === $curr ) {
281
- $mode = 1;
282
- $stop = false;
283
- } elseif ( '>' === $curr ) {
284
- '' === $name or $arr[ $name ] = $value;
285
- break;
286
- } elseif ( ! ctype_space($curr) ) {
287
- if ( ctype_space( $attrs[ $j - 1 ] ) ) { # previous char
288
- '' === $name or $arr[ $name ] = ''; # previous name
289
- $name = $curr; # initiate new
290
- } else {
291
- $name .= $curr;
292
- }
293
- }
294
- } elseif ( $mode > 0 ) {# value
295
-
296
- if ( $stop === false ) {
297
- if ( ! ctype_space($curr) ) {
298
- if ( '"' === $curr || "'" === $curr ) {
299
- $value = '';
300
- $stop = $curr;
301
- } else {
302
- $value = $curr;
303
- $stop = $space;
304
- }
305
- }
306
- } elseif ( $stop === $space ? ctype_space($curr) : $curr === $stop ) {
307
- $arr[ $name ] = $value;
308
- $mode = 0;
309
- $name = $value = '';
310
- } else {
311
- $value .= $curr;
312
- }
313
- } else {# neither
314
-
315
- if ( '>' === $curr )
316
- break;
317
- if ( ! ctype_space( $curr ) ) {
318
- # initiate
319
- $name = $curr;
320
- $mode = -1;
321
- }
322
- }
323
- }
324
-
325
- # incl the final pair if it was quoteless
326
- '' === $name or $arr[ $name ] = $value;
327
-
328
- return $arr;
329
- }
330
-
331
- /**
332
- * Make a clean <a> code (callback for regexp)
333
- * @param array $matches Result of a preg call in filter_content()
334
- * @return string Clean <a> code
335
- */
336
- public function call_parse_link( $matches ) {
337
- $attrs = $matches[ 1 ];
338
- $attrs = stripslashes( $attrs );
339
- $attrs = $this->parse_attrs( $attrs );
340
-
341
- $rel = ( isset( $attrs[ 'rel' ] ) ) ? strtolower( $attrs[ 'rel' ] ) : '';
342
-
343
- // href
344
- $href = $attrs[ 'href' ];
345
- $href = strtolower( $href );
346
- $href = trim( $href );
347
-
348
- // check if it is an external link and not excluded
349
- if ( ! $this->is_external( $href, $rel ))
350
- return $matches[ 0 ];
351
-
352
- if ( $this->is_ignored( $href ) ) {
353
- return apply_filters('wpel_external_link', $matches[ 0 ], $matches[ 0 ], $matches[ 2 ], $attrs, true);
354
- }
355
-
356
- // set rel="external" (when not already set)
357
- if ( $this->get_opt( 'external' ) )
358
- $this->add_attr_value( $attrs, 'rel', 'external' );
359
-
360
- // set rel="nofollow" when doesn't have "follow" (or already "nofollow")
361
- if ( $this->get_opt( 'nofollow' ) AND strpos( $rel, 'follow' ) === FALSE )
362
- $this->add_attr_value( $attrs, 'rel', 'nofollow' );
363
-
364
- // set title
365
- $title_format = $this->get_opt( 'title' );
366
- $title = ( isset( $attrs[ 'title' ] ) ) ? $attrs[ 'title' ] : '';
367
- $attrs[ 'title' ] = str_replace( '%title%', $title, $title_format );
368
-
369
- // set user-defined class
370
- $class = $this->get_opt( 'class_name' );
371
- if ( $class )
372
- $this->add_attr_value( $attrs, 'class', $class );
373
-
374
- // set icon class, unless no-icon class isset or another icon class ('ext-icon-...') is found or content contains image
375
- if ( $this->get_opt( 'icon' ) > 0
376
- AND ( ! $this->get_opt( 'no_icon_class' ) OR strpos( $attrs[ 'class' ], $this->get_opt( 'no_icon_class' ) ) === FALSE )
377
- AND strpos( $attrs[ 'class' ], 'ext-icon-' ) === FALSE
378
- AND !( $this->get_opt( 'image_no_icon' ) AND (bool) preg_match( '/<img([^>]*)>/is', $matches[ 2 ] )) ){
379
- $icon_class = 'ext-icon-'. $this->get_opt( 'icon', 'style' );
380
- $this->add_attr_value( $attrs, 'class', $icon_class );
381
- }
382
-
383
- // set target
384
- if ( ! $this->get_opt( 'use_js' ) AND ( ! $this->get_opt( 'no_icon_same_window' )
385
- OR ! $this->get_opt( 'no_icon_class' )
386
- OR strpos( $attrs[ 'class' ], $this->get_opt( 'no_icon_class' ) ) === FALSE ) ) {
387
- if ( $this->get_opt( 'target' ) == '_none' ) {
388
- unset( $attrs[ 'target' ] );
389
- } else {
390
- $attrs[ 'target' ] = $this->get_opt( 'target' );
391
- }
392
- }
393
-
394
- // create element code
395
- $link = '<a';
396
-
397
- foreach ( $attrs AS $key => $value ) {
398
- $link .= ' '. $key .'="'. $value .'"';
399
- }
400
-
401
- $link .= '>'. $matches[ 2 ] .'</a>';
402
-
403
- // filter
404
- $link = apply_filters('wpel_external_link', $link, $matches[ 0 ], $matches[ 2 ], $attrs, false);
405
-
406
- return $link;
407
- }
408
-
409
- /**
410
- * Add value to attribute
411
- * @param array $attrs
412
- * @param string $attr
413
- * @param string $value
414
- * @param string $default Optional, default NULL which means tje attribute will be removed when (new) value is empty
415
- * @return New value
416
- */
417
- public function add_attr_value( &$attrs, $attr_name, $value, $default = NULL ) {
418
- if ( key_exists( $attr_name, $attrs ) )
419
- $old_value = $attrs[ $attr_name ];
420
-
421
- if ( empty( $old_value ) )
422
- $old_value = '';
423
-
424
- $split = explode( ' ', strtolower( $old_value ) );
425
-
426
- if ( in_array( $value, $split ) ) {
427
- $value = $old_value;
428
- } else {
429
- $value = ( empty( $old_value ) )
430
- ? $value
431
- : $old_value .' '. $value;
432
- }
433
-
434
- if ( empty( $value ) AND $default === NULL ) {
435
- unset( $attrs[ $attr_name ] );
436
- } else {
437
- $attrs[ $attr_name ] = $value;
438
- }
439
-
440
- return $value;
441
- }
442
-
443
- /**
444
- * Experimental phpQuery...
445
- */
446
-
447
- /**
448
- * Filter content
449
- * @param string $content
450
- * @return string
451
- */
452
- private function filter_phpquery( $content ) {
453
- // Workaround: remove <head>-attributes before using phpQuery
454
- $regexp_head = '/<head(>|\s(.*?)>)>/is';
455
- $clean_head = '<head>';
456
-
457
- // set simple <head> without attributes
458
- preg_match( $regexp_head, $content, $matches );
459
-
460
- if( count( $matches ) > 0 ) {
461
- $original_head = $matches[ 0 ];
462
- $content = str_replace( $original_head, $clean_head, $content );
463
- }
464
-
465
- //phpQuery::$debug = true;
466
-
467
- // set document
468
- $doc = phpQuery::newDocument( $content );
469
-
470
- $excl_sel = $this->get_opt( 'filter_excl_sel' );
471
-
472
- // set excludes
473
- if ( ! empty( $excl_sel ) ) {
474
- $excludes = $doc->find( $excl_sel );
475
- $excludes->filter( 'a' )->attr( 'excluded', true );
476
- $excludes->find( 'a' )->attr( 'excluded', true );
477
- }
478
-
479
- // get <a>-tags
480
- $links = $doc->find( 'a' );
481
-
482
- // set links
483
- $count = count( $links );
484
-
485
- for( $x = 0; $x < $count; $x++ ) {
486
- $a = $links->eq( $x );
487
-
488
- if ( ! $a->attr( 'excluded' ) )
489
- $this->set_link_phpquery( $links->eq( $x ) );
490
- }
491
-
492
- // remove excluded
493
- if ( ! empty( $excl_sel ) ) {
494
- $excludes = $doc->find( $excl_sel );
495
- $excludes->filter( 'a' )->removeAttr( 'excluded' );
496
- $excludes->find( 'a' )->removeAttr( 'excluded' );
497
- }
498
-
499
- // remove style when no icon classes are found
500
- if ( strpos( $doc, 'ext-icon-' ) === FALSE ) {
501
- // remove icon css
502
- $css = $doc->find( 'link#wp-external-links-css' )->eq(0);
503
- $css->remove();
504
- }
505
-
506
- // get document content
507
- $content = (string) $doc;
508
-
509
- if( isset( $original_head ) ) {
510
- // recover original <head> with attributes
511
- $content = str_replace( $clean_head, $original_head, $content );
512
- }
513
-
514
- return $content;
515
- }
516
-
517
- /**
518
- * Set link...
519
- * @param Node $a
520
- * @return Node
521
- */
522
- public function set_link_phpquery( $a ) {
523
- $href = strtolower( $a->attr( 'href' ) . '' );
524
- $rel = strtolower( $a->attr( 'rel' ) . '' );
525
-
526
- // check if it is an external link and not excluded
527
- if ( ! $this->is_external( $href, $rel ) || $this->is_ignored( $href ) )
528
- return $a;
529
-
530
- // add "external" to rel-attribute
531
- if ( $this->get_opt( 'external' ) ){
532
- $this->add_attr_value_phpquery( $a, 'rel', 'external' );
533
- }
534
-
535
- // add "nofollow" to rel-attribute, when doesn't have "follow"
536
- if ( $this->get_opt( 'nofollow' ) AND strpos( $rel, 'follow' ) === FALSE ){
537
- $this->add_attr_value_phpquery( $a, 'rel', 'nofollow' );
538
- }
539
-
540
- // set title
541
- $title = str_replace( '%title%', $a->attr( 'title' ), $this->get_opt( 'title' ) );
542
- $a->attr( 'title', $title );
543
-
544
- // add icon class, unless no-icon class isset or another icon class ('ext-icon-...') is found
545
- if ( $this->get_opt( 'icon' ) > 0 AND ( ! $this->get_opt( 'no_icon_class' ) OR strpos( $a->attr( 'class' ), $this->get_opt( 'no_icon_class' ) ) === FALSE ) AND strpos( $a->attr( 'class' ), 'ext-icon-' ) === FALSE ){
546
- $icon_class = 'ext-icon-'. $this->get_opt( 'icon' );
547
- $a->addClass( $icon_class );
548
- }
549
-
550
- // add user-defined class
551
- if ( $this->get_opt( 'class_name' ) ){
552
- $a->addClass( $this->get_opt( 'class_name' ) );
553
- }
554
-
555
- // set target
556
- if ( $this->get_opt( 'target' ) != '_none' AND ! $this->get_opt( 'use_js' ) AND ( ! $this->get_opt( 'no_icon_same_window' ) OR ! $this->get_opt( 'no_icon_class' ) OR strpos( $a->attr( 'class' ), $this->get_opt( 'no_icon_class' ) ) === FALSE ) )
557
- $a->attr( 'target', $this->get_opt( 'target' ) );
558
-
559
- return $a;
560
- }
561
-
562
- /**
563
- * Add value to attribute
564
- * @param Node $node
565
- * @param string $attr
566
- * @param string $value
567
- * @return New value
568
- */
569
- private function add_attr_value_phpquery( $node, $attr, $value ) {
570
- $old_value = $node->attr( $attr );
571
-
572
- if ( empty( $old_value ) )
573
- $old_value = '';
574
-
575
- $split = split( ' ', strtolower( $old_value ) );
576
-
577
- if ( in_array( $value, $split ) ) {
578
- $value = $old_value;
579
- } else {
580
- $value = ( empty( $old_value ) )
581
- ? $value
582
- : $old_value .' '. $value;
583
- }
584
-
585
- $node->attr( $attr, $value );
586
-
587
- return $value;
588
- }
589
-
590
- } // End WP_External_Links Class
591
-
592
- endif;
593
-
 
 
 
 
 
 
 
 
 
 
594
  /* ommit PHP closing tag, to prevent unwanted whitespace at the end of the parts generated by the included files */
1
+ <?php defined( 'ABSPATH' ) OR die( 'No direct access.' );
2
+ if ( ! class_exists( 'WP_External_Links' ) ):
3
+
4
+ /**
5
+ * Class WP_External_Links
6
+ * @package WordPress
7
+ * @since
8
+ * @category WordPress Plugins
9
+ */
10
+ final class WP_External_Links {
11
+
12
+ /**
13
+ * Admin object
14
+ * @var Admin_External_Links
15
+ */
16
+ public $admin = NULL;
17
+
18
+ /**
19
+ * Array of ignored links
20
+ * @var type
21
+ */
22
+ private $ignored = array();
23
+
24
+
25
+ /**
26
+ * Constructor
27
+ */
28
+ public function __construct() {
29
+ // set admin object
30
+ $this->admin = new Admin_External_Links();
31
+
32
+ // add actions
33
+ add_action( 'wp', array( $this, 'call_wp' ) );
34
+ }
35
+
36
+ /**
37
+ * Quick helper method for getting saved option values
38
+ * @param string $key
39
+ * @return mixed
40
+ */
41
+ public function get_opt( $key ) {
42
+ $lookup = $this->admin->save_options;
43
+
44
+ foreach ( $lookup as $option_name => $values ) {
45
+ $value = $this->admin->form->value( $key, '___NONE___', $option_name );
46
+
47
+ if ($value !== '___NONE___')
48
+ return $value;
49
+ }
50
+
51
+ throw new Exception('Option with key "' . $key . '" does not exist.');
52
+ }
53
+
54
+ /**
55
+ * wp callback
56
+ */
57
+ public function call_wp() {
58
+ if ( ! is_admin() && ! is_feed() ) {
59
+ // add wp_head for setting js vars and css style
60
+ add_action( 'wp_head', array( $this, 'call_wp_head' ) );
61
+
62
+ // set js file
63
+ if ( $this->get_opt( 'use_js' ) )
64
+ wp_enqueue_script( 'wp-external-links', plugins_url( 'js/wp-external-links.js', WP_EXTERNAL_LINKS_FILE ), array(), WP_EXTERNAL_LINKS_VERSION, (bool) $this->get_opt( 'load_in_footer' ) );
65
+
66
+ // filters
67
+ if ( $this->get_opt( 'filter_page' ) ) {
68
+ // filter body
69
+ ob_start( array( $this, 'call_filter_content' ) );
70
+
71
+ // set ob flush
72
+ add_action('wp_footer', array($this, 'callback_flush_buffer'), 10000);
73
+
74
+ } else {
75
+ // set filter priority
76
+ $priority = 1000000000;
77
+
78
+ // content
79
+ if ( $this->get_opt( 'filter_posts' ) ) {
80
+ add_filter( 'the_title', array( $this, 'call_filter_content' ), $priority );
81
+ add_filter( 'the_content', array( $this, 'call_filter_content' ), $priority );
82
+ add_filter( 'get_the_excerpt', array( $this, 'call_filter_content' ), $priority );
83
+ // redundant:
84
+ //add_filter( 'the_excerpt', array( $this, 'call_filter_content' ), $priority );
85
+ }
86
+
87
+ // comments
88
+ if ( $this->get_opt( 'filter_comments' ) ) {
89
+ add_filter( 'get_comment_text', array( $this, 'call_filter_content' ), $priority );
90
+ // redundant:
91
+ //add_filter( 'comment_text', array( $this, 'call_filter_content' ), $priority );
92
+
93
+ add_filter( 'comment_excerpt', array( $this, 'call_filter_content' ), $priority );
94
+ // redundant:
95
+ //add_filter( 'get_comment_excerpt', array( $this, 'call_filter_content' ), $priority );
96
+
97
+ add_filter( 'comment_url', array( $this, 'call_filter_content' ), $priority );
98
+ add_filter( 'get_comment_author_url', array( $this, 'call_filter_content' ), $priority );
99
+ add_filter( 'get_comment_author_link', array( $this, 'call_filter_content' ), $priority );
100
+ add_filter( 'get_comment_author_url_link', array( $this, 'call_filter_content' ), $priority );
101
+ }
102
+
103
+ // widgets
104
+ if ( $this->get_opt( 'filter_widgets' ) ) {
105
+ if ( $this->admin->check_widget_content_filter() ) {
106
+ // only if Widget Logic plugin is installed and 'widget_content' option is activated
107
+ add_filter( 'widget_content', array( $this, 'call_filter_content' ), $priority );
108
+ } else {
109
+ // filter text widgets
110
+ add_filter( 'widget_title', array( $this, 'call_filter_content' ), $priority );
111
+ add_filter( 'widget_text', array( $this, 'call_filter_content' ), $priority );
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ // hook
118
+ do_action('wpel_ready', array($this, 'call_filter_content'), $this);
119
+ }
120
+
121
+ /**
122
+ * End output buffer
123
+ */
124
+ public function callback_flush_buffer() {
125
+ ob_end_flush();
126
+ }
127
+
128
+ /**
129
+ * wp_head callback
130
+ */
131
+ public function call_wp_head() {
132
+ // set ignored
133
+ $ignored = $this->get_opt( 'ignore' );
134
+ $ignored = trim( $ignored );
135
+ $ignored = explode( "\n", $ignored );
136
+ $ignored = array_map( 'trim', $ignored );
137
+ $ignored = array_map( 'strtolower', $ignored );
138
+ $this->ignored = $ignored;
139
+
140
+ $icon = $this->get_opt('icon');
141
+
142
+ if ($icon) {
143
+ $padding = ($icon < 20) ? 15 : 12;
144
+ ?>
145
+ <style type="text/css" media="screen">
146
+ /* WP External Links Plugin */
147
+ .ext-icon-<?php echo $icon ?> { background:url(<?php echo plugins_url('/images/ext-icons/ext-icon-' . $icon . '.png', WP_EXTERNAL_LINKS_FILE) ?>) no-repeat 100% 50%; padding-right:<?php echo $padding ?>px; }';
148
+ </style>
149
+ <?php
150
+ }
151
+
152
+ if ( $this->get_opt( 'use_js' ) AND $this->get_opt( 'target' ) != '_none' ):
153
+ // set exclude class
154
+ $excludeClass = ( $this->get_opt( 'no_icon_same_window' ) AND $this->get_opt( 'no_icon_class' ) )
155
+ ? $this->get_opt( 'no_icon_class' )
156
+ : '';
157
+ ?>
158
+ <script type="text/javascript">/* <![CDATA[ */
159
+ /* WP External Links Plugin */
160
+ var wpExtLinks = { baseUrl: '<?php echo get_bloginfo( 'wpurl' ) ?>', target: '<?php echo $this->get_opt( 'target' ) ?>', excludeClass: '<?php echo $excludeClass ?>' };
161
+ /* ]]> */</script>
162
+ <?php
163
+ endif;
164
+ }
165
+
166
+ /**
167
+ * Filter content
168
+ * @param string $content
169
+ * @return string
170
+ */
171
+ public function call_filter_content( $content ) {
172
+ if ( $this->get_opt( 'fix_js' ) ) {
173
+ // fix js problem by replacing </a> by <\/a>
174
+ $content = preg_replace_callback( '/<script([^>]*)>(.*?)<\/script[^>]*>/is', array( $this, 'call_fix_js' ), $content );
175
+ }
176
+
177
+ if ( $this->get_opt( 'phpquery' ) ) {
178
+ // Include phpQuery
179
+ if ( ! class_exists( 'phpQuery' ) ) {
180
+ require_once( 'phpQuery.php' );
181
+ }
182
+
183
+ return $this->filter_phpquery( $content );
184
+ } else {
185
+ return $this->filter( $content );
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Fix </a> in JavaScript blocks (callback for regexp)
191
+ * @param array $matches Result of a preg call in filter_content()
192
+ * @return string Clean code
193
+ */
194
+ public function call_fix_js( $matches ) {
195
+ return str_replace( '</a>', '<\/a>', $matches[ 0 ] );
196
+ }
197
+
198
+ /**
199
+ * Check if link is external
200
+ * @param string $href
201
+ * @param string $rel
202
+ * @return boolean
203
+ */
204
+ private function is_external( $href, $rel ) {
205
+ return ( isset( $href ) AND ( ( strpos( $href, strtolower( get_bloginfo( 'wpurl' ) ) ) === FALSE )
206
+ AND ( substr( $href, 0, 7 ) == 'http://'
207
+ OR substr( $href, 0, 8 ) == 'https://'
208
+ OR substr( $href, 0, 6 ) == 'ftp://'
209
+ OR substr( $href, 0, 2 ) == '//' ) ) );
210
+ }
211
+
212
+ /**
213
+ * Is an ignored link
214
+ * @param string $href
215
+ * @return boolean
216
+ */
217
+ private function is_ignored( $href ) {
218
+ // check if this links should be ignored
219
+ for ( $x = 0, $count = count($this->ignored); $x < $count; $x++ ) {
220
+ if ( strrpos( $href, $this->ignored[ $x ] ) !== FALSE )
221
+ return TRUE;
222
+ }
223
+
224
+ return FALSE;
225
+ }
226
+
227
+ /**
228
+ * Filter content
229
+ * @param string $content
230
+ * @return string
231
+ */
232
+ private function filter( $content ) {
233
+ // replace links
234
+ $content = preg_replace_callback( '/<a[^A-Za-z](.*?)>(.*?)<\/a[\s+]*>/is', array( $this, 'call_parse_link' ), $content );
235
+
236
+ // remove style when no icon classes are found
237
+ if ( strpos( $content, 'ext-icon-' ) === FALSE ) {
238
+ // remove style with id wp-external-links-css
239
+ $content = preg_replace( '/<link ([^>]*)wp-external-links-css([^>]*)\/>[\s+]*/i', '', $content );
240
+ }
241
+
242
+ return $content;
243
+ }
244
+
245
+ /**
246
+ * Parse an attributes string into an array. If the string starts with a tag,
247
+ * then the attributes on the first tag are parsed. This parses via a manual
248
+ * loop and is designed to be safer than using DOMDocument.
249
+ *
250
+ * @param string|* $attrs
251
+ * @return array
252
+ *
253
+ * @example parse_attrs( 'src="example.jpg" alt="example"' )
254
+ * @example parse_attrs( '<img src="example.jpg" alt="example">' )
255
+ * @example parse_attrs( '<a href="example"></a>' )
256
+ * @example parse_attrs( '<a href="example">' )
257
+ *
258
+ * @link http://dev.airve.com/demo/speed_tests/php/parse_attrs.php
259
+ */
260
+ private function parse_attrs ($attrs) {
261
+ if ( ! is_scalar($attrs) )
262
+ return (array) $attrs;
263
+
264
+ $attrs = str_split( trim($attrs) );
265
+
266
+ if ( '<' === $attrs[0] ) # looks like a tag so strip the tagname
267
+ while ( $attrs && ! ctype_space($attrs[0]) && $attrs[0] !== '>' )
268
+ array_shift($attrs);
269
+
270
+ $arr = array(); # output
271
+ $name = ''; # for the current attr being parsed
272
+ $value = ''; # for the current attr being parsed
273
+ $mode = 0; # whether current char is part of the name (-), the value (+), or neither (0)
274
+ $stop = false; # delimiter for the current $value being parsed
275
+ $space = ' '; # a single space
276
+
277
+ foreach ( $attrs as $j => $curr ) {
278
+ if ( $mode < 0 ) {# name
279
+ if ( '=' === $curr ) {
280
+ $mode = 1;
281
+ $stop = false;
282
+ } elseif ( '>' === $curr ) {
283
+ '' === $name or $arr[ $name ] = $value;
284
+ break;
285
+ } elseif ( ! ctype_space($curr) ) {
286
+ if ( ctype_space( $attrs[ $j - 1 ] ) ) { # previous char
287
+ '' === $name or $arr[ $name ] = ''; # previous name
288
+ $name = $curr; # initiate new
289
+ } else {
290
+ $name .= $curr;
291
+ }
292
+ }
293
+ } elseif ( $mode > 0 ) {# value
294
+
295
+ if ( $stop === false ) {
296
+ if ( ! ctype_space($curr) ) {
297
+ if ( '"' === $curr || "'" === $curr ) {
298
+ $value = '';
299
+ $stop = $curr;
300
+ } else {
301
+ $value = $curr;
302
+ $stop = $space;
303
+ }
304
+ }
305
+ } elseif ( $stop === $space ? ctype_space($curr) : $curr === $stop ) {
306
+ $arr[ $name ] = $value;
307
+ $mode = 0;
308
+ $name = $value = '';
309
+ } else {
310
+ $value .= $curr;
311
+ }
312
+ } else {# neither
313
+
314
+ if ( '>' === $curr )
315
+ break;
316
+ if ( ! ctype_space( $curr ) ) {
317
+ # initiate
318
+ $name = $curr;
319
+ $mode = -1;
320
+ }
321
+ }
322
+ }
323
+
324
+ # incl the final pair if it was quoteless
325
+ '' === $name or $arr[ $name ] = $value;
326
+
327
+ return $arr;
328
+ }
329
+
330
+ /**
331
+ * Make a clean <a> code (callback for regexp)
332
+ * @param array $matches Result of a preg call in filter_content()
333
+ * @return string Clean <a> code
334
+ */
335
+ public function call_parse_link( $matches ) {
336
+ $link = $matches[ 0 ];
337
+ $label = $matches[ 2 ];
338
+ $created_link = $link;
339
+
340
+ // parse attributes
341
+ $attrs = $matches[ 1 ];
342
+ $attrs = stripslashes( $attrs );
343
+ $attrs = $this->parse_attrs( $attrs );
344
+
345
+ $rel = ( isset( $attrs[ 'rel' ] ) ) ? strtolower( $attrs[ 'rel' ] ) : '';
346
+
347
+ // href preperation
348
+ $href = $attrs[ 'href' ];
349
+ $href = strtolower( $href );
350
+ $href = trim( $href );
351
+
352
+ // checks
353
+ $is_external = $this->is_external( $href, $rel );
354
+ $is_ignored = $this->is_ignored( $href );
355
+
356
+ // is an internal link?
357
+ // rel=external will be threaded as external link
358
+ if ( ! $is_external && strpos( $rel, 'external' ) === FALSE) {
359
+ return apply_filters('wpel_internal_link', $created_link, $label, $attrs);
360
+ }
361
+
362
+ if ( $is_ignored ) {
363
+ return apply_filters('wpel_external_link', $created_link, $link, $label, $attrs, TRUE);
364
+ }
365
+
366
+ // set rel="external" (when not already set)
367
+ if ( $this->get_opt( 'external' ) )
368
+ $this->add_attr_value( $attrs, 'rel', 'external' );
369
+
370
+ // set rel="nofollow" when doesn't have "follow" (or already "nofollow")
371
+ if ( $this->get_opt( 'nofollow' ) AND strpos( $rel, 'follow' ) === FALSE )
372
+ $this->add_attr_value( $attrs, 'rel', 'nofollow' );
373
+
374
+ // set title
375
+ $title_format = $this->get_opt( 'title' );
376
+ $title = ( isset( $attrs[ 'title' ] ) ) ? $attrs[ 'title' ] : '';
377
+ $attrs[ 'title' ] = str_replace( '%title%', $title, $title_format );
378
+
379
+ // set user-defined class
380
+ $class = $this->get_opt( 'class_name' );
381
+ if ( $class )
382
+ $this->add_attr_value( $attrs, 'class', $class );
383
+
384
+ // set icon class, unless no-icon class isset or another icon class ('ext-icon-...') is found or content contains image
385
+ if ( $this->get_opt( 'icon' ) > 0
386
+ AND ( ! $this->get_opt( 'no_icon_class' ) OR strpos( $attrs[ 'class' ], $this->get_opt( 'no_icon_class' ) ) === FALSE )
387
+ AND strpos( $attrs[ 'class' ], 'ext-icon-' ) === FALSE
388
+ AND !( $this->get_opt( 'image_no_icon' ) AND (bool) preg_match( '/<img([^>]*)>/is', $label )) ){
389
+ $icon_class = 'ext-icon-'. $this->get_opt( 'icon', 'style' );
390
+ $this->add_attr_value( $attrs, 'class', $icon_class );
391
+ }
392
+
393
+ // set target
394
+ if ( ! $this->get_opt( 'use_js' ) AND ( ! $this->get_opt( 'no_icon_same_window' )
395
+ OR ! $this->get_opt( 'no_icon_class' )
396
+ OR strpos( $attrs[ 'class' ], $this->get_opt( 'no_icon_class' ) ) === FALSE ) ) {
397
+ if ( $this->get_opt( 'target' ) == '_none' ) {
398
+ unset( $attrs[ 'target' ] );
399
+ } else {
400
+ $attrs[ 'target' ] = $this->get_opt( 'target' );
401
+ }
402
+ }
403
+
404
+ // create element code
405
+ $created_link = '<a';
406
+
407
+ foreach ( $attrs AS $key => $value ) {
408
+ $created_link .= ' '. $key .'="'. $value .'"';
409
+ }
410
+
411
+ $created_link .= '>'. $label .'</a>';
412
+
413
+ // filter
414
+ $created_link = apply_filters('wpel_external_link', $created_link, $link, $label, $attrs, FALSE);
415
+
416
+ return $created_link;
417
+ }
418
+
419
+ /**
420
+ * Add value to attribute
421
+ * @param array $attrs
422
+ * @param string $attr
423
+ * @param string $value
424
+ * @param string $default Optional, default NULL which means tje attribute will be removed when (new) value is empty
425
+ * @return New value
426
+ */
427
+ public function add_attr_value( &$attrs, $attr_name, $value, $default = NULL ) {
428
+ if ( key_exists( $attr_name, $attrs ) )
429
+ $old_value = $attrs[ $attr_name ];
430
+
431
+ if ( empty( $old_value ) )
432
+ $old_value = '';
433
+
434
+ $split = explode( ' ', strtolower( $old_value ) );
435
+
436
+ if ( in_array( $value, $split ) ) {
437
+ $value = $old_value;
438
+ } else {
439
+ $value = ( empty( $old_value ) )
440
+ ? $value
441
+ : $old_value .' '. $value;
442
+ }
443
+
444
+ if ( empty( $value ) AND $default === NULL ) {
445
+ unset( $attrs[ $attr_name ] );
446
+ } else {
447
+ $attrs[ $attr_name ] = $value;
448
+ }
449
+
450
+ return $value;
451
+ }
452
+
453
+ /**
454
+ * Experimental phpQuery...
455
+ */
456
+
457
+ /**
458
+ * Filter content
459
+ * @param string $content
460
+ * @return string
461
+ */
462
+ private function filter_phpquery( $content ) {
463
+ // Workaround: remove <head>-attributes before using phpQuery
464
+ $regexp_head = '/<head(>|\s(.*?)>)>/is';
465
+ $clean_head = '<head>';
466
+
467
+ // set simple <head> without attributes
468
+ preg_match( $regexp_head, $content, $matches );
469
+
470
+ if( count( $matches ) > 0 ) {
471
+ $original_head = $matches[ 0 ];
472
+ $content = str_replace( $original_head, $clean_head, $content );
473
+ }
474
+
475
+ //phpQuery::$debug = true;
476
+
477
+ // set document
478
+ $doc = phpQuery::newDocument( $content );
479
+
480
+ $excl_sel = $this->get_opt( 'filter_excl_sel' );
481
+
482
+ // set excludes
483
+ if ( ! empty( $excl_sel ) ) {
484
+ $excludes = $doc->find( $excl_sel );
485
+ $excludes->filter( 'a' )->attr( 'excluded', true );
486
+ $excludes->find( 'a' )->attr( 'excluded', true );
487
+ }
488
+
489
+ // get <a>-tags
490
+ $links = $doc->find( 'a' );
491
+
492
+ // set links
493
+ $count = count( $links );
494
+
495
+ for( $x = 0; $x < $count; $x++ ) {
496
+ $a = $links->eq( $x );
497
+
498
+ if ( ! $a->attr( 'excluded' ) )
499
+ $this->set_link_phpquery( $links->eq( $x ) );
500
+ }
501
+
502
+ // remove excluded
503
+ if ( ! empty( $excl_sel ) ) {
504
+ $excludes = $doc->find( $excl_sel );
505
+ $excludes->filter( 'a' )->removeAttr( 'excluded' );
506
+ $excludes->find( 'a' )->removeAttr( 'excluded' );
507
+ }
508
+
509
+ // remove style when no icon classes are found
510
+ if ( strpos( $doc, 'ext-icon-' ) === FALSE ) {
511
+ // remove icon css
512
+ $css = $doc->find( 'link#wp-external-links-css' )->eq(0);
513
+ $css->remove();
514
+ }
515
+
516
+ // get document content
517
+ $content = (string) $doc;
518
+
519
+ if( isset( $original_head ) ) {
520
+ // recover original <head> with attributes
521
+ $content = str_replace( $clean_head, $original_head, $content );
522
+ }
523
+
524
+ return $content;
525
+ }
526
+
527
+ /**
528
+ * Set link...
529
+ * @param Node $a
530
+ * @return Node
531
+ */
532
+ public function set_link_phpquery( $a ) {
533
+ $href = strtolower( $a->attr( 'href' ) . '' );
534
+ $rel = strtolower( $a->attr( 'rel' ) . '' );
535
+
536
+ // check if it is an external link and not excluded
537
+ if ( ! $this->is_external( $href, $rel ) || $this->is_ignored( $href ) )
538
+ return $a;
539
+
540
+ // add "external" to rel-attribute
541
+ if ( $this->get_opt( 'external' ) ){
542
+ $this->add_attr_value_phpquery( $a, 'rel', 'external' );
543
+ }
544
+
545
+ // add "nofollow" to rel-attribute, when doesn't have "follow"
546
+ if ( $this->get_opt( 'nofollow' ) AND strpos( $rel, 'follow' ) === FALSE ){
547
+ $this->add_attr_value_phpquery( $a, 'rel', 'nofollow' );
548
+ }
549
+
550
+ // set title
551
+ $title = str_replace( '%title%', $a->attr( 'title' ), $this->get_opt( 'title' ) );
552
+ $a->attr( 'title', $title );
553
+
554
+ // add icon class, unless no-icon class isset or another icon class ('ext-icon-...') is found
555
+ if ( $this->get_opt( 'icon' ) > 0 AND ( ! $this->get_opt( 'no_icon_class' ) OR strpos( $a->attr( 'class' ), $this->get_opt( 'no_icon_class' ) ) === FALSE ) AND strpos( $a->attr( 'class' ), 'ext-icon-' ) === FALSE ){
556
+ $icon_class = 'ext-icon-'. $this->get_opt( 'icon' );
557
+ $a->addClass( $icon_class );
558
+ }
559
+
560
+ // add user-defined class
561
+ if ( $this->get_opt( 'class_name' ) ){
562
+ $a->addClass( $this->get_opt( 'class_name' ) );
563
+ }
564
+
565
+ // set target
566
+ if ( $this->get_opt( 'target' ) != '_none' AND ! $this->get_opt( 'use_js' ) AND ( ! $this->get_opt( 'no_icon_same_window' ) OR ! $this->get_opt( 'no_icon_class' ) OR strpos( $a->attr( 'class' ), $this->get_opt( 'no_icon_class' ) ) === FALSE ) )
567
+ $a->attr( 'target', $this->get_opt( 'target' ) );
568
+
569
+ return $a;
570
+ }
571
+
572
+ /**
573
+ * Add value to attribute
574
+ * @param Node $node
575
+ * @param string $attr
576
+ * @param string $value
577
+ * @return New value
578
+ */
579
+ private function add_attr_value_phpquery( $node, $attr, $value ) {
580
+ $old_value = $node->attr( $attr );
581
+
582
+ if ( empty( $old_value ) )
583
+ $old_value = '';
584
+
585
+ $split = split( ' ', strtolower( $old_value ) );
586
+
587
+ if ( in_array( $value, $split ) ) {
588
+ $value = $old_value;
589
+ } else {
590
+ $value = ( empty( $old_value ) )
591
+ ? $value
592
+ : $old_value .' '. $value;
593
+ }
594
+
595
+ $node->attr( $attr, $value );
596
+
597
+ return $value;
598
+ }
599
+
600
+ } // End WP_External_Links Class
601
+
602
+ endif;
603
+
604
  /* ommit PHP closing tag, to prevent unwanted whitespace at the end of the parts generated by the included files */
includes/phpQuery.php CHANGED
@@ -1,5702 +1,5702 @@
1
- <?php
2
- /**
3
- * phpQuery is a server-side, chainable, CSS3 selector driven
4
- * Document Object Model (DOM) API based on jQuery JavaScript Library.
5
- *
6
- * @version 0.9.5
7
- * @link http://code.google.com/p/phpquery/
8
- * @link http://phpquery-library.blogspot.com/
9
- * @link http://jquery.com/
10
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
11
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
12
- * @package phpQuery
13
- */
14
-
15
- // class names for instanceof
16
- // TODO move them as class constants into phpQuery
17
- define('DOMDOCUMENT', 'DOMDocument');
18
- define('DOMELEMENT', 'DOMElement');
19
- define('DOMNODELIST', 'DOMNodeList');
20
- define('DOMNODE', 'DOMNode');
21
-
22
- /**
23
- * DOMEvent class.
24
- *
25
- * Based on
26
- * @link http://developer.mozilla.org/En/DOM:event
27
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
28
- * @package phpQuery
29
- * @todo implement ArrayAccess ?
30
- */
31
- class DOMEvent {
32
- /**
33
- * Returns a boolean indicating whether the event bubbles up through the DOM or not.
34
- *
35
- * @var unknown_type
36
- */
37
- public $bubbles = true;
38
- /**
39
- * Returns a boolean indicating whether the event is cancelable.
40
- *
41
- * @var unknown_type
42
- */
43
- public $cancelable = true;
44
- /**
45
- * Returns a reference to the currently registered target for the event.
46
- *
47
- * @var unknown_type
48
- */
49
- public $currentTarget;
50
- /**
51
- * Returns detail about the event, depending on the type of event.
52
- *
53
- * @var unknown_type
54
- * @link http://developer.mozilla.org/en/DOM/event.detail
55
- */
56
- public $detail; // ???
57
- /**
58
- * Used to indicate which phase of the event flow is currently being evaluated.
59
- *
60
- * NOT IMPLEMENTED
61
- *
62
- * @var unknown_type
63
- * @link http://developer.mozilla.org/en/DOM/event.eventPhase
64
- */
65
- public $eventPhase; // ???
66
- /**
67
- * The explicit original target of the event (Mozilla-specific).
68
- *
69
- * NOT IMPLEMENTED
70
- *
71
- * @var unknown_type
72
- */
73
- public $explicitOriginalTarget; // moz only
74
- /**
75
- * The original target of the event, before any retargetings (Mozilla-specific).
76
- *
77
- * NOT IMPLEMENTED
78
- *
79
- * @var unknown_type
80
- */
81
- public $originalTarget; // moz only
82
- /**
83
- * Identifies a secondary target for the event.
84
- *
85
- * @var unknown_type
86
- */
87
- public $relatedTarget;
88
- /**
89
- * Returns a reference to the target to which the event was originally dispatched.
90
- *
91
- * @var unknown_type
92
- */
93
- public $target;
94
- /**
95
- * Returns the time that the event was created.
96
- *
97
- * @var unknown_type
98
- */
99
- public $timeStamp;
100
- /**
101
- * Returns the name of the event (case-insensitive).
102
- */
103
- public $type;
104
- public $runDefault = true;
105
- public $data = null;
106
- public function __construct($data) {
107
- foreach($data as $k => $v) {
108
- $this->$k = $v;
109
- }
110
- if (! $this->timeStamp)
111
- $this->timeStamp = time();
112
- }
113
- /**
114
- * Cancels the event (if it is cancelable).
115
- *
116
- */
117
- public function preventDefault() {
118
- $this->runDefault = false;
119
- }
120
- /**
121
- * Stops the propagation of events further along in the DOM.
122
- *
123
- */
124
- public function stopPropagation() {
125
- $this->bubbles = false;
126
- }
127
- }
128
-
129
-
130
- /**
131
- * DOMDocumentWrapper class simplifies work with DOMDocument.
132
- *
133
- * Know bug:
134
- * - in XHTML fragments, <br /> changes to <br clear="none" />
135
- *
136
- * @todo check XML catalogs compatibility
137
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
138
- * @package phpQuery
139
- */
140
- class DOMDocumentWrapper {
141
- /**
142
- * @var DOMDocument
143
- */
144
- public $document;
145
- public $id;
146
- /**
147
- * @todo Rewrite as method and quess if null.
148
- * @var unknown_type
149
- */
150
- public $contentType = '';
151
- public $xpath;
152
- public $uuid = 0;
153
- public $data = array();
154
- public $dataNodes = array();
155
- public $events = array();
156
- public $eventsNodes = array();
157
- public $eventsGlobal = array();
158
- /**
159
- * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28
160
- * @var unknown_type
161
- */
162
- public $frames = array();
163
- /**
164
- * Document root, by default equals to document itself.
165
- * Used by documentFragments.
166
- *
167
- * @var DOMNode
168
- */
169
- public $root;
170
- public $isDocumentFragment;
171
- public $isXML = false;
172
- public $isXHTML = false;
173
- public $isHTML = false;
174
- public $charset;
175
- public function __construct($markup = null, $contentType = null, $newDocumentID = null) {
176
- if (isset($markup))
177
- $this->load($markup, $contentType, $newDocumentID);
178
- $this->id = $newDocumentID
179
- ? $newDocumentID
180
- : md5(microtime());
181
- }
182
- public function load($markup, $contentType = null, $newDocumentID = null) {
183
- // phpQuery::$documents[$id] = $this;
184
- $this->contentType = strtolower($contentType);
185
- if ($markup instanceof DOMDOCUMENT) {
186
- $this->document = $markup;
187
- $this->root = $this->document;
188
- $this->charset = $this->document->encoding;
189
- // TODO isDocumentFragment
190
- } else {
191
- $loaded = $this->loadMarkup($markup);
192
- }
193
- if ($loaded) {
194
- // $this->document->formatOutput = true;
195
- $this->document->preserveWhiteSpace = true;
196
- $this->xpath = new DOMXPath($this->document);
197
- $this->afterMarkupLoad();
198
- return true;
199
- // remember last loaded document
200
- // return phpQuery::selectDocument($id);
201
- }
202
- return false;
203
- }
204
- protected function afterMarkupLoad() {
205
- if ($this->isXHTML) {
206
- $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml");
207
- }
208
- }
209
- protected function loadMarkup($markup) {
210
- $loaded = false;
211
- if ($this->contentType) {
212
- self::debug("Load markup for content type {$this->contentType}");
213
- // content determined by contentType
214
- list($contentType, $charset) = $this->contentTypeToArray($this->contentType);
215
- switch($contentType) {
216
- case 'text/html':
217
- phpQuery::debug("Loading HTML, content type '{$this->contentType}'");
218
- $loaded = $this->loadMarkupHTML($markup, $charset);
219
- break;
220
- case 'text/xml':
221
- case 'application/xhtml+xml':
222
- phpQuery::debug("Loading XML, content type '{$this->contentType}'");
223
- $loaded = $this->loadMarkupXML($markup, $charset);
224
- break;
225
- default:
226
- // for feeds or anything that sometimes doesn't use text/xml
227
- if (strpos('xml', $this->contentType) !== false) {
228
- phpQuery::debug("Loading XML, content type '{$this->contentType}'");
229
- $loaded = $this->loadMarkupXML($markup, $charset);
230
- } else
231
- phpQuery::debug("Could not determine document type from content type '{$this->contentType}'");
232
- }
233
- } else {
234
- // content type autodetection
235
- if ($this->isXML($markup)) {
236
- phpQuery::debug("Loading XML, isXML() == true");
237
- $loaded = $this->loadMarkupXML($markup);
238
- if (! $loaded && $this->isXHTML) {
239
- phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true');
240
- $loaded = $this->loadMarkupHTML($markup);
241
- }
242
- } else {
243
- phpQuery::debug("Loading HTML, isXML() == false");
244
- $loaded = $this->loadMarkupHTML($markup);
245
- }
246
- }
247
- return $loaded;
248
- }
249
- protected function loadMarkupReset() {
250
- $this->isXML = $this->isXHTML = $this->isHTML = false;
251
- }
252
- protected function documentCreate($charset, $version = '1.0') {
253
- if (! $version)
254
- $version = '1.0';
255
- $this->document = new DOMDocument($version, $charset);
256
- $this->charset = $this->document->encoding;
257
- // $this->document->encoding = $charset;
258
- $this->document->formatOutput = true;
259
- $this->document->preserveWhiteSpace = true;
260
- }
261
- protected function loadMarkupHTML($markup, $requestedCharset = null) {
262
- if (phpQuery::$debug)
263
- phpQuery::debug('Full markup load (HTML): '.substr($markup, 0, 250));
264
- $this->loadMarkupReset();
265
- $this->isHTML = true;
266
- if (!isset($this->isDocumentFragment))
267
- $this->isDocumentFragment = self::isDocumentFragmentHTML($markup);
268
- $charset = null;
269
- $documentCharset = $this->charsetFromHTML($markup);
270
- $addDocumentCharset = false;
271
- if ($documentCharset) {
272
- $charset = $documentCharset;
273
- $markup = $this->charsetFixHTML($markup);
274
- } else if ($requestedCharset) {
275
- $charset = $requestedCharset;
276
- }
277
- if (! $charset)
278
- $charset = phpQuery::$defaultCharset;
279
- // HTTP 1.1 says that the default charset is ISO-8859-1
280
- // @see http://www.w3.org/International/O-HTTP-charset
281
- if (! $documentCharset) {
282
- $documentCharset = 'ISO-8859-1';
283
- $addDocumentCharset = true;
284
- }
285
- // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding'
286
- // Worse, some pages can have mixed encodings... we'll try not to worry about that
287
- $requestedCharset = strtoupper($requestedCharset);
288
- $documentCharset = strtoupper($documentCharset);
289
- phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset");
290
- if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) {
291
- phpQuery::debug("CHARSET CONVERT");
292
- // Document Encoding Conversion
293
- // http://code.google.com/p/phpquery/issues/detail?id=86
294
- if (function_exists('mb_detect_encoding')) {
295
- $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO');
296
- $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets));
297
- if (! $docEncoding)
298
- $docEncoding = $documentCharset; // ok trust the document
299
- phpQuery::debug("DETECTED '$docEncoding'");
300
- // Detected does not match what document says...
301
- if ($docEncoding !== $documentCharset) {
302
- // Tricky..
303
- }
304
- if ($docEncoding !== $requestedCharset) {
305
- phpQuery::debug("CONVERT $docEncoding => $requestedCharset");
306
- $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding);
307
- $markup = $this->charsetAppendToHTML($markup, $requestedCharset);
308
- $charset = $requestedCharset;
309
- }
310
- } else {
311
- phpQuery::debug("TODO: charset conversion without mbstring...");
312
- }
313
- }
314
- $return = false;
315
- if ($this->isDocumentFragment) {
316
- phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'");
317
- $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
318
- } else {
319
- if ($addDocumentCharset) {
320
- phpQuery::debug("Full markup load (HTML), appending charset: '$charset'");
321
- $markup = $this->charsetAppendToHTML($markup, $charset);
322
- }
323
- phpQuery::debug("Full markup load (HTML), documentCreate('$charset')");
324
- $this->documentCreate($charset);
325
- $return = phpQuery::$debug === 2
326
- ? $this->document->loadHTML($markup)
327
- : @$this->document->loadHTML($markup);
328
- if ($return)
329
- $this->root = $this->document;
330
- }
331
- if ($return && ! $this->contentType)
332
- $this->contentType = 'text/html';
333
- return $return;
334
- }
335
- protected function loadMarkupXML($markup, $requestedCharset = null) {
336
- if (phpQuery::$debug)
337
- phpQuery::debug('Full markup load (XML): '.substr($markup, 0, 250));
338
- $this->loadMarkupReset();
339
- $this->isXML = true;
340
- // check agains XHTML in contentType or markup
341
- $isContentTypeXHTML = $this->isXHTML();
342
- $isMarkupXHTML = $this->isXHTML($markup);
343
- if ($isContentTypeXHTML || $isMarkupXHTML) {
344
- self::debug('Full markup load (XML), XHTML detected');
345
- $this->isXHTML = true;
346
- }
347
- // determine document fragment
348
- if (! isset($this->isDocumentFragment))
349
- $this->isDocumentFragment = $this->isXHTML
350
- ? self::isDocumentFragmentXHTML($markup)
351
- : self::isDocumentFragmentXML($markup);
352
- // this charset will be used
353
- $charset = null;
354
- // charset from XML declaration @var string
355
- $documentCharset = $this->charsetFromXML($markup);
356
- if (! $documentCharset) {
357
- if ($this->isXHTML) {
358
- // this is XHTML, try to get charset from content-type meta header
359
- $documentCharset = $this->charsetFromHTML($markup);
360
- if ($documentCharset) {
361
- phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'");
362
- $this->charsetAppendToXML($markup, $documentCharset);
363
- $charset = $documentCharset;
364
- }
365
- }
366
- if (! $documentCharset) {
367
- // if still no document charset...
368
- $charset = $requestedCharset;
369
- }
370
- } else if ($requestedCharset) {
371
- $charset = $requestedCharset;
372
- }
373
- if (! $charset) {
374
- $charset = phpQuery::$defaultCharset;
375
- }
376
- if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) {
377
- // TODO place for charset conversion
378
- // $charset = $requestedCharset;
379
- }
380
- $return = false;
381
- if ($this->isDocumentFragment) {
382
- phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'");
383
- $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
384
- } else {
385
- // FIXME ???
386
- if ($isContentTypeXHTML && ! $isMarkupXHTML)
387
- if (! $documentCharset) {
388
- phpQuery::debug("Full markup load (XML), appending charset '$charset'");
389
- $markup = $this->charsetAppendToXML($markup, $charset);
390
- }
391
- // see http://pl2.php.net/manual/en/book.dom.php#78929
392
- // LIBXML_DTDLOAD (>= PHP 5.1)
393
- // does XML ctalogues works with LIBXML_NONET
394
- // $this->document->resolveExternals = true;
395
- // TODO test LIBXML_COMPACT for performance improvement
396
- // create document
397
- $this->documentCreate($charset);
398
- if (phpversion() < 5.1) {
399
- $this->document->resolveExternals = true;
400
- $return = phpQuery::$debug === 2
401
- ? $this->document->loadXML($markup)
402
- : @$this->document->loadXML($markup);
403
- } else {
404
- /** @link http://pl2.php.net/manual/en/libxml.constants.php */
405
- $libxmlStatic = phpQuery::$debug === 2
406
- ? LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET
407
- : LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOERROR;
408
- $return = $this->document->loadXML($markup, $libxmlStatic);
409
- // if (! $return)
410
- // $return = $this->document->loadHTML($markup);
411
- }
412
- if ($return)
413
- $this->root = $this->document;
414
- }
415
- if ($return) {
416
- if (! $this->contentType) {
417
- if ($this->isXHTML)
418
- $this->contentType = 'application/xhtml+xml';
419
- else
420
- $this->contentType = 'text/xml';
421
- }
422
- return $return;
423
- } else {
424
- throw new Exception("Error loading XML markup");
425
- }
426
- }
427
- protected function isXHTML($markup = null) {
428
- if (! isset($markup)) {
429
- return strpos($this->contentType, 'xhtml') !== false;
430
- }
431
- // XXX ok ?
432
- return strpos($markup, "<!DOCTYPE html") !== false;
433
- // return stripos($doctype, 'xhtml') !== false;
434
- // $doctype = isset($dom->doctype) && is_object($dom->doctype)
435
- // ? $dom->doctype->publicId
436
- // : self::$defaultDoctype;
437
- }
438
- protected function isXML($markup) {
439
- // return strpos($markup, '<?xml') !== false && stripos($markup, 'xhtml') === false;
440
- return strpos(substr($markup, 0, 100), '<'.'?xml') !== false;
441
- }
442
- protected function contentTypeToArray($contentType) {
443
- $matches = explode(';', trim(strtolower($contentType)));
444
- if (isset($matches[1])) {
445
- $matches[1] = explode('=', $matches[1]);
446
- // strip 'charset='
447
- $matches[1] = isset($matches[1][1]) && trim($matches[1][1])
448
- ? $matches[1][1]
449
- : $matches[1][0];
450
- } else
451
- $matches[1] = null;
452
- return $matches;
453
- }
454
- /**
455
- *
456
- * @param $markup
457
- * @return array contentType, charset
458
- */
459
- protected function contentTypeFromHTML($markup) {
460
- $matches = array();
461
- // find meta tag
462
- preg_match('@<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
463
- $markup, $matches
464
- );
465
- if (! isset($matches[0]))
466
- return array(null, null);
467
- // get attr 'content'
468
- preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches);
469
- if (! isset($matches[0]))
470
- return array(null, null);
471
- return $this->contentTypeToArray($matches[2]);
472
- }
473
- protected function charsetFromHTML($markup) {
474
- $contentType = $this->contentTypeFromHTML($markup);
475
- return $contentType[1];
476
- }
477
- protected function charsetFromXML($markup) {
478
- $matches;
479
- // find declaration
480
- preg_match('@<'.'?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i',
481
- $markup, $matches
482
- );
483
- return isset($matches[2])
484
- ? strtolower($matches[2])
485
- : null;
486
- }
487
- /**
488
- * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug.
489
- *
490
- * @link http://code.google.com/p/phpquery/issues/detail?id=80
491
- * @param $html
492
- */
493
- protected function charsetFixHTML($markup) {
494
- $matches = array();
495
- // find meta tag
496
- preg_match('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
497
- $markup, $matches, PREG_OFFSET_CAPTURE
498
- );
499
- if (! isset($matches[0]))
500
- return;
501
- $metaContentType = $matches[0][0];
502
- $markup = substr($markup, 0, $matches[0][1])
503
- .substr($markup, $matches[0][1]+strlen($metaContentType));
504
- $headStart = stripos($markup, '<head>');
505
- $markup = substr($markup, 0, $headStart+6).$metaContentType
506
- .substr($markup, $headStart+6);
507
- return $markup;
508
- }
509
- protected function charsetAppendToHTML($html, $charset, $xhtml = false) {
510
- // remove existing meta[type=content-type]
511
- $html = preg_replace('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html);
512
- $meta = '<meta http-equiv="Content-Type" content="text/html;charset='
513
- .$charset.'" '
514
- .($xhtml ? '/' : '')
515
- .'>';
516
- if (strpos($html, '<head') === false) {
517
- if (strpos($hltml, '<html') === false) {
518
- return $meta.$html;
519
- } else {
520
- return preg_replace(
521
- '@<html(.*?)(?(?<!\?)>)@s',
522
- "<html\\1><head>{$meta}</head>",
523
- $html
524
- );
525
- }
526
- } else {
527
- return preg_replace(
528
- '@<head(.*?)(?(?<!\?)>)@s',
529
- '<head\\1>'.$meta,
530
- $html
531
- );
532
- }
533
- }
534
- protected function charsetAppendToXML($markup, $charset) {
535
- $declaration = '<'.'?xml version="1.0" encoding="'.$charset.'"?'.'>';
536
- return $declaration.$markup;
537
- }
538
- public static function isDocumentFragmentHTML($markup) {
539
- return stripos($markup, '<html') === false && stripos($markup, '<!doctype') === false;
540
- }
541
- public static function isDocumentFragmentXML($markup) {
542
- return stripos($markup, '<'.'?xml') === false;
543
- }
544
- public static function isDocumentFragmentXHTML($markup) {
545
- return self::isDocumentFragmentHTML($markup);
546
- }
547
- public function importAttr($value) {
548
- // TODO
549
- }
550
- /**
551
- *
552
- * @param $source
553
- * @param $target
554
- * @param $sourceCharset
555
- * @return array Array of imported nodes.
556
- */
557
- public function import($source, $sourceCharset = null) {
558
- // TODO charset conversions
559
- $return = array();
560
- if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
561
- $source = array($source);
562
- // if (is_array($source)) {
563
- // foreach($source as $node) {
564
- // if (is_string($node)) {
565
- // // string markup
566
- // $fake = $this->documentFragmentCreate($node, $sourceCharset);
567
- // if ($fake === false)
568
- // throw new Exception("Error loading documentFragment markup");
569
- // else
570
- // $return = array_merge($return,
571
- // $this->import($fake->root->childNodes)
572
- // );
573
- // } else {
574
- // $return[] = $this->document->importNode($node, true);
575
- // }
576
- // }
577
- // return $return;
578
- // } else {
579
- // // string markup
580
- // $fake = $this->documentFragmentCreate($source, $sourceCharset);
581
- // if ($fake === false)
582
- // throw new Exception("Error loading documentFragment markup");
583
- // else
584
- // return $this->import($fake->root->childNodes);
585
- // }
586
- if (is_array($source) || $source instanceof DOMNODELIST) {
587
- // dom nodes
588
- self::debug('Importing nodes to document');
589
- foreach($source as $node)
590
- $return[] = $this->document->importNode($node, true);
591
- } else {
592
- // string markup
593
- $fake = $this->documentFragmentCreate($source, $sourceCharset);
594
- if ($fake === false)
595
- throw new Exception("Error loading documentFragment markup");
596
- else
597
- return $this->import($fake->root->childNodes);
598
- }
599
- return $return;
600
- }
601
- /**
602
- * Creates new document fragment.
603
- *
604
- * @param $source
605
- * @return DOMDocumentWrapper
606
- */
607
- protected function documentFragmentCreate($source, $charset = null) {
608
- $fake = new DOMDocumentWrapper();
609
- $fake->contentType = $this->contentType;
610
- $fake->isXML = $this->isXML;
611
- $fake->isHTML = $this->isHTML;
612
- $fake->isXHTML = $this->isXHTML;
613
- $fake->root = $fake->document;
614
- if (! $charset)
615
- $charset = $this->charset;
616
- // $fake->documentCreate($this->charset);
617
- if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
618
- $source = array($source);
619
- if (is_array($source) || $source instanceof DOMNODELIST) {
620
- // dom nodes
621
- // load fake document
622
- if (! $this->documentFragmentLoadMarkup($fake, $charset))
623
- return false;
624
- $nodes = $fake->import($source);
625
- foreach($nodes as $node)
626
- $fake->root->appendChild($node);
627
- } else {
628
- // string markup
629
- $this->documentFragmentLoadMarkup($fake, $charset, $source);
630
- }
631
- return $fake;
632
- }
633
- /**
634
- *
635
- * @param $document DOMDocumentWrapper
636
- * @param $markup
637
- * @return $document
638
- */
639
- private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) {
640
- // TODO error handling
641
- // TODO copy doctype
642
- // tempolary turn off
643
- $fragment->isDocumentFragment = false;
644
- if ($fragment->isXML) {
645
- if ($fragment->isXHTML) {
646
- // add FAKE element to set default namespace
647
- $fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?>'
648
- .'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
649
- .'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
650
- .'<fake xmlns="http://www.w3.org/1999/xhtml">'.$markup.'</fake>');
651
- $fragment->root = $fragment->document->firstChild->nextSibling;
652
- } else {
653
- $fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?><fake>'.$markup.'</fake>');
654
- $fragment->root = $fragment->document->firstChild;
655
- }
656
- } else {
657
- $markup2 = phpQuery::$defaultDoctype.'<html><head><meta http-equiv="Content-Type" content="text/html;charset='
658
- .$charset.'"></head>';
659
- $noBody = strpos($markup, '<body') === false;
660
- if ($noBody)
661
- $markup2 .= '<body>';
662
- $markup2 .= $markup;
663
- if ($noBody)
664
- $markup2 .= '</body>';
665
- $markup2 .= '</html>';
666
- $fragment->loadMarkupHTML($markup2);
667
- // TODO resolv body tag merging issue
668
- $fragment->root = $noBody
669
- ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling
670
- : $fragment->document->firstChild->nextSibling->firstChild->nextSibling;
671
- }
672
- if (! $fragment->root)
673
- return false;
674
- $fragment->isDocumentFragment = true;
675
- return true;
676
- }
677
- protected function documentFragmentToMarkup($fragment) {
678
- phpQuery::debug('documentFragmentToMarkup');
679
- $tmp = $fragment->isDocumentFragment;
680
- $fragment->isDocumentFragment = false;
681
- $markup = $fragment->markup();
682
- if ($fragment->isXML) {
683
- $markup = substr($markup, 0, strrpos($markup, '</fake>'));
684
- if ($fragment->isXHTML) {
685
- $markup = substr($markup, strpos($markup, '<fake')+43);
686
- } else {
687
- $markup = substr($markup, strpos($markup, '<fake>')+6);
688
- }
689
- } else {
690
- $markup = substr($markup, strpos($markup, '<body>')+6);
691
- $markup = substr($markup, 0, strrpos($markup, '</body>'));
692
- }
693
- $fragment->isDocumentFragment = $tmp;
694
- if (phpQuery::$debug)
695
- phpQuery::debug('documentFragmentToMarkup: '.substr($markup, 0, 150));
696
- return $markup;
697
- }
698
- /**
699
- * Return document markup, starting with optional $nodes as root.
700
- *
701
- * @param $nodes DOMNode|DOMNodeList
702
- * @return string
703
- */
704
- public function markup($nodes = null, $innerMarkup = false) {
705
- if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT)
706
- $nodes = null;
707
- if (isset($nodes)) {
708
- $markup = '';
709
- if (!is_array($nodes) && !($nodes instanceof DOMNODELIST) )
710
- $nodes = array($nodes);
711
- if ($this->isDocumentFragment && ! $innerMarkup)
712
- foreach($nodes as $i => $node)
713
- if ($node->isSameNode($this->root)) {
714
- // var_dump($node);
715
- $nodes = array_slice($nodes, 0, $i)
716
- + phpQuery::DOMNodeListToArray($node->childNodes)
717
- + array_slice($nodes, $i+1);
718
- }
719
- if ($this->isXML && ! $innerMarkup) {
720
- self::debug("Getting outerXML with charset '{$this->charset}'");
721
- // we need outerXML, so we can benefit from
722
- // $node param support in saveXML()
723
- foreach($nodes as $node)
724
- $markup .= $this->document->saveXML($node);
725
- } else {
726
- $loop = array();
727
- if ($innerMarkup)
728
- foreach($nodes as $node) {
729
- if ($node->childNodes)
730
- foreach($node->childNodes as $child)
731
- $loop[] = $child;
732
- else
733
- $loop[] = $node;
734
- }
735
- else
736
- $loop = $nodes;
737
- self::debug("Getting markup, moving selected nodes (".count($loop).") to new DocumentFragment");
738
- $fake = $this->documentFragmentCreate($loop);
739
- $markup = $this->documentFragmentToMarkup($fake);
740
- }
741
- if ($this->isXHTML) {
742
- self::debug("Fixing XHTML");
743
- $markup = self::markupFixXHTML($markup);
744
- }
745
- self::debug("Markup: ".substr($markup, 0, 250));
746
- return $markup;
747
- } else {
748
- if ($this->isDocumentFragment) {
749
- // documentFragment, html only...
750
- self::debug("Getting markup, DocumentFragment detected");
751
- // return $this->markup(
752
- //// $this->document->getElementsByTagName('body')->item(0)
753
- // $this->document->root, true
754
- // );
755
- $markup = $this->documentFragmentToMarkup($this);
756
- // no need for markupFixXHTML, as it's done thought markup($nodes) method
757
- return $markup;
758
- } else {
759
- self::debug("Getting markup (".($this->isXML?'XML':'HTML')."), final with charset '{$this->charset}'");
760
- $markup = $this->isXML
761
- ? $this->document->saveXML()
762
- : $this->document->saveHTML();
763
- if ($this->isXHTML) {
764
- self::debug("Fixing XHTML");
765
- $markup = self::markupFixXHTML($markup);
766
- }
767
- self::debug("Markup: ".substr($markup, 0, 250));
768
- return $markup;
769
- }
770
- }
771
- }
772
- protected static function markupFixXHTML($markup) {
773
- $markup = self::expandEmptyTag('script', $markup);
774
- $markup = self::expandEmptyTag('select', $markup);
775
- $markup = self::expandEmptyTag('textarea', $markup);
776
- return $markup;
777
- }
778
- public static function debug($text) {
779
- phpQuery::debug($text);
780
- }
781
- /**
782
- * expandEmptyTag
783
- *
784
- * @param $tag
785
- * @param $xml
786
- * @return unknown_type
787
- * @author mjaque at ilkebenson dot com
788
- * @link http://php.net/manual/en/domdocument.savehtml.php#81256
789
- */
790
- public static function expandEmptyTag($tag, $xml){
791
- $indice = 0;
792
- while ($indice< strlen($xml)){
793
- $pos = strpos($xml, "<$tag ", $indice);
794
- if ($pos){
795
- $posCierre = strpos($xml, ">", $pos);
796
- if ($xml[$posCierre-1] == "/"){
797
- $xml = substr_replace($xml, "></$tag>", $posCierre-1, 2);
798
- }
799
- $indice = $posCierre;
800
- }
801
- else break;
802
- }
803
- return $xml;
804
- }
805
- }
806
-
807
- /**
808
- * Event handling class.
809
- *
810
- * @author Tobiasz Cudnik
811
- * @package phpQuery
812
- * @static
813
- */
814
- abstract class phpQueryEvents {
815
- /**
816
- * Trigger a type of event on every matched element.
817
- *
818
- * @param DOMNode|phpQueryObject|string $document
819
- * @param unknown_type $type
820
- * @param unknown_type $data
821
- *
822
- * @TODO exclusive events (with !)
823
- * @TODO global events (test)
824
- * @TODO support more than event in $type (space-separated)
825
- */
826
- public static function trigger($document, $type, $data = array(), $node = null) {
827
- // trigger: function(type, data, elem, donative, extra) {
828
- $documentID = phpQuery::getDocumentID($document);
829
- $namespace = null;
830
- if (strpos($type, '.') !== false)
831
- list($name, $namespace) = explode('.', $type);
832
- else
833
- $name = $type;
834
- if (! $node) {
835
- if (self::issetGlobal($documentID, $type)) {
836
- $pq = phpQuery::getDocument($documentID);
837
- // TODO check add($pq->document)
838
- $pq->find('*')->add($pq->document)
839
- ->trigger($type, $data);
840
- }
841
- } else {
842
- if (isset($data[0]) && $data[0] instanceof DOMEvent) {
843
- $event = $data[0];
844
- $event->relatedTarget = $event->target;
845
- $event->target = $node;
846
- $data = array_slice($data, 1);
847
- } else {
848
- $event = new DOMEvent(array(
849
- 'type' => $type,
850
- 'target' => $node,
851
- 'timeStamp' => time(),
852
- ));
853
- }
854
- $i = 0;
855
- while($node) {
856
- // TODO whois
857
- phpQuery::debug("Triggering ".($i?"bubbled ":'')."event '{$type}' on "
858
- ."node \n");//.phpQueryObject::whois($node)."\n");
859
- $event->currentTarget = $node;
860
- $eventNode = self::getNode($documentID, $node);
861
- if (isset($eventNode->eventHandlers)) {
862
- foreach($eventNode->eventHandlers as $eventType => $handlers) {
863
- $eventNamespace = null;
864
- if (strpos($type, '.') !== false)
865
- list($eventName, $eventNamespace) = explode('.', $eventType);
866
- else
867
- $eventName = $eventType;
868
- if ($name != $eventName)
869
- continue;
870
- if ($namespace && $eventNamespace && $namespace != $eventNamespace)
871
- continue;
872
- foreach($handlers as $handler) {
873
- phpQuery::debug("Calling event handler\n");
874
- $event->data = $handler['data']
875
- ? $handler['data']
876
- : null;
877
- $params = array_merge(array($event), $data);
878
- $return = phpQuery::callbackRun($handler['callback'], $params);
879
- if ($return === false) {
880
- $event->bubbles = false;
881
- }
882
- }
883
- }
884
- }
885
- // to bubble or not to bubble...
886
- if (! $event->bubbles)
887
- break;
888
- $node = $node->parentNode;
889
- $i++;
890
- }
891
- }
892
- }
893
- /**
894
- * Binds a handler to one or more events (like click) for each matched element.
895
- * Can also bind custom events.
896
- *
897
- * @param DOMNode|phpQueryObject|string $document
898
- * @param unknown_type $type
899
- * @param unknown_type $data Optional
900
- * @param unknown_type $callback
901
- *
902
- * @TODO support '!' (exclusive) events
903
- * @TODO support more than event in $type (space-separated)
904
- * @TODO support binding to global events
905
- */
906
- public static function add($document, $node, $type, $data, $callback = null) {
907
- phpQuery::debug("Binding '$type' event");
908
- $documentID = phpQuery::getDocumentID($document);
909
- // if (is_null($callback) && is_callable($data)) {
910
- // $callback = $data;
911
- // $data = null;
912
- // }
913
- $eventNode = self::getNode($documentID, $node);
914
- if (! $eventNode)
915
- $eventNode = self::setNode($documentID, $node);
916
- if (!isset($eventNode->eventHandlers[$type]))
917
- $eventNode->eventHandlers[$type] = array();
918
- $eventNode->eventHandlers[$type][] = array(
919
- 'callback' => $callback,
920
- 'data' => $data,
921
- );
922
- }
923
- /**
924
- * Enter description here...
925
- *
926
- * @param DOMNode|phpQueryObject|string $document
927
- * @param unknown_type $type
928
- * @param unknown_type $callback
929
- *
930
- * @TODO namespace events
931
- * @TODO support more than event in $type (space-separated)
932
- */
933
- public static function remove($document, $node, $type = null, $callback = null) {
934
- $documentID = phpQuery::getDocumentID($document);
935
- $eventNode = self::getNode($documentID, $node);
936
- if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) {
937
- if ($callback) {
938
- foreach($eventNode->eventHandlers[$type] as $k => $handler)
939
- if ($handler['callback'] == $callback)
940
- unset($eventNode->eventHandlers[$type][$k]);
941
- } else {
942
- unset($eventNode->eventHandlers[$type]);
943
- }
944
- }
945
- }
946
- protected static function getNode($documentID, $node) {
947
- foreach(phpQuery::$documents[$documentID]->eventsNodes as $eventNode) {
948
- if ($node->isSameNode($eventNode))
949
- return $eventNode;
950
- }
951
- }
952
- protected static function setNode($documentID, $node) {
953
- phpQuery::$documents[$documentID]->eventsNodes[] = $node;
954
- return phpQuery::$documents[$documentID]->eventsNodes[
955
- count(phpQuery::$documents[$documentID]->eventsNodes)-1
956
- ];
957
- }
958
- protected static function issetGlobal($documentID, $type) {
959
- return isset(phpQuery::$documents[$documentID])
960
- ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal)
961
- : false;
962
- }
963
- }
964
-
965
-
966
- interface ICallbackNamed {
967
- function hasName();
968
- function getName();
969
- }
970
- /**
971
- * Callback class introduces currying-like pattern.
972
- *
973
- * Example:
974
- * function foo($param1, $param2, $param3) {
975
- * var_dump($param1, $param2, $param3);
976
- * }
977
- * $fooCurried = new Callback('foo',
978
- * 'param1 is now statically set',
979
- * new CallbackParam, new CallbackParam
980
- * );
981
- * phpQuery::callbackRun($fooCurried,
982
- * array('param2 value', 'param3 value'
983
- * );
984
- *
985
- * Callback class is supported in all phpQuery methods which accepts callbacks.
986
- *
987
- * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures
988
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
989
- *
990
- * @TODO??? return fake forwarding function created via create_function
991
- * @TODO honor paramStructure
992
- */
993
- class Callback
994
- implements ICallbackNamed {
995
- public $callback = null;
996
- public $params = null;
997
- protected $name;
998
- public function __construct($callback, $param1 = null, $param2 = null,
999
- $param3 = null) {
1000
- $params = func_get_args();
1001
- $params = array_slice($params, 1);
1002
- if ($callback instanceof Callback) {
1003
- // TODO implement recurention
1004
- } else {
1005
- $this->callback = $callback;
1006
- $this->params = $params;
1007
- }
1008
- }
1009
- public function getName() {
1010
- return 'Callback: '.$this->name;
1011
- }
1012
- public function hasName() {
1013
- return isset($this->name) && $this->name;
1014
- }
1015
- public function setName($name) {
1016
- $this->name = $name;
1017
- return $this;
1018
- }
1019
- // TODO test me
1020
- // public function addParams() {
1021
- // $params = func_get_args();
1022
- // return new Callback($this->callback, $this->params+$params);
1023
- // }
1024
- }
1025
- /**
1026
- * Shorthand for new Callback(create_function(...), ...);
1027
- *
1028
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1029
- */
1030
- class CallbackBody extends Callback {
1031
- public function __construct($paramList, $code, $param1 = null, $param2 = null,
1032
- $param3 = null) {
1033
- $params = func_get_args();
1034
- $params = array_slice($params, 2);
1035
- $this->callback = create_function($paramList, $code);
1036
- $this->params = $params;
1037
- }
1038
- }
1039
- /**
1040
- * Callback type which on execution returns reference passed during creation.
1041
- *
1042
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1043
- */
1044
- class CallbackReturnReference extends Callback
1045
- implements ICallbackNamed {
1046
- protected $reference;
1047
- public function __construct(&$reference, $name = null){
1048
- $this->reference =& $reference;
1049
- $this->callback = array($this, 'callback');
1050
- }
1051
- public function callback() {
1052
- return $this->reference;
1053
- }
1054
- public function getName() {
1055
- return 'Callback: '.$this->name;
1056
- }
1057
- public function hasName() {
1058
- return isset($this->name) && $this->name;
1059
- }
1060
- }
1061
- /**
1062
- * Callback type which on execution returns value passed during creation.
1063
- *
1064
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1065
- */
1066
- class CallbackReturnValue extends Callback
1067
- implements ICallbackNamed {
1068
- protected $value;
1069
- protected $name;
1070
- public function __construct($value, $name = null){
1071
- $this->value =& $value;
1072
- $this->name = $name;
1073
- $this->callback = array($this, 'callback');
1074
- }
1075
- public function callback() {
1076
- return $this->value;
1077
- }
1078
- public function __toString() {
1079
- return $this->getName();
1080
- }
1081
- public function getName() {
1082
- return 'Callback: '.$this->name;
1083
- }
1084
- public function hasName() {
1085
- return isset($this->name) && $this->name;
1086
- }
1087
- }
1088
- /**
1089
- * CallbackParameterToReference can be used when we don't really want a callback,
1090
- * only parameter passed to it. CallbackParameterToReference takes first
1091
- * parameter's value and passes it to reference.
1092
- *
1093
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1094
- */
1095
- class CallbackParameterToReference extends Callback {
1096
- /**
1097
- * @param $reference
1098
- * @TODO implement $paramIndex;
1099
- * param index choose which callback param will be passed to reference
1100
- */
1101
- public function __construct(&$reference){
1102
- $this->callback =& $reference;
1103
- }
1104
- }
1105
- //class CallbackReference extends Callback {
1106
- // /**
1107
- // *
1108
- // * @param $reference
1109
- // * @param $paramIndex
1110
- // * @todo implement $paramIndex; param index choose which callback param will be passed to reference
1111
- // */
1112
- // public function __construct(&$reference, $name = null){
1113
- // $this->callback =& $reference;
1114
- // }
1115
- //}
1116
- class CallbackParam {}
1117
-
1118
- /**
1119
- * Class representing phpQuery objects.
1120
- *
1121
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1122
- * @package phpQuery
1123
- * @method phpQueryObject clone() clone()
1124
- * @method phpQueryObject empty() empty()
1125
- * @method phpQueryObject next() next($selector = null)
1126
- * @method phpQueryObject prev() prev($selector = null)
1127
- * @property Int $length
1128
- */
1129
- class phpQueryObject
1130
- implements Iterator, Countable, ArrayAccess {
1131
- public $documentID = null;
1132
- /**
1133
- * DOMDocument class.
1134
- *
1135
- * @var DOMDocument
1136
- */
1137
- public $document = null;
1138
- public $charset = null;
1139
- /**
1140
- *
1141
- * @var DOMDocumentWrapper
1142
- */
1143
- public $documentWrapper = null;
1144
- /**
1145
- * XPath interface.
1146
- *
1147
- * @var DOMXPath
1148
- */
1149
- public $xpath = null;
1150
- /**
1151
- * Stack of selected elements.
1152
- * @TODO refactor to ->nodes
1153
- * @var array
1154
- */
1155
- public $elements = array();
1156
- /**
1157
- * @access private
1158
- */
1159
- protected $elementsBackup = array();
1160
- /**
1161
- * @access private
1162
- */
1163
- protected $previous = null;
1164
- /**
1165
- * @access private
1166
- * @TODO deprecate
1167
- */
1168
- protected $root = array();
1169
- /**
1170
- * Indicated if doument is just a fragment (no <html> tag).
1171
- *
1172
- * Every document is realy a full document, so even documentFragments can
1173
- * be queried against <html>, but getDocument(id)->htmlOuter() will return
1174
- * only contents of <body>.
1175
- *
1176
- * @var bool
1177
- */
1178
- public $documentFragment = true;
1179
- /**
1180
- * Iterator interface helper
1181
- * @access private
1182
- */
1183
- protected $elementsInterator = array();
1184
- /**
1185
- * Iterator interface helper
1186
- * @access private
1187
- */
1188
- protected $valid = false;
1189
- /**
1190
- * Iterator interface helper
1191
- * @access private
1192
- */
1193
- protected $current = null;
1194
- /**
1195
- * Enter description here...
1196
- *
1197
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1198
- */
1199
- public function __construct($documentID) {
1200
- // if ($documentID instanceof self)
1201
- // var_dump($documentID->getDocumentID());
1202
- $id = $documentID instanceof self
1203
- ? $documentID->getDocumentID()
1204
- : $documentID;
1205
- // var_dump($id);
1206
- if (! isset(phpQuery::$documents[$id] )) {
1207
- // var_dump(phpQuery::$documents);
1208
- throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first.");
1209
- }
1210
- $this->documentID = $id;
1211
- $this->documentWrapper =& phpQuery::$documents[$id];
1212
- $this->document =& $this->documentWrapper->document;
1213
- $this->xpath =& $this->documentWrapper->xpath;
1214
- $this->charset =& $this->documentWrapper->charset;
1215
- $this->documentFragment =& $this->documentWrapper->isDocumentFragment;
1216
- // TODO check $this->DOM->documentElement;
1217
- // $this->root = $this->document->documentElement;
1218
- $this->root =& $this->documentWrapper->root;
1219
- // $this->toRoot();
1220
- $this->elements = array($this->root);
1221
- }
1222
- /**
1223
- *
1224
- * @access private
1225
- * @param $attr
1226
- * @return unknown_type
1227
- */
1228
- public function __get($attr) {
1229
- switch($attr) {
1230
- // FIXME doesnt work at all ?
1231
- case 'length':
1232
- return $this->size();
1233
- break;
1234
- default:
1235
- return $this->$attr;
1236
- }
1237
- }
1238
- /**
1239
- * Saves actual object to $var by reference.
1240
- * Useful when need to break chain.
1241
- * @param phpQueryObject $var
1242
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1243
- */
1244
- public function toReference(&$var) {
1245
- return $var = $this;
1246
- }
1247
- public function documentFragment($state = null) {
1248
- if ($state) {
1249
- phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state;
1250
- return $this;
1251
- }
1252
- return $this->documentFragment;
1253
- }
1254
- /**
1255
- * @access private
1256
- * @TODO documentWrapper
1257
- */
1258
- protected function isRoot( $node) {
1259
- // return $node instanceof DOMDOCUMENT || $node->tagName == 'html';
1260
- return $node instanceof DOMDOCUMENT
1261
- || ($node instanceof DOMELEMENT && $node->tagName == 'html')
1262
- || $this->root->isSameNode($node);
1263
- }
1264
- /**
1265
- * @access private
1266
- */
1267
- protected function stackIsRoot() {
1268
- return $this->size() == 1 && $this->isRoot($this->elements[0]);
1269
- }
1270
- /**
1271
- * Enter description here...
1272
- * NON JQUERY METHOD
1273
- *
1274
- * Watch out, it doesn't creates new instance, can be reverted with end().
1275
- *
1276
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1277
- */
1278
- public function toRoot() {
1279
- $this->elements = array($this->root);
1280
- return $this;
1281
- // return $this->newInstance(array($this->root));
1282
- }
1283
- /**
1284
- * Saves object's DocumentID to $var by reference.
1285
- * <code>
1286
- * $myDocumentId;
1287
- * phpQuery::newDocument('<div/>')
1288
- * ->getDocumentIDRef($myDocumentId)
1289
- * ->find('div')->...
1290
- * </code>
1291
- *
1292
- * @param unknown_type $domId
1293
- * @see phpQuery::newDocument
1294
- * @see phpQuery::newDocumentFile
1295
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1296
- */
1297
- public function getDocumentIDRef(&$documentID) {
1298
- $documentID = $this->getDocumentID();
1299
- return $this;
1300
- }
1301
- /**
1302
- * Returns object with stack set to document root.
1303
- *
1304
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1305
- */
1306
- public function getDocument() {
1307
- return phpQuery::getDocument($this->getDocumentID());
1308
- }
1309
- /**
1310
- *
1311
- * @return DOMDocument
1312
- */
1313
- public function getDOMDocument() {
1314
- return $this->document;
1315
- }
1316
- /**
1317
- * Get object's Document ID.
1318
- *
1319
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1320
- */
1321
- public function getDocumentID() {
1322
- return $this->documentID;
1323
- }
1324
- /**
1325
- * Unloads whole document from memory.
1326
- * CAUTION! None further operations will be possible on this document.
1327
- * All objects refering to it will be useless.
1328
- *
1329
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1330
- */
1331
- public function unloadDocument() {
1332
- phpQuery::unloadDocuments($this->getDocumentID());
1333
- }
1334
- public function isHTML() {
1335
- return $this->documentWrapper->isHTML;
1336
- }
1337
- public function isXHTML() {
1338
- return $this->documentWrapper->isXHTML;
1339
- }
1340
- public function isXML() {
1341
- return $this->documentWrapper->isXML;
1342
- }
1343
- /**
1344
- * Enter description here...
1345
- *
1346
- * @link http://docs.jquery.com/Ajax/serialize
1347
- * @return string
1348
- */
1349
- public function serialize() {
1350
- return phpQuery::param($this->serializeArray());
1351
- }
1352
- /**
1353
- * Enter description here...
1354
- *
1355
- * @link http://docs.jquery.com/Ajax/serializeArray
1356
- * @return array
1357
- */
1358
- public function serializeArray($submit = null) {
1359
- $source = $this->filter('form, input, select, textarea')
1360
- ->find('input, select, textarea')
1361
- ->andSelf()
1362
- ->not('form');
1363
- $return = array();
1364
- // $source->dumpDie();
1365
- foreach($source as $input) {
1366
- $input = phpQuery::pq($input);
1367
- if ($input->is('[disabled]'))
1368
- continue;
1369
- if (!$input->is('[name]'))
1370
- continue;
1371
- if ($input->is('[type=checkbox]') && !$input->is('[checked]'))
1372
- continue;
1373
- // jquery diff
1374
- if ($submit && $input->is('[type=submit]')) {
1375
- if ($submit instanceof DOMELEMENT && ! $input->elements[0]->isSameNode($submit))
1376
- continue;
1377
- else if (is_string($submit) && $input->attr('name') != $submit)
1378
- continue;
1379
- }
1380
- $return[] = array(
1381
- 'name' => $input->attr('name'),
1382
- 'value' => $input->val(),
1383
- );
1384
- }
1385
- return $return;
1386
- }
1387
- /**
1388
- * @access private
1389
- */
1390
- protected function debug($in) {
1391
- if (! phpQuery::$debug )
1392
- return;
1393
- print('<pre>');
1394
- print_r($in);
1395
- // file debug
1396
- // file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
1397
- // quite handy debug trace
1398
- // if ( is_array($in))
1399
- // print_r(array_slice(debug_backtrace(), 3));
1400
- print("</pre>\n");
1401
- }
1402
- /**
1403
- * @access private
1404
- */
1405
- protected function isRegexp($pattern) {
1406
- return in_array(
1407
- $pattern[ mb_strlen($pattern)-1 ],
1408
- array('^','*','$')
1409
- );
1410
- }
1411
- /**
1412
- * Determines if $char is really a char.
1413
- *
1414
- * @param string $char
1415
- * @return bool
1416
- * @todo rewrite me to charcode range ! ;)
1417
- * @access private
1418
- */
1419
- protected function isChar($char) {
1420
- return extension_loaded('mbstring') && phpQuery::$mbstringSupport
1421
- ? mb_eregi('\w', $char)
1422
- : preg_match('@\w@', $char);
1423
- }
1424
- /**
1425
- * @access private
1426
- */
1427
- protected function parseSelector($query) {
1428
- // clean spaces
1429
- // TODO include this inside parsing ?
1430
- $query = trim(
1431
- preg_replace('@\s+@', ' ',
1432
- preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query)
1433
- )
1434
- );
1435
- $queries = array(array());
1436
- if (! $query)
1437
- return $queries;
1438
- $return =& $queries[0];
1439
- $specialChars = array('>',' ');
1440
- // $specialCharsMapping = array('/' => '>');
1441
- $specialCharsMapping = array();
1442
- $strlen = mb_strlen($query);
1443
- $classChars = array('.', '-');
1444
- $pseudoChars = array('-');
1445
- $tagChars = array('*', '|', '-');
1446
- // split multibyte string
1447
- // http://code.google.com/p/phpquery/issues/detail?id=76
1448
- $_query = array();
1449
- for ($i=0; $i<$strlen; $i++)
1450
- $_query[] = mb_substr($query, $i, 1);
1451
- $query = $_query;
1452
- // it works, but i dont like it...
1453
- $i = 0;
1454
- while( $i < $strlen) {
1455
- $c = $query[$i];
1456
- $tmp = '';
1457
- // TAG
1458
- if ($this->isChar($c) || in_array($c, $tagChars)) {
1459
- while(isset($query[$i])
1460
- && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))) {
1461
- $tmp .= $query[$i];
1462
- $i++;
1463
- }
1464
- $return[] = $tmp;
1465
- // IDs
1466
- } else if ( $c == '#') {
1467
- $i++;
1468
- while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) {
1469
- $tmp .= $query[$i];
1470
- $i++;
1471
- }
1472
- $return[] = '#'.$tmp;
1473
- // SPECIAL CHARS
1474
- } else if (in_array($c, $specialChars)) {
1475
- $return[] = $c;
1476
- $i++;
1477
- // MAPPED SPECIAL MULTICHARS
1478
- // } else if ( $c.$query[$i+1] == '//') {
1479
- // $return[] = ' ';
1480
- // $i = $i+2;
1481
- // MAPPED SPECIAL CHARS
1482
- } else if ( isset($specialCharsMapping[$c])) {
1483
- $return[] = $specialCharsMapping[$c];
1484
- $i++;
1485
- // COMMA
1486
- } else if ( $c == ',') {
1487
- $queries[] = array();
1488
- $return =& $queries[ count($queries)-1 ];
1489
- $i++;
1490
- while( isset($query[$i]) && $query[$i] == ' ')
1491
- $i++;
1492
- // CLASSES
1493
- } else if ($c == '.') {
1494
- while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) {
1495
- $tmp .= $query[$i];
1496
- $i++;
1497
- }
1498
- $return[] = $tmp;
1499
- // ~ General Sibling Selector
1500
- } else if ($c == '~') {
1501
- $spaceAllowed = true;
1502
- $tmp .= $query[$i++];
1503
- while( isset($query[$i])
1504
- && ($this->isChar($query[$i])
1505
- || in_array($query[$i], $classChars)
1506
- || $query[$i] == '*'
1507
- || ($query[$i] == ' ' && $spaceAllowed)
1508
- )) {
1509
- if ($query[$i] != ' ')
1510
- $spaceAllowed = false;
1511
- $tmp .= $query[$i];
1512
- $i++;
1513
- }
1514
- $return[] = $tmp;
1515
- // + Adjacent sibling selectors
1516
- } else if ($c == '+') {
1517
- $spaceAllowed = true;
1518
- $tmp .= $query[$i++];
1519
- while( isset($query[$i])
1520
- && ($this->isChar($query[$i])
1521
- || in_array($query[$i], $classChars)
1522
- || $query[$i] == '*'
1523
- || ($spaceAllowed && $query[$i] == ' ')
1524
- )) {
1525
- if ($query[$i] != ' ')
1526
- $spaceAllowed = false;
1527
- $tmp .= $query[$i];
1528
- $i++;
1529
- }
1530
- $return[] = $tmp;
1531
- // ATTRS
1532
- } else if ($c == '[') {
1533
- $stack = 1;
1534
- $tmp .= $c;
1535
- while( isset($query[++$i])) {
1536
- $tmp .= $query[$i];
1537
- if ( $query[$i] == '[') {
1538
- $stack++;
1539
- } else if ( $query[$i] == ']') {
1540
- $stack--;
1541
- if (! $stack )
1542
- break;
1543
- }
1544
- }
1545
- $return[] = $tmp;
1546
- $i++;
1547
- // PSEUDO CLASSES
1548
- } else if ($c == ':') {
1549
- $stack = 1;
1550
- $tmp .= $query[$i++];
1551
- while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) {
1552
- $tmp .= $query[$i];
1553
- $i++;
1554
- }
1555
- // with arguments ?
1556
- if ( isset($query[$i]) && $query[$i] == '(') {
1557
- $tmp .= $query[$i];
1558
- $stack = 1;
1559
- while( isset($query[++$i])) {
1560
- $tmp .= $query[$i];
1561
- if ( $query[$i] == '(') {
1562
- $stack++;
1563
- } else if ( $query[$i] == ')') {
1564
- $stack--;
1565
- if (! $stack )
1566
- break;
1567
- }
1568
- }
1569
- $return[] = $tmp;
1570
- $i++;
1571
- } else {
1572
- $return[] = $tmp;
1573
- }
1574
- } else {
1575
- $i++;
1576
- }
1577
- }
1578
- foreach($queries as $k => $q) {
1579
- if (isset($q[0])) {
1580
- if (isset($q[0][0]) && $q[0][0] == ':')
1581
- array_unshift($queries[$k], '*');
1582
- if ($q[0] != '>')
1583
- array_unshift($queries[$k], ' ');
1584
- }
1585
- }
1586
- return $queries;
1587
- }
1588
- /**
1589
- * Return matched DOM nodes.
1590
- *
1591
- * @param int $index
1592
- * @return array|DOMElement Single DOMElement or array of DOMElement.
1593
- */
1594
- public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1595
- $return = isset($index)
1596
- ? (isset($this->elements[$index]) ? $this->elements[$index] : null)
1597
- : $this->elements;
1598
- // pass thou callbacks
1599
- $args = func_get_args();
1600
- $args = array_slice($args, 1);
1601
- foreach($args as $callback) {
1602
- if (is_array($return))
1603
- foreach($return as $k => $v)
1604
- $return[$k] = phpQuery::callbackRun($callback, array($v));
1605
- else
1606
- $return = phpQuery::callbackRun($callback, array($return));
1607
- }
1608
- return $return;
1609
- }
1610
- /**
1611
- * Return matched DOM nodes.
1612
- * jQuery difference.
1613
- *
1614
- * @param int $index
1615
- * @return array|string Returns string if $index != null
1616
- * @todo implement callbacks
1617
- * @todo return only arrays ?
1618
- * @todo maybe other name...
1619
- */
1620
- public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1621
- if ($index)
1622
- $return = $this->eq($index)->text();
1623
- else {
1624
- $return = array();
1625
- for($i = 0; $i < $this->size(); $i++) {
1626
- $return[] = $this->eq($i)->text();
1627
- }
1628
- }
1629
- // pass thou callbacks
1630
- $args = func_get_args();
1631
- $args = array_slice($args, 1);
1632
- foreach($args as $callback) {
1633
- $return = phpQuery::callbackRun($callback, array($return));
1634
- }
1635
- return $return;
1636
- }
1637
- /**
1638
- * Return matched DOM nodes.
1639
- * jQuery difference.
1640
- *
1641
- * @param int $index
1642
- * @return array|string Returns string if $index != null
1643
- * @todo implement callbacks
1644
- * @todo return only arrays ?
1645
- * @todo maybe other name...
1646
- */
1647
- public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1648
- if ($index)
1649
- $return = $this->eq($index)->text();
1650
- else {
1651
- $return = array();
1652
- for($i = 0; $i < $this->size(); $i++) {
1653
- $return[] = $this->eq($i)->text();
1654
- }
1655
- // pass thou callbacks
1656
- $args = func_get_args();
1657
- $args = array_slice($args, 1);
1658
- }
1659
- foreach($args as $callback) {
1660
- if (is_array($return))
1661
- foreach($return as $k => $v)
1662
- $return[$k] = phpQuery::callbackRun($callback, array($v));
1663
- else
1664
- $return = phpQuery::callbackRun($callback, array($return));
1665
- }
1666
- return $return;
1667
- }
1668
- /**
1669
- * Returns new instance of actual class.
1670
- *
1671
- * @param array $newStack Optional. Will replace old stack with new and move old one to history.c
1672
- */
1673
- public function newInstance($newStack = null) {
1674
- $class = get_class($this);
1675
- // support inheritance by passing old object to overloaded constructor
1676
- $new = $class != 'phpQuery'
1677
- ? new $class($this, $this->getDocumentID())
1678
- : new phpQueryObject($this->getDocumentID());
1679
- $new->previous = $this;
1680
- if (is_null($newStack)) {
1681
- $new->elements = $this->elements;
1682
- if ($this->elementsBackup)
1683
- $this->elements = $this->elementsBackup;
1684
- } else if (is_string($newStack)) {
1685
- $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack();
1686
- } else {
1687
- $new->elements = $newStack;
1688
- }
1689
- return $new;
1690
- }
1691
- /**
1692
- * Enter description here...
1693
- *
1694
- * In the future, when PHP will support XLS 2.0, then we would do that this way:
1695
- * contains(tokenize(@class, '\s'), "something")
1696
- * @param unknown_type $class
1697
- * @param unknown_type $node
1698
- * @return boolean
1699
- * @access private
1700
- */
1701
- protected function matchClasses($class, $node) {
1702
- // multi-class
1703
- if ( mb_strpos($class, '.', 1)) {
1704
- $classes = explode('.', substr($class, 1));
1705
- $classesCount = count( $classes );
1706
- $nodeClasses = explode(' ', $node->getAttribute('class') );
1707
- $nodeClassesCount = count( $nodeClasses );
1708
- if ( $classesCount > $nodeClassesCount )
1709
- return false;
1710
- $diff = count(
1711
- array_diff(
1712
- $classes,
1713
- $nodeClasses
1714
- )
1715
- );
1716
- if (! $diff )
1717
- return true;
1718
- // single-class
1719
- } else {
1720
- return in_array(
1721
- // strip leading dot from class name
1722
- substr($class, 1),
1723
- // get classes for element as array
1724
- explode(' ', $node->getAttribute('class') )
1725
- );
1726
- }
1727
- }
1728
- /**
1729
- * @access private
1730
- */
1731
- protected function runQuery($XQuery, $selector = null, $compare = null) {
1732
- if ($compare && ! method_exists($this, $compare))
1733
- return false;
1734
- $stack = array();
1735
- if (! $this->elements)
1736
- $this->debug('Stack empty, skipping...');
1737
- // var_dump($this->elements[0]->nodeType);
1738
- // element, document
1739
- foreach($this->stack(array(1, 9, 13)) as $k => $stackNode) {
1740
- $detachAfter = false;
1741
- // to work on detached nodes we need temporary place them somewhere
1742
- // thats because context xpath queries sucks ;]
1743
- $testNode = $stackNode;
1744
- while ($testNode) {
1745
- if (! $testNode->parentNode && ! $this->isRoot($testNode)) {
1746
- $this->root->appendChild($testNode);
1747
- $detachAfter = $testNode;
1748
- break;
1749
- }
1750
- $testNode = isset($testNode->parentNode)
1751
- ? $testNode->parentNode
1752
- : null;
1753
- }
1754
- // XXX tmp ?
1755
- $xpath = $this->documentWrapper->isXHTML
1756
- ? $this->getNodeXpath($stackNode, 'html')
1757
- : $this->getNodeXpath($stackNode);
1758
- // FIXME pseudoclasses-only query, support XML
1759
- $query = $XQuery == '//' && $xpath == '/html[1]'
1760
- ? '//*'
1761
- : $xpath.$XQuery;
1762
- $this->debug("XPATH: {$query}");
1763
- // run query, get elements
1764
- $nodes = $this->xpath->query($query);
1765
- $this->debug("QUERY FETCHED");
1766
- if (! $nodes->length )
1767
- $this->debug('Nothing found');
1768
- $debug = array();
1769
- foreach($nodes as $node) {
1770
- $matched = false;
1771
- if ( $compare) {
1772
- phpQuery::$debug ?
1773
- $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()")
1774
- : null;
1775
- $phpQueryDebug = phpQuery::$debug;
1776
- phpQuery::$debug = false;
1777
- // TODO ??? use phpQuery::callbackRun()
1778
- if (call_user_func_array(array($this, $compare), array($selector, $node)))
1779
- $matched = true;
1780
- phpQuery::$debug = $phpQueryDebug;
1781
- } else {
1782
- $matched = true;
1783
- }
1784
- if ( $matched) {
1785
- if (phpQuery::$debug)
1786
- $debug[] = $this->whois( $node );
1787
- $stack[] = $node;
1788
- }
1789
- }
1790
- if (phpQuery::$debug) {
1791
- $this->debug("Matched ".count($debug).": ".implode(', ', $debug));
1792
- }
1793
- if ($detachAfter)
1794
- $this->root->removeChild($detachAfter);
1795
- }
1796
- $this->elements = $stack;
1797
- }
1798
- /**
1799
- * Enter description here...
1800
- *
1801
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1802
- */
1803
- public function find($selectors, $context = null, $noHistory = false) {
1804
- if (!$noHistory)
1805
- // backup last stack /for end()/
1806
- $this->elementsBackup = $this->elements;
1807
- // allow to define context
1808
- // TODO combine code below with phpQuery::pq() context guessing code
1809
- // as generic function
1810
- if ($context) {
1811
- if (! is_array($context) && $context instanceof DOMELEMENT)
1812
- $this->elements = array($context);
1813
- else if (is_array($context)) {
1814
- $this->elements = array();
1815
- foreach ($context as $c)
1816
- if ($c instanceof DOMELEMENT)
1817
- $this->elements[] = $c;
1818
- } else if ( $context instanceof self )
1819
- $this->elements = $context->elements;
1820
- }
1821
- $queries = $this->parseSelector($selectors);
1822
- $this->debug(array('FIND', $selectors, $queries));
1823
- $XQuery = '';
1824
- // remember stack state because of multi-queries
1825
- $oldStack = $this->elements;
1826
- // here we will be keeping found elements
1827
- $stack = array();
1828
- foreach($queries as $selector) {
1829
- $this->elements = $oldStack;
1830
- $delimiterBefore = false;
1831
- foreach($selector as $s) {
1832
- // TAG
1833
- $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport
1834
- ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*'
1835
- : preg_match('@^[\w|\||-]+$@', $s) || $s == '*';
1836
- if ($isTag) {
1837
- if ($this->isXML()) {
1838
- // namespace support
1839
- if (mb_strpos($s, '|') !== false) {
1840
- $ns = $tag = null;
1841
- list($ns, $tag) = explode('|', $s);
1842
- $XQuery .= "$ns:$tag";
1843
- } else if ($s == '*') {
1844
- $XQuery .= "*";
1845
- } else {
1846
- $XQuery .= "*[local-name()='$s']";
1847
- }
1848
- } else {
1849
- $XQuery .= $s;
1850
- }
1851
- // ID
1852
- } else if ($s[0] == '#') {
1853
- if ($delimiterBefore)
1854
- $XQuery .= '*';
1855
- $XQuery .= "[@id='".substr($s, 1)."']";
1856
- // ATTRIBUTES
1857
- } else if ($s[0] == '[') {
1858
- if ($delimiterBefore)
1859
- $XQuery .= '*';
1860
- // strip side brackets
1861
- $attr = trim($s, '][');
1862
- $execute = false;
1863
- // attr with specifed value
1864
- if (mb_strpos($s, '=')) {
1865
- $value = null;
1866
- list($attr, $value) = explode('=', $attr);
1867
- $value = trim($value, "'\"");
1868
- if ($this->isRegexp($attr)) {
1869
- // cut regexp character
1870
- $attr = substr($attr, 0, -1);
1871
- $execute = true;
1872
- $XQuery .= "[@{$attr}]";
1873
- } else {
1874
- $XQuery .= "[@{$attr}='{$value}']";
1875
- }
1876
- // attr without specified value
1877
- } else {
1878
- $XQuery .= "[@{$attr}]";
1879
- }
1880
- if ($execute) {
1881
- $this->runQuery($XQuery, $s, 'is');
1882
- $XQuery = '';
1883
- if (! $this->length())
1884
- break;
1885
- }
1886
- // CLASSES
1887
- } else if ($s[0] == '.') {
1888
- // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]");
1889
- // thx wizDom ;)
1890
- if ($delimiterBefore)
1891
- $XQuery .= '*';
1892
- $XQuery .= '[@class]';
1893
- $this->runQuery($XQuery, $s, 'matchClasses');
1894
- $XQuery = '';
1895
- if (! $this->length() )
1896
- break;
1897
- // ~ General Sibling Selector
1898
- } else if ($s[0] == '~') {
1899
- $this->runQuery($XQuery);
1900
- $XQuery = '';
1901
- $this->elements = $this
1902
- ->siblings(
1903
- substr($s, 1)
1904
- )->elements;
1905
- if (! $this->length() )
1906
- break;
1907
- // + Adjacent sibling selectors
1908
- } else if ($s[0] == '+') {
1909
- // TODO /following-sibling::
1910
- $this->runQuery($XQuery);
1911
- $XQuery = '';
1912
- $subSelector = substr($s, 1);
1913
- $subElements = $this->elements;
1914
- $this->elements = array();
1915
- foreach($subElements as $node) {
1916
- // search first DOMElement sibling
1917
- $test = $node->nextSibling;
1918
- while($test && ! ($test instanceof DOMELEMENT))
1919
- $test = $test->nextSibling;
1920
- if ($test && $this->is($subSelector, $test))
1921
- $this->elements[] = $test;
1922
- }
1923
- if (! $this->length() )
1924
- break;
1925
- // PSEUDO CLASSES
1926
- } else if ($s[0] == ':') {
1927
- // TODO optimization for :first :last
1928
- if ($XQuery) {
1929
- $this->runQuery($XQuery);
1930
- $XQuery = '';
1931
- }
1932
- if (! $this->length())
1933
- break;
1934
- $this->pseudoClasses($s);
1935
- if (! $this->length())
1936
- break;
1937
- // DIRECT DESCENDANDS
1938
- } else if ($s == '>') {
1939
- $XQuery .= '/';
1940
- $delimiterBefore = 2;
1941
- // ALL DESCENDANDS
1942
- } else if ($s == ' ') {
1943
- $XQuery .= '//';
1944
- $delimiterBefore = 2;
1945
- // ERRORS
1946
- } else {
1947
- phpQuery::debug("Unrecognized token '$s'");
1948
- }
1949
- $delimiterBefore = $delimiterBefore === 2;
1950
- }
1951
- // run query if any
1952
- if ($XQuery && $XQuery != '//') {
1953
- $this->runQuery($XQuery);
1954
- $XQuery = '';
1955
- }
1956
- foreach($this->elements as $node)
1957
- if (! $this->elementsContainsNode($node, $stack))
1958
- $stack[] = $node;
1959
- }
1960
- $this->elements = $stack;
1961
- return $this->newInstance();
1962
- }
1963
- /**
1964
- * @todo create API for classes with pseudoselectors
1965
- * @access private
1966
- */
1967
- protected function pseudoClasses($class) {
1968
- // TODO clean args parsing ?
1969
- $class = ltrim($class, ':');
1970
- $haveArgs = mb_strpos($class, '(');
1971
- if ($haveArgs !== false) {
1972
- $args = substr($class, $haveArgs+1, -1);
1973
- $class = substr($class, 0, $haveArgs);
1974
- }
1975
- switch($class) {
1976
- case 'even':
1977
- case 'odd':
1978
- $stack = array();
1979
- foreach($this->elements as $i => $node) {
1980
- if ($class == 'even' && ($i%2) == 0)
1981
- $stack[] = $node;
1982
- else if ( $class == 'odd' && $i % 2 )
1983
- $stack[] = $node;
1984
- }
1985
- $this->elements = $stack;
1986
- break;
1987
- case 'eq':
1988
- $k = intval($args);
1989
- $this->elements = isset( $this->elements[$k] )
1990
- ? array( $this->elements[$k] )
1991
- : array();
1992
- break;
1993
- case 'gt':
1994
- $this->elements = array_slice($this->elements, $args+1);
1995
- break;
1996
- case 'lt':
1997
- $this->elements = array_slice($this->elements, 0, $args+1);
1998
- break;
1999
- case 'first':
2000
- if (isset($this->elements[0]))
2001
- $this->elements = array($this->elements[0]);
2002
- break;
2003
- case 'last':
2004
- if ($this->elements)
2005
- $this->elements = array($this->elements[count($this->elements)-1]);
2006
- break;
2007
- /*case 'parent':
2008
- $stack = array();
2009
- foreach($this->elements as $node) {
2010
- if ( $node->childNodes->length )
2011
- $stack[] = $node;
2012
- }
2013
- $this->elements = $stack;
2014
- break;*/
2015
- case 'contains':
2016
- $text = trim($args, "\"'");
2017
- $stack = array();
2018
- foreach($this->elements as $node) {
2019
- if (mb_stripos($node->textContent, $text) === false)
2020
- continue;
2021
- $stack[] = $node;
2022
- }
2023
- $this->elements = $stack;
2024
- break;
2025
- case 'not':
2026
- $selector = self::unQuote($args);
2027
- $this->elements = $this->not($selector)->stack();
2028
- break;
2029
- case 'slice':
2030
- // TODO jQuery difference ?
2031
- $args = explode(',',
2032
- str_replace(', ', ',', trim($args, "\"'"))
2033
- );
2034
- $start = $args[0];
2035
- $end = isset($args[1])
2036
- ? $args[1]
2037
- : null;
2038
- if ($end > 0)
2039
- $end = $end-$start;
2040
- $this->elements = array_slice($this->elements, $start, $end);
2041
- break;
2042
- case 'has':
2043
- $selector = trim($args, "\"'");
2044
- $stack = array();
2045
- foreach($this->stack(1) as $el) {
2046
- if ($this->find($selector, $el, true)->length)
2047
- $stack[] = $el;
2048
- }
2049
- $this->elements = $stack;
2050
- break;
2051
- case 'submit':
2052
- case 'reset':
2053
- $this->elements = phpQuery::merge(
2054
- $this->map(array($this, 'is'),
2055
- "input[type=$class]", new CallbackParam()
2056
- ),
2057
- $this->map(array($this, 'is'),
2058
- "button[type=$class]", new CallbackParam()
2059
- )
2060
- );
2061
- break;
2062
- // $stack = array();
2063
- // foreach($this->elements as $node)
2064
- // if ($node->is('input[type=submit]') || $node->is('button[type=submit]'))
2065
- // $stack[] = $el;
2066
- // $this->elements = $stack;
2067
- case 'input':
2068
- $this->elements = $this->map(
2069
- array($this, 'is'),
2070
- 'input', new CallbackParam()
2071
- )->elements;
2072
- break;
2073
- case 'password':
2074
- case 'checkbox':
2075
- case 'radio':
2076
- case 'hidden':
2077
- case 'image':
2078
- case 'file':
2079
- $this->elements = $this->map(
2080
- array($this, 'is'),
2081
- "input[type=$class]", new CallbackParam()
2082
- )->elements;
2083
- break;
2084
- case 'parent':
2085
- $this->elements = $this->map(
2086
- create_function('$node', '
2087
- return $node instanceof DOMELEMENT && $node->childNodes->length
2088
- ? $node : null;')
2089
- )->elements;
2090
- break;
2091
- case 'empty':
2092
- $this->elements = $this->map(
2093
- create_function('$node', '
2094
- return $node instanceof DOMELEMENT && $node->childNodes->length
2095
- ? null : $node;')
2096
- )->elements;
2097
- break;
2098
- case 'disabled':
2099
- case 'selected':
2100
- case 'checked':
2101
- $this->elements = $this->map(
2102
- array($this, 'is'),
2103
- "[$class]", new CallbackParam()
2104
- )->elements;
2105
- break;
2106
- case 'enabled':
2107
- $this->elements = $this->map(
2108
- create_function('$node', '
2109
- return pq($node)->not(":disabled") ? $node : null;')
2110
- )->elements;
2111
- break;
2112
- case 'header':
2113
- $this->elements = $this->map(
2114
- create_function('$node',
2115
- '$isHeader = isset($node->tagName) && in_array($node->tagName, array(
2116
- "h1", "h2", "h3", "h4", "h5", "h6", "h7"
2117
- ));
2118
- return $isHeader
2119
- ? $node
2120
- : null;')
2121
- )->elements;
2122
- // $this->elements = $this->map(
2123
- // create_function('$node', '$node = pq($node);
2124
- // return $node->is("h1")
2125
- // || $node->is("h2")
2126
- // || $node->is("h3")
2127
- // || $node->is("h4")
2128
- // || $node->is("h5")
2129
- // || $node->is("h6")
2130
- // || $node->is("h7")
2131
- // ? $node
2132
- // : null;')
2133
- // )->elements;
2134
- break;
2135
- case 'only-child':
2136
- $this->elements = $this->map(
2137
- create_function('$node',
2138
- 'return pq($node)->siblings()->size() == 0 ? $node : null;')
2139
- )->elements;
2140
- break;
2141
- case 'first-child':
2142
- $this->elements = $this->map(
2143
- create_function('$node', 'return pq($node)->prevAll()->size() == 0 ? $node : null;')
2144
- )->elements;
2145
- break;
2146
- case 'last-child':
2147
- $this->elements = $this->map(
2148
- create_function('$node', 'return pq($node)->nextAll()->size() == 0 ? $node : null;')
2149
- )->elements;
2150
- break;
2151
- case 'nth-child':
2152
- $param = trim($args, "\"'");
2153
- if (! $param)
2154
- break;
2155
- // nth-child(n+b) to nth-child(1n+b)
2156
- if ($param{0} == 'n')
2157
- $param = '1'.$param;
2158
- // :nth-child(index/even/odd/equation)
2159
- if ($param == 'even' || $param == 'odd')
2160
- $mapped = $this->map(
2161
- create_function('$node, $param',
2162
- '$index = pq($node)->prevAll()->size()+1;
2163
- if ($param == "even" && ($index%2) == 0)
2164
- return $node;
2165
- else if ($param == "odd" && $index%2 == 1)
2166
- return $node;
2167
- else
2168
- return null;'),
2169
- new CallbackParam(), $param
2170
- );
2171
- else if (mb_strlen($param) > 1 && $param{1} == 'n')
2172
- // an+b
2173
- $mapped = $this->map(
2174
- create_function('$node, $param',
2175
- '$prevs = pq($node)->prevAll()->size();
2176
- $index = 1+$prevs;
2177
- $b = mb_strlen($param) > 3
2178
- ? $param{3}
2179
- : 0;
2180
- $a = $param{0};
2181
- if ($b && $param{2} == "-")
2182
- $b = -$b;
2183
- if ($a > 0) {
2184
- return ($index-$b)%$a == 0
2185
- ? $node
2186
- : null;
2187
- phpQuery::debug($a."*".floor($index/$a)."+$b-1 == ".($a*floor($index/$a)+$b-1)." ?= $prevs");
2188
- return $a*floor($index/$a)+$b-1 == $prevs
2189
- ? $node
2190
- : null;
2191
- } else if ($a == 0)
2192
- return $index == $b
2193
- ? $node
2194
- : null;
2195
- else
2196
- // negative value
2197
- return $index <= $b
2198
- ? $node
2199
- : null;
2200
- // if (! $b)
2201
- // return $index%$a == 0
2202
- // ? $node
2203
- // : null;
2204
- // else
2205
- // return ($index-$b)%$a == 0
2206
- // ? $node
2207
- // : null;
2208
- '),
2209
- new CallbackParam(), $param
2210
- );
2211
- else
2212
- // index
2213
- $mapped = $this->map(
2214
- create_function('$node, $index',
2215
- '$prevs = pq($node)->prevAll()->size();
2216
- if ($prevs && $prevs == $index-1)
2217
- return $node;
2218
- else if (! $prevs && $index == 1)
2219
- return $node;
2220
- else
2221
- return null;'),
2222
- new CallbackParam(), $param
2223
- );
2224
- $this->elements = $mapped->elements;
2225
- break;
2226
- default:
2227
- $this->debug("Unknown pseudoclass '{$class}', skipping...");
2228
- }
2229
- }
2230
- /**
2231
- * @access private
2232
- */
2233
- protected function __pseudoClassParam($paramsString) {
2234
- // TODO;
2235
- }
2236
- /**
2237
- * Enter description here...
2238
- *
2239
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2240
- */
2241
- public function is($selector, $nodes = null) {
2242
- phpQuery::debug(array("Is:", $selector));
2243
- if (! $selector)
2244
- return false;
2245
- $oldStack = $this->elements;
2246
- $returnArray = false;
2247
- if ($nodes && is_array($nodes)) {
2248
- $this->elements = $nodes;
2249
- } else if ($nodes)
2250
- $this->elements = array($nodes);
2251
- $this->filter($selector, true);
2252
- $stack = $this->elements;
2253
- $this->elements = $oldStack;
2254
- if ($nodes)
2255
- return $stack ? $stack : null;
2256
- return (bool)count($stack);
2257
- }
2258
- /**
2259
- * Enter description here...
2260
- * jQuery difference.
2261
- *
2262
- * Callback:
2263
- * - $index int
2264
- * - $node DOMNode
2265
- *
2266
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2267
- * @link http://docs.jquery.com/Traversing/filter
2268
- */
2269
- public function filterCallback($callback, $_skipHistory = false) {
2270
- if (! $_skipHistory) {
2271
- $this->elementsBackup = $this->elements;
2272
- $this->debug("Filtering by callback");
2273
- }
2274
- $newStack = array();
2275
- foreach($this->elements as $index => $node) {
2276
- $result = phpQuery::callbackRun($callback, array($index, $node));
2277
- if (is_null($result) || (! is_null($result) && $result))
2278
- $newStack[] = $node;
2279
- }
2280
- $this->elements = $newStack;
2281
- return $_skipHistory
2282
- ? $this
2283
- : $this->newInstance();
2284
- }
2285
- /**
2286
- * Enter description here...
2287
- *
2288
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2289
- * @link http://docs.jquery.com/Traversing/filter
2290
- */
2291
- public function filter($selectors, $_skipHistory = false) {
2292
- if ($selectors instanceof Callback OR $selectors instanceof Closure)
2293
- return $this->filterCallback($selectors, $_skipHistory);
2294
- if (! $_skipHistory)
2295
- $this->elementsBackup = $this->elements;
2296
- $notSimpleSelector = array(' ', '>', '~', '+', '/');
2297
- if (! is_array($selectors))
2298
- $selectors = $this->parseSelector($selectors);
2299
- if (! $_skipHistory)
2300
- $this->debug(array("Filtering:", $selectors));
2301
- $finalStack = array();
2302
- foreach($selectors as $selector) {
2303
- $stack = array();
2304
- if (! $selector)
2305
- break;
2306
- // avoid first space or /
2307
- if (in_array($selector[0], $notSimpleSelector))
2308
- $selector = array_slice($selector, 1);
2309
- // PER NODE selector chunks
2310
- foreach($this->stack() as $node) {
2311
- $break = false;
2312
- foreach($selector as $s) {
2313
- if (!($node instanceof DOMELEMENT)) {
2314
- // all besides DOMElement
2315
- if ( $s[0] == '[') {
2316
- $attr = trim($s, '[]');
2317
- if ( mb_strpos($attr, '=')) {
2318
- list( $attr, $val ) = explode('=', $attr);
2319
- if ($attr == 'nodeType' && $node->nodeType != $val)
2320
- $break = true;
2321
- }
2322
- } else
2323
- $break = true;
2324
- } else {
2325
- // DOMElement only
2326
- // ID
2327
- if ( $s[0] == '#') {
2328
- if ( $node->getAttribute('id') != substr($s, 1) )
2329
- $break = true;
2330
- // CLASSES
2331
- } else if ( $s[0] == '.') {
2332
- if (! $this->matchClasses( $s, $node ) )
2333
- $break = true;
2334
- // ATTRS
2335
- } else if ( $s[0] == '[') {
2336
- // strip side brackets
2337
- $attr = trim($s, '[]');
2338
- if (mb_strpos($attr, '=')) {
2339
- list($attr, $val) = explode('=', $attr);
2340
- $val = self::unQuote($val);
2341
- if ($attr == 'nodeType') {
2342
- if ($val != $node->nodeType)
2343
- $break = true;
2344
- } else if ($this->isRegexp($attr)) {
2345
- $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport
2346
- ? quotemeta(trim($val, '"\''))
2347
- : preg_quote(trim($val, '"\''), '@');
2348
- // switch last character
2349
- switch( substr($attr, -1)) {
2350
- // quotemeta used insted of preg_quote
2351
- // http://code.google.com/p/phpquery/issues/detail?id=76
2352
- case '^':
2353
- $pattern = '^'.$val;
2354
- break;
2355
- case '*':
2356
- $pattern = '.*'.$val.'.*';
2357
- break;
2358
- case '$':
2359
- $pattern = '.*'.$val.'$';
2360
- break;
2361
- }
2362
- // cut last character
2363
- $attr = substr($attr, 0, -1);
2364
- $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport
2365
- ? mb_ereg_match($pattern, $node->getAttribute($attr))
2366
- : preg_match("@{$pattern}@", $node->getAttribute($attr));
2367
- if (! $isMatch)
2368
- $break = true;
2369
- } else if ($node->getAttribute($attr) != $val)
2370
- $break = true;
2371
- } else if (! $node->hasAttribute($attr))
2372
- $break = true;
2373
- // PSEUDO CLASSES
2374
- } else if ( $s[0] == ':') {
2375
- // skip
2376
- // TAG
2377
- } else if (trim($s)) {
2378
- if ($s != '*') {
2379
- // TODO namespaces
2380
- if (isset($node->tagName)) {
2381
- if ($node->tagName != $s)
2382
- $break = true;
2383
- } else if ($s == 'html' && ! $this->isRoot($node))
2384
- $break = true;
2385
- }
2386
- // AVOID NON-SIMPLE SELECTORS
2387
- } else if (in_array($s, $notSimpleSelector)) {
2388
- $break = true;
2389
- $this->debug(array('Skipping non simple selector', $selector));
2390
- }
2391
- }
2392
- if ($break)
2393
- break;
2394
- }
2395
- // if element passed all chunks of selector - add it to new stack
2396
- if (! $break )
2397
- $stack[] = $node;
2398
- }
2399
- $tmpStack = $this->elements;
2400
- $this->elements = $stack;
2401
- // PER ALL NODES selector chunks
2402
- foreach($selector as $s)
2403
- // PSEUDO CLASSES
2404
- if ($s[0] == ':')
2405
- $this->pseudoClasses($s);
2406
- foreach($this->elements as $node)
2407
- // XXX it should be merged without duplicates
2408
- // but jQuery doesnt do that
2409
- $finalStack[] = $node;
2410
- $this->elements = $tmpStack;
2411
- }
2412
- $this->elements = $finalStack;
2413
- if ($_skipHistory) {
2414
- return $this;
2415
- } else {
2416
- $this->debug("Stack length after filter(): ".count($finalStack));
2417
- return $this->newInstance();
2418
- }
2419
- }
2420
- /**
2421
- *
2422
- * @param $value
2423
- * @return unknown_type
2424
- * @TODO implement in all methods using passed parameters
2425
- */
2426
- protected static function unQuote($value) {
2427
- return $value[0] == '\'' || $value[0] == '"'
2428
- ? substr($value, 1, -1)
2429
- : $value;
2430
- }
2431
- /**
2432
- * Enter description here...
2433
- *
2434
- * @link http://docs.jquery.com/Ajax/load
2435
- * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2436
- * @todo Support $selector
2437
- */
2438
- public function load($url, $data = null, $callback = null) {
2439
- if ($data && ! is_array($data)) {
2440
- $callback = $data;
2441
- $data = null;
2442
- }
2443
- if (mb_strpos($url, ' ') !== false) {
2444
- $matches = null;
2445
- if (extension_loaded('mbstring') && phpQuery::$mbstringSupport)
2446
- mb_ereg('^([^ ]+) (.*)$', $url, $matches);
2447
- else
2448
- preg_match('^([^ ]+) (.*)$', $url, $matches);
2449
- $url = $matches[1];
2450
- $selector = $matches[2];
2451
- // FIXME this sucks, pass as callback param
2452
- $this->_loadSelector = $selector;
2453
- }
2454
- $ajax = array(
2455
- 'url' => $url,
2456
- 'type' => $data ? 'POST' : 'GET',
2457
- 'data' => $data,
2458
- 'complete' => $callback,
2459
- 'success' => array($this, '__loadSuccess')
2460
- );
2461
- phpQuery::ajax($ajax);
2462
- return $this;
2463
- }
2464
- /**
2465
- * @access private
2466
- * @param $html
2467
- * @return unknown_type
2468
- */
2469
- public function __loadSuccess($html) {
2470
- if ($this->_loadSelector) {
2471
- $html = phpQuery::newDocument($html)->find($this->_loadSelector);
2472
- unset($this->_loadSelector);
2473
- }
2474
- foreach($this->stack(1) as $node) {
2475
- phpQuery::pq($node, $this->getDocumentID())
2476
- ->markup($html);
2477
- }
2478
- }
2479
- /**
2480
- * Enter description here...
2481
- *
2482
- * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2483
- * @todo
2484
- */
2485
- public function css() {
2486
- // TODO
2487
- return $this;
2488
- }
2489
- /**
2490
- * @todo
2491
- *
2492
- */
2493
- public function show(){
2494
- // TODO
2495
- return $this;
2496
- }
2497
- /**
2498
- * @todo
2499
- *
2500
- */
2501
- public function hide(){
2502
- // TODO
2503
- return $this;
2504
- }
2505
- /**
2506
- * Trigger a type of event on every matched element.
2507
- *
2508
- * @param unknown_type $type
2509
- * @param unknown_type $data
2510
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2511
- * @TODO support more than event in $type (space-separated)
2512
- */
2513
- public function trigger($type, $data = array()) {
2514
- foreach($this->elements as $node)
2515
- phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node);
2516
- return $this;
2517
- }
2518
- /**
2519
- * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions.
2520
- *
2521
- * @param unknown_type $type
2522
- * @param unknown_type $data
2523
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2524
- * @TODO
2525
- */
2526
- public function triggerHandler($type, $data = array()) {
2527
- // TODO;
2528
- }
2529
- /**
2530
- * Binds a handler to one or more events (like click) for each matched element.
2531
- * Can also bind custom events.
2532
- *
2533
- * @param unknown_type $type
2534
- * @param unknown_type $data Optional
2535
- * @param unknown_type $callback
2536
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2537
- * @TODO support '!' (exclusive) events
2538
- * @TODO support more than event in $type (space-separated)
2539
- */
2540
- public function bind($type, $data, $callback = null) {
2541
- // TODO check if $data is callable, not using is_callable
2542
- if (! isset($callback)) {
2543
- $callback = $data;
2544
- $data = null;
2545
- }
2546
- foreach($this->elements as $node)
2547
- phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback);
2548
- return $this;
2549
- }
2550
- /**
2551
- * Enter description here...
2552
- *
2553
- * @param unknown_type $type
2554
- * @param unknown_type $callback
2555
- * @return unknown
2556
- * @TODO namespace events
2557
- * @TODO support more than event in $type (space-separated)
2558
- */
2559
- public function unbind($type = null, $callback = null) {
2560
- foreach($this->elements as $node)
2561
- phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback);
2562
- return $this;
2563
- }
2564
- /**
2565
- * Enter description here...
2566
- *
2567
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2568
- */
2569
- public function change($callback = null) {
2570
- if ($callback)
2571
- return $this->bind('change', $callback);
2572
- return $this->trigger('change');
2573
- }
2574
- /**
2575
- * Enter description here...
2576
- *
2577
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2578
- */
2579
- public function submit($callback = null) {
2580
- if ($callback)
2581
- return $this->bind('submit', $callback);
2582
- return $this->trigger('submit');
2583
- }
2584
- /**
2585
- * Enter description here...
2586
- *
2587
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2588
- */
2589
- public function click($callback = null) {
2590
- if ($callback)
2591
- return $this->bind('click', $callback);
2592
- return $this->trigger('click');
2593
- }
2594
- /**
2595
- * Enter description here...
2596
- *
2597
- * @param String|phpQuery
2598
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2599
- */
2600
- public function wrapAllOld($wrapper) {
2601
- $wrapper = pq($wrapper)->_clone();
2602
- if (! $wrapper->length() || ! $this->length() )
2603
- return $this;
2604
- $wrapper->insertBefore($this->elements[0]);
2605
- $deepest = $wrapper->elements[0];
2606
- while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
2607
- $deepest = $deepest->firstChild;
2608
- pq($deepest)->append($this);
2609
- return $this;
2610
- }
2611
- /**
2612
- * Enter description here...
2613
- *
2614
- * TODO testme...
2615
- * @param String|phpQuery
2616
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2617
- */
2618
- public function wrapAll($wrapper) {
2619
- if (! $this->length())
2620
- return $this;
2621
- return phpQuery::pq($wrapper, $this->getDocumentID())
2622
- ->clone()
2623
- ->insertBefore($this->get(0))
2624
- ->map(array($this, '___wrapAllCallback'))
2625
- ->append($this);
2626
- }
2627
- /**
2628
- *
2629
- * @param $node
2630
- * @return unknown_type
2631
- * @access private
2632
- */
2633
- public function ___wrapAllCallback($node) {
2634
- $deepest = $node;
2635
- while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
2636
- $deepest = $deepest->firstChild;
2637
- return $deepest;
2638
- }
2639
- /**
2640
- * Enter description here...
2641
- * NON JQUERY METHOD
2642
- *
2643
- * @param String|phpQuery
2644
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2645
- */
2646
- public function wrapAllPHP($codeBefore, $codeAfter) {
2647
- return $this
2648
- ->slice(0, 1)
2649
- ->beforePHP($codeBefore)
2650
- ->end()
2651
- ->slice(-1)
2652
- ->afterPHP($codeAfter)
2653
- ->end();
2654
- }
2655
- /**
2656
- * Enter description here...
2657
- *
2658
- * @param String|phpQuery
2659
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2660
- */
2661
- public function wrap($wrapper) {
2662
- foreach($this->stack() as $node)
2663
- phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper);
2664
- return $this;
2665
- }
2666
- /**
2667
- * Enter description here...
2668
- *
2669
- * @param String|phpQuery
2670
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2671
- */
2672
- public function wrapPHP($codeBefore, $codeAfter) {
2673
- foreach($this->stack() as $node)
2674
- phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter);
2675
- return $this;
2676
- }
2677
- /**
2678
- * Enter description here...
2679
- *
2680
- * @param String|phpQuery
2681
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2682
- */
2683
- public function wrapInner($wrapper) {
2684
- foreach($this->stack() as $node)
2685
- phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper);
2686
- return $this;
2687
- }
2688
- /**
2689
- * Enter description here...
2690
- *
2691
- * @param String|phpQuery
2692
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2693
- */
2694
- public function wrapInnerPHP($codeBefore, $codeAfter) {
2695
- foreach($this->stack(1) as $node)
2696
- phpQuery::pq($node, $this->getDocumentID())->contents()
2697
- ->wrapAllPHP($codeBefore, $codeAfter);
2698
- return $this;
2699
- }
2700
- /**
2701
- * Enter description here...
2702
- *
2703
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2704
- * @testme Support for text nodes
2705
- */
2706
- public function contents() {
2707
- $stack = array();
2708
- foreach($this->stack(1) as $el) {
2709
- // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56
2710
- // if (! isset($el->childNodes))
2711
- // continue;
2712
- foreach($el->childNodes as $node) {
2713
- $stack[] = $node;
2714
- }
2715
- }
2716
- return $this->newInstance($stack);
2717
- }
2718
- /**
2719
- * Enter description here...
2720
- *
2721
- * jQuery difference.
2722
- *
2723
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2724
- */
2725
- public function contentsUnwrap() {
2726
- foreach($this->stack(1) as $node) {
2727
- if (! $node->parentNode )
2728
- continue;
2729
- $childNodes = array();
2730
- // any modification in DOM tree breaks childNodes iteration, so cache them first
2731
- foreach($node->childNodes as $chNode )
2732
- $childNodes[] = $chNode;
2733
- foreach($childNodes as $chNode )
2734
- // $node->parentNode->appendChild($chNode);
2735
- $node->parentNode->insertBefore($chNode, $node);
2736
- $node->parentNode->removeChild($node);
2737
- }
2738
- return $this;
2739
- }
2740
- /**
2741
- * Enter description here...
2742
- *
2743
- * jQuery difference.
2744
- */
2745
- public function switchWith($markup) {
2746
- $markup = pq($markup, $this->getDocumentID());
2747
- $content = null;
2748
- foreach($this->stack(1) as $node) {
2749
- pq($node)
2750
- ->contents()->toReference($content)->end()
2751
- ->replaceWith($markup->clone()->append($content));
2752
- }
2753
- return $this;
2754
- }
2755
- /**
2756
- * Enter description here...
2757
- *
2758
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2759
- */
2760
- public function eq($num) {
2761
- $oldStack = $this->elements;
2762
- $this->elementsBackup = $this->elements;
2763
- $this->elements = array();
2764
- if ( isset($oldStack[$num]) )
2765
- $this->elements[] = $oldStack[$num];
2766
- return $this->newInstance();
2767
- }
2768
- /**
2769
- * Enter description here...
2770
- *
2771
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2772
- */
2773
- public function size() {
2774
- return count($this->elements);
2775
- }
2776
- /**
2777
- * Enter description here...
2778
- *
2779
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2780
- * @deprecated Use length as attribute
2781
- */
2782
- public function length() {
2783
- return $this->size();
2784
- }
2785
- public function count() {
2786
- return $this->size();
2787
- }
2788
- /**
2789
- * Enter description here...
2790
- *
2791
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2792
- * @todo $level
2793
- */
2794
- public function end($level = 1) {
2795
- // $this->elements = array_pop( $this->history );
2796
- // return $this;
2797
- // $this->previous->DOM = $this->DOM;
2798
- // $this->previous->XPath = $this->XPath;
2799
- return $this->previous
2800
- ? $this->previous
2801
- : $this;
2802
- }
2803
- /**
2804
- * Enter description here...
2805
- * Normal use ->clone() .
2806
- *
2807
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2808
- * @access private
2809
- */
2810
- public function _clone() {
2811
- $newStack = array();
2812
- //pr(array('copy... ', $this->whois()));
2813
- //$this->dumpHistory('copy');
2814
- $this->elementsBackup = $this->elements;
2815
- foreach($this->elements as $node) {
2816
- $newStack[] = $node->cloneNode(true);
2817
- }
2818
- $this->elements = $newStack;
2819
- return $this->newInstance();
2820
- }
2821
- /**
2822
- * Enter description here...
2823
- *
2824
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2825
- */
2826
- public function replaceWithPHP($code) {
2827
- return $this->replaceWith(phpQuery::php($code));
2828
- }
2829
- /**
2830
- * Enter description here...
2831
- *
2832
- * @param String|phpQuery $content
2833
- * @link http://docs.jquery.com/Manipulation/replaceWith#content
2834
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2835
- */
2836
- public function replaceWith($content) {
2837
- return $this->after($content)->remove();
2838
- }
2839
- /**
2840
- * Enter description here...
2841
- *
2842
- * @param String $selector
2843
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2844
- * @todo this works ?
2845
- */
2846
- public function replaceAll($selector) {
2847
- foreach(phpQuery::pq($selector, $this->getDocumentID()) as $node)
2848
- phpQuery::pq($node, $this->getDocumentID())
2849
- ->after($this->_clone())
2850
- ->remove();
2851
- return $this;
2852
- }
2853
- /**
2854
- * Enter description here...
2855
- *
2856
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2857
- */
2858
- public function remove($selector = null) {
2859
- $loop = $selector
2860
- ? $this->filter($selector)->elements
2861
- : $this->elements;
2862
- foreach($loop as $node) {
2863
- if (! $node->parentNode )
2864
- continue;
2865
- if (isset($node->tagName))
2866
- $this->debug("Removing '{$node->tagName}'");
2867
- $node->parentNode->removeChild($node);
2868
- // Mutation event
2869
- $event = new DOMEvent(array(
2870
- 'target' => $node,
2871
- 'type' => 'DOMNodeRemoved'
2872
- ));
2873
- phpQueryEvents::trigger($this->getDocumentID(),
2874
- $event->type, array($event), $node
2875
- );
2876
- }
2877
- return $this;
2878
- }
2879
- protected function markupEvents($newMarkup, $oldMarkup, $node) {
2880
- if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) {
2881
- $event = new DOMEvent(array(
2882
- 'target' => $node,
2883
- 'type' => 'change'
2884
- ));
2885
- phpQueryEvents::trigger($this->getDocumentID(),
2886
- $event->type, array($event), $node
2887
- );
2888
- }
2889
- }
2890
- /**
2891
- * jQuey difference
2892
- *
2893
- * @param $markup
2894
- * @return unknown_type
2895
- * @TODO trigger change event for textarea
2896
- */
2897
- public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2898
- $args = func_get_args();
2899
- if ($this->documentWrapper->isXML)
2900
- return call_user_func_array(array($this, 'xml'), $args);
2901
- else
2902
- return call_user_func_array(array($this, 'html'), $args);
2903
- }
2904
- /**
2905
- * jQuey difference
2906
- *
2907
- * @param $markup
2908
- * @return unknown_type
2909
- */
2910
- public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2911
- $args = func_get_args();
2912
- if ($this->documentWrapper->isXML)
2913
- return call_user_func_array(array($this, 'xmlOuter'), $args);
2914
- else
2915
- return call_user_func_array(array($this, 'htmlOuter'), $args);
2916
- }
2917
- /**
2918
- * Enter description here...
2919
- *
2920
- * @param unknown_type $html
2921
- * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2922
- * @TODO force html result
2923
- */
2924
- public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2925
- if (isset($html)) {
2926
- // INSERT
2927
- $nodes = $this->documentWrapper->import($html);
2928
- $this->empty();
2929
- foreach($this->stack(1) as $alreadyAdded => $node) {
2930
- // for now, limit events for textarea
2931
- if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
2932
- $oldHtml = pq($node, $this->getDocumentID())->markup();
2933
- foreach($nodes as $newNode) {
2934
- $node->appendChild($alreadyAdded
2935
- ? $newNode->cloneNode(true)
2936
- : $newNode
2937
- );
2938
- }
2939
- // for now, limit events for textarea
2940
- if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
2941
- $this->markupEvents($html, $oldHtml, $node);
2942
- }
2943
- return $this;
2944
- } else {
2945
- // FETCH
2946
- $return = $this->documentWrapper->markup($this->elements, true);
2947
- $args = func_get_args();
2948
- foreach(array_slice($args, 1) as $callback) {
2949
- $return = phpQuery::callbackRun($callback, array($return));
2950
- }
2951
- return $return;
2952
- }
2953
- }
2954
- /**
2955
- * @TODO force xml result
2956
- */
2957
- public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2958
- $args = func_get_args();
2959
- return call_user_func_array(array($this, 'html'), $args);
2960
- }
2961
- /**
2962
- * Enter description here...
2963
- * @TODO force html result
2964
- *
2965
- * @return String
2966
- */
2967
- public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2968
- $markup = $this->documentWrapper->markup($this->elements);
2969
- // pass thou callbacks
2970
- $args = func_get_args();
2971
- foreach($args as $callback) {
2972
- $markup = phpQuery::callbackRun($callback, array($markup));
2973
- }
2974
- return $markup;
2975
- }
2976
- /**
2977
- * @TODO force xml result
2978
- */
2979
- public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2980
- $args = func_get_args();
2981
- return call_user_func_array(array($this, 'htmlOuter'), $args);
2982
- }
2983
- public function __toString() {
2984
- return $this->markupOuter();
2985
- }
2986
- /**
2987
- * Just like html(), but returns markup with VALID (dangerous) PHP tags.
2988
- *
2989
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2990
- * @todo support returning markup with PHP tags when called without param
2991
- */
2992
- public function php($code = null) {
2993
- return $this->markupPHP($code);
2994
- }
2995
- /**
2996
- * Enter description here...
2997
- *
2998
- * @param $code
2999
- * @return unknown_type
3000
- */
3001
- public function markupPHP($code = null) {
3002
- return isset($code)
3003
- ? $this->markup(phpQuery::php($code))
3004
- : phpQuery::markupToPHP($this->markup());
3005
- }
3006
- /**
3007
- * Enter description here...
3008
- *
3009
- * @param $code
3010
- * @return unknown_type
3011
- */
3012
- public function markupOuterPHP() {
3013
- return phpQuery::markupToPHP($this->markupOuter());
3014
- }
3015
- /**
3016
- * Enter description here...
3017
- *
3018
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3019
- */
3020
- public function children($selector = null) {
3021
- $stack = array();
3022
- foreach($this->stack(1) as $node) {
3023
- // foreach($node->getElementsByTagName('*') as $newNode) {
3024
- foreach($node->childNodes as $newNode) {
3025
- if ($newNode->nodeType != 1)
3026
- continue;
3027
- if ($selector && ! $this->is($selector, $newNode))
3028
- continue;
3029
- if ($this->elementsContainsNode($newNode, $stack))
3030
- continue;
3031
- $stack[] = $newNode;
3032
- }
3033
- }
3034
- $this->elementsBackup = $this->elements;
3035
- $this->elements = $stack;
3036
- return $this->newInstance();
3037
- }
3038
- /**
3039
- * Enter description here...
3040
- *
3041
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3042
- */
3043
- public function ancestors($selector = null) {
3044
- return $this->children( $selector );
3045
- }
3046
- /**
3047
- * Enter description here...
3048
- *
3049
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3050
- */
3051
- public function append( $content) {
3052
- return $this->insert($content, __FUNCTION__);
3053
- }
3054
- /**
3055
- * Enter description here...
3056
- *
3057
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3058
- */
3059
- public function appendPHP( $content) {
3060
- return $this->insert("<php><!-- {$content} --></php>", 'append');
3061
- }
3062
- /**
3063
- * Enter description here...
3064
- *
3065
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3066
- */
3067
- public function appendTo( $seletor) {
3068
- return $this->insert($seletor, __FUNCTION__);
3069
- }
3070
- /**
3071
- * Enter description here...
3072
- *
3073
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3074
- */
3075
- public function prepend( $content) {
3076
- return $this->insert($content, __FUNCTION__);
3077
- }
3078
- /**
3079
- * Enter description here...
3080
- *
3081
- * @todo accept many arguments, which are joined, arrays maybe also
3082
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3083
- */
3084
- public function prependPHP( $content) {
3085
- return $this->insert("<php><!-- {$content} --></php>", 'prepend');
3086
- }
3087
- /**
3088
- * Enter description here...
3089
- *
3090
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3091
- */
3092
- public function prependTo( $seletor) {
3093
- return $this->insert($seletor, __FUNCTION__);
3094
- }
3095
- /**
3096
- * Enter description here...
3097
- *
3098
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3099
- */
3100
- public function before($content) {
3101
- return $this->insert($content, __FUNCTION__);
3102
- }
3103
- /**
3104
- * Enter description here...
3105
- *
3106
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3107
- */
3108
- public function beforePHP( $content) {
3109
- return $this->insert("<php><!-- {$content} --></php>", 'before');
3110
- }
3111
- /**
3112
- * Enter description here...
3113
- *
3114
- * @param String|phpQuery
3115
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3116
- */
3117
- public function insertBefore( $seletor) {
3118
- return $this->insert($seletor, __FUNCTION__);
3119
- }
3120
- /**
3121
- * Enter description here...
3122
- *
3123
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3124
- */
3125
- public function after( $content) {
3126
- return $this->insert($content, __FUNCTION__);
3127
- }
3128
- /**
3129
- * Enter description here...
3130
- *
3131
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3132
- */
3133
- public function afterPHP( $content) {
3134
- return $this->insert("<php><!-- {$content} --></php>", 'after');
3135
- }
3136
- /**
3137
- * Enter description here...
3138
- *
3139
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3140
- */
3141
- public function insertAfter( $seletor) {
3142
- return $this->insert($seletor, __FUNCTION__);
3143
- }
3144
- /**
3145
- * Internal insert method. Don't use it.
3146
- *
3147
- * @param unknown_type $target
3148
- * @param unknown_type $type
3149
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3150
- * @access private
3151
- */
3152
- public function insert($target, $type) {
3153
- $this->debug("Inserting data with '{$type}'");
3154
- $to = false;
3155
- switch( $type) {
3156
- case 'appendTo':
3157
- case 'prependTo':
3158
- case 'insertBefore':
3159
- case 'insertAfter':
3160
- $to = true;
3161
- }
3162
- switch(gettype($target)) {
3163
- case 'string':
3164
- $insertFrom = $insertTo = array();
3165
- if ($to) {
3166
- // INSERT TO
3167
- $insertFrom = $this->elements;
3168
- if (phpQuery::isMarkup($target)) {
3169
- // $target is new markup, import it
3170
- $insertTo = $this->documentWrapper->import($target);
3171
- // insert into selected element
3172
- } else {
3173
- // $tagret is a selector
3174
- $thisStack = $this->elements;
3175
- $this->toRoot();
3176
- $insertTo = $this->find($target)->elements;
3177
- $this->elements = $thisStack;
3178
- }
3179
- } else {
3180
- // INSERT FROM
3181
- $insertTo = $this->elements;
3182
- $insertFrom = $this->documentWrapper->import($target);
3183
- }
3184
- break;
3185
- case 'object':
3186
- $insertFrom = $insertTo = array();
3187
- // phpQuery
3188
- if ($target instanceof self) {
3189
- if ($to) {
3190
- $insertTo = $target->elements;
3191
- if ($this->documentFragment && $this->stackIsRoot())
3192
- // get all body children
3193
- // $loop = $this->find('body > *')->elements;
3194
- // TODO test it, test it hard...
3195
- // $loop = $this->newInstance($this->root)->find('> *')->elements;
3196
- $loop = $this->root->childNodes;
3197
- else
3198
- $loop = $this->elements;
3199
- // import nodes if needed
3200
- $insertFrom = $this->getDocumentID() == $target->getDocumentID()
3201
- ? $loop
3202
- : $target->documentWrapper->import($loop);
3203
- } else {
3204
- $insertTo = $this->elements;
3205
- if ( $target->documentFragment && $target->stackIsRoot() )
3206
- // get all body children
3207
- // $loop = $target->find('body > *')->elements;
3208
- $loop = $target->root->childNodes;
3209
- else
3210
- $loop = $target->elements;
3211
- // import nodes if needed
3212
- $insertFrom = $this->getDocumentID() == $target->getDocumentID()
3213
- ? $loop
3214
- : $this->documentWrapper->import($loop);
3215
- }
3216
- // DOMNODE
3217
- } elseif ($target instanceof DOMNODE) {
3218
- // import node if needed
3219
- // if ( $target->ownerDocument != $this->DOM )
3220
- // $target = $this->DOM->importNode($target, true);
3221
- if ( $to) {
3222
- $insertTo = array($target);
3223
- if ($this->documentFragment && $this->stackIsRoot())
3224
- // get all body children
3225
- $loop = $this->root->childNodes;
3226
- // $loop = $this->find('body > *')->elements;
3227
- else
3228
- $loop = $this->elements;
3229
- foreach($loop as $fromNode)
3230
- // import nodes if needed
3231
- $insertFrom[] = ! $fromNode->ownerDocument->isSameNode($target->ownerDocument)
3232
- ? $target->ownerDocument->importNode($fromNode, true)
3233
- : $fromNode;
3234
- } else {
3235
- // import node if needed
3236
- if (! $target->ownerDocument->isSameNode($this->document))
3237
- $target = $this->document->importNode($target, true);
3238
- $insertTo = $this->elements;
3239
- $insertFrom[] = $target;
3240
- }
3241
- }
3242
- break;
3243
- }
3244
- phpQuery::debug("From ".count($insertFrom)."; To ".count($insertTo)." nodes");
3245
- foreach($insertTo as $insertNumber => $toNode) {
3246
- // we need static relative elements in some cases
3247
- switch( $type) {
3248
- case 'prependTo':
3249
- case 'prepend':
3250
- $firstChild = $toNode->firstChild;
3251
- break;
3252
- case 'insertAfter':
3253
- case 'after':
3254
- $nextSibling = $toNode->nextSibling;
3255
- break;
3256
- }
3257
- foreach($insertFrom as $fromNode) {
3258
- // clone if inserted already before
3259
- $insert = $insertNumber
3260
- ? $fromNode->cloneNode(true)
3261
- : $fromNode;
3262
- switch($type) {
3263
- case 'appendTo':
3264
- case 'append':
3265
- // $toNode->insertBefore(
3266
- // $fromNode,
3267
- // $toNode->lastChild->nextSibling
3268
- // );
3269
- $toNode->appendChild($insert);
3270
- $eventTarget = $insert;
3271
- break;
3272
- case 'prependTo':
3273
- case 'prepend':
3274
- $toNode->insertBefore(
3275
- $insert,
3276
- $firstChild
3277
- );
3278
- break;
3279
- case 'insertBefore':
3280
- case 'before':
3281
- if (! $toNode->parentNode)
3282
- throw new Exception("No parentNode, can't do {$type}()");
3283
- else
3284
- $toNode->parentNode->insertBefore(
3285
- $insert,
3286
- $toNode
3287
- );
3288
- break;
3289
- case 'insertAfter':
3290
- case 'after':
3291
- if (! $toNode->parentNode)
3292
- throw new Exception("No parentNode, can't do {$type}()");
3293
- else
3294
- $toNode->parentNode->insertBefore(
3295
- $insert,
3296
- $nextSibling
3297
- );
3298
- break;
3299
- }
3300
- // Mutation event
3301
- $event = new DOMEvent(array(
3302
- 'target' => $insert,
3303
- 'type' => 'DOMNodeInserted'
3304
- ));
3305
- phpQueryEvents::trigger($this->getDocumentID(),
3306
- $event->type, array($event), $insert
3307
- );
3308
- }
3309
- }
3310
- return $this;
3311
- }
3312
- /**
3313
- * Enter description here...
3314
- *
3315
- * @return Int
3316
- */
3317
- public function index($subject) {
3318
- $index = -1;
3319
- $subject = $subject instanceof phpQueryObject
3320
- ? $subject->elements[0]
3321
- : $subject;
3322
- foreach($this->newInstance() as $k => $node) {
3323
- if ($node->isSameNode($subject))
3324
- $index = $k;
3325
- }
3326
- return $index;
3327
- }
3328
- /**
3329
- * Enter description here...
3330
- *
3331
- * @param unknown_type $start
3332
- * @param unknown_type $end
3333
- *
3334
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3335
- * @testme
3336
- */
3337
- public function slice($start, $end = null) {
3338
- // $last = count($this->elements)-1;
3339
- // $end = $end
3340
- // ? min($end, $last)
3341
- // : $last;
3342
- // if ($start < 0)
3343
- // $start = $last+$start;
3344
- // if ($start > $last)
3345
- // return array();
3346
- if ($end > 0)
3347
- $end = $end-$start;
3348
- return $this->newInstance(
3349
- array_slice($this->elements, $start, $end)
3350
- );
3351
- }
3352
- /**
3353
- * Enter description here...
3354
- *
3355
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3356
- */
3357
- public function reverse() {
3358
- $this->elementsBackup = $this->elements;
3359
- $this->elements = array_reverse($this->elements);
3360
- return $this->newInstance();
3361
- }
3362
- /**
3363
- * Return joined text content.
3364
- * @return String
3365
- */
3366
- public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) {
3367
- if (isset($text))
3368
- return $this->html(htmlspecialchars($text));
3369
- $args = func_get_args();
3370
- $args = array_slice($args, 1);
3371
- $return = '';
3372
- foreach($this->elements as $node) {
3373
- $text = $node->textContent;
3374
- if (count($this->elements) > 1 && $text)
3375
- $text .= "\n";
3376
- foreach($args as $callback) {
3377
- $text = phpQuery::callbackRun($callback, array($text));
3378
- }
3379
- $return .= $text;
3380
- }
3381
- return $return;
3382
- }
3383
- /**
3384
- * Enter description here...
3385
- *
3386
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3387
- */
3388
- public function plugin($class, $file = null) {
3389
- phpQuery::plugin($class, $file);
3390
- return $this;
3391
- }
3392
- /**
3393
- * Deprecated, use $pq->plugin() instead.
3394
- *
3395
- * @deprecated
3396
- * @param $class
3397
- * @param $file
3398
- * @return unknown_type
3399
- */
3400
- public static function extend($class, $file = null) {
3401
- return $this->plugin($class, $file);
3402
- }
3403
- /**
3404
- *
3405
- * @access private
3406
- * @param $method
3407
- * @param $args
3408
- * @return unknown_type
3409
- */
3410
- public function __call($method, $args) {
3411
- $aliasMethods = array('clone', 'empty');
3412
- if (isset(phpQuery::$extendMethods[$method])) {
3413
- array_unshift($args, $this);
3414
- return phpQuery::callbackRun(
3415
- phpQuery::$extendMethods[$method], $args
3416
- );
3417
- } else if (isset(phpQuery::$pluginsMethods[$method])) {
3418
- array_unshift($args, $this);
3419
- $class = phpQuery::$pluginsMethods[$method];
3420
- $realClass = "phpQueryObjectPlugin_$class";
3421
- $return = call_user_func_array(
3422
- array($realClass, $method),
3423
- $args
3424
- );
3425
- // XXX deprecate ?
3426
- return is_null($return)
3427
- ? $this
3428
- : $return;
3429
- } else if (in_array($method, $aliasMethods)) {
3430
- return call_user_func_array(array($this, '_'.$method), $args);
3431
- } else
3432
- throw new Exception("Method '{$method}' doesnt exist");
3433
- }
3434
- /**
3435
- * Safe rename of next().
3436
- *
3437
- * Use it ONLY when need to call next() on an iterated object (in same time).
3438
- * Normaly there is no need to do such thing ;)
3439
- *
3440
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3441
- * @access private
3442
- */
3443
- public function _next($selector = null) {
3444
- return $this->newInstance(
3445
- $this->getElementSiblings('nextSibling', $selector, true)
3446
- );
3447
- }
3448
- /**
3449
- * Use prev() and next().
3450
- *
3451
- * @deprecated
3452
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3453
- * @access private
3454
- */
3455
- public function _prev($selector = null) {
3456
- return $this->prev($selector);
3457
- }
3458
- /**
3459
- * Enter description here...
3460
- *
3461
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3462
- */
3463
- public function prev($selector = null) {
3464
- return $this->newInstance(
3465
- $this->getElementSiblings('previousSibling', $selector, true)
3466
- );
3467
- }
3468
- /**
3469
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3470
- * @todo
3471
- */
3472
- public function prevAll($selector = null) {
3473
- return $this->newInstance(
3474
- $this->getElementSiblings('previousSibling', $selector)
3475
- );
3476
- }
3477
- /**
3478
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3479
- * @todo FIXME: returns source elements insted of next siblings
3480
- */
3481
- public function nextAll($selector = null) {
3482
- return $this->newInstance(
3483
- $this->getElementSiblings('nextSibling', $selector)
3484
- );
3485
- }
3486
- /**
3487
- * @access private
3488
- */
3489
- protected function getElementSiblings($direction, $selector = null, $limitToOne = false) {
3490
- $stack = array();
3491
- $count = 0;
3492
- foreach($this->stack() as $node) {
3493
- $test = $node;
3494
- while( isset($test->{$direction}) && $test->{$direction}) {
3495
- $test = $test->{$direction};
3496
- if (! $test instanceof DOMELEMENT)
3497
- continue;
3498
- $stack[] = $test;
3499
- if ($limitToOne)
3500
- break;
3501
- }
3502
- }
3503
- if ($selector) {
3504
- $stackOld = $this->elements;
3505
- $this->elements = $stack;
3506
- $stack = $this->filter($selector, true)->stack();
3507
- $this->elements = $stackOld;
3508
- }
3509
- return $stack;
3510
- }
3511
- /**
3512
- * Enter description here...
3513
- *
3514
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3515
- */
3516
- public function siblings($selector = null) {
3517
- $stack = array();
3518
- $siblings = array_merge(
3519
- $this->getElementSiblings('previousSibling', $selector),
3520
- $this->getElementSiblings('nextSibling', $selector)
3521
- );
3522
- foreach($siblings as $node) {
3523
- if (! $this->elementsContainsNode($node, $stack))
3524
- $stack[] = $node;
3525
- }
3526
- return $this->newInstance($stack);
3527
- }
3528
- /**
3529
- * Enter description here...
3530
- *
3531
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3532
- */
3533
- public function not($selector = null) {
3534
- if (is_string($selector))
3535
- phpQuery::debug(array('not', $selector));
3536
- else
3537
- phpQuery::debug('not');
3538
- $stack = array();
3539
- if ($selector instanceof self || $selector instanceof DOMNODE) {
3540
- foreach($this->stack() as $node) {
3541
- if ($selector instanceof self) {
3542
- $matchFound = false;
3543
- foreach($selector->stack() as $notNode) {
3544
- if ($notNode->isSameNode($node))
3545
- $matchFound = true;
3546
- }
3547
- if (! $matchFound)
3548
- $stack[] = $node;
3549
- } else if ($selector instanceof DOMNODE) {
3550
- if (! $selector->isSameNode($node))
3551
- $stack[] = $node;
3552
- } else {
3553
- if (! $this->is($selector))
3554
- $stack[] = $node;
3555
- }
3556
- }
3557
- } else {
3558
- $orgStack = $this->stack();
3559
- $matched = $this->filter($selector, true)->stack();
3560
- // $matched = array();
3561
- // // simulate OR in filter() instead of AND 5y
3562
- // foreach($this->parseSelector($selector) as $s) {
3563
- // $matched = array_merge($matched,
3564
- // $this->filter(array($s))->stack()
3565
- // );
3566
- // }
3567
- foreach($orgStack as $node)
3568
- if (! $this->elementsContainsNode($node, $matched))
3569
- $stack[] = $node;
3570
- }
3571
- return $this->newInstance($stack);
3572
- }
3573
- /**
3574
- * Enter description here...
3575
- *
3576
- * @param string|phpQueryObject
3577
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3578
- */
3579
- public function add($selector = null) {
3580
- if (! $selector)
3581
- return $this;
3582
- $stack = array();
3583
- $this->elementsBackup = $this->elements;
3584
- $found = phpQuery::pq($selector, $this->getDocumentID());
3585
- $this->merge($found->elements);
3586
- return $this->newInstance();
3587
- }
3588
- /**
3589
- * @access private
3590
- */
3591
- protected function merge() {
3592
- foreach(func_get_args() as $nodes)
3593
- foreach($nodes as $newNode )
3594
- if (! $this->elementsContainsNode($newNode) )
3595
- $this->elements[] = $newNode;
3596
- }
3597
- /**
3598
- * @access private
3599
- * TODO refactor to stackContainsNode
3600
- */
3601
- protected function elementsContainsNode($nodeToCheck, $elementsStack = null) {
3602
- $loop = ! is_null($elementsStack)
3603
- ? $elementsStack
3604
- : $this->elements;
3605
- foreach($loop as $node) {
3606
- if ( $node->isSameNode( $nodeToCheck ) )
3607
- return true;
3608
- }
3609
- return false;
3610
- }
3611
- /**
3612
- * Enter description here...
3613
- *
3614
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3615
- */
3616
- public function parent($selector = null) {
3617
- $stack = array();
3618
- foreach($this->elements as $node )
3619
- if ( $node->parentNode && ! $this->elementsContainsNode($node->parentNode, $stack) )
3620
- $stack[] = $node->parentNode;
3621
- $this->elementsBackup = $this->elements;
3622
- $this->elements = $stack;
3623
- if ( $selector )
3624
- $this->filter($selector, true);
3625
- return $this->newInstance();
3626
- }
3627
- /**
3628
- * Enter description here...
3629
- *
3630
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3631
- */
3632
- public function parents($selector = null) {
3633
- $stack = array();
3634
- if (! $this->elements )
3635
- $this->debug('parents() - stack empty');
3636
- foreach($this->elements as $node) {
3637
- $test = $node;
3638
- while( $test->parentNode) {
3639
- $test = $test->parentNode;
3640
- if ($this->isRoot($test))
3641
- break;
3642
- if (! $this->elementsContainsNode($test, $stack)) {
3643
- $stack[] = $test;
3644
- continue;
3645
- }
3646
- }
3647
- }
3648
- $this->elementsBackup = $this->elements;
3649
- $this->elements = $stack;
3650
- if ( $selector )
3651
- $this->filter($selector, true);
3652
- return $this->newInstance();
3653
- }
3654
- /**
3655
- * Internal stack iterator.
3656
- *
3657
- * @access private
3658
- */
3659
- public function stack($nodeTypes = null) {
3660
- if (!isset($nodeTypes))
3661
- return $this->elements;
3662
- if (!is_array($nodeTypes))
3663
- $nodeTypes = array($nodeTypes);
3664
- $return = array();
3665
- foreach($this->elements as $node) {
3666
- if (in_array($node->nodeType, $nodeTypes))
3667
- $return[] = $node;
3668
- }
3669
- return $return;
3670
- }
3671
- // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes
3672
- protected function attrEvents($attr, $oldAttr, $oldValue, $node) {
3673
- // skip events for XML documents
3674
- if (! $this->isXHTML() && ! $this->isHTML())
3675
- return;
3676
- $event = null;
3677
- // identify
3678
- $isInputValue = $node->tagName == 'input'
3679
- && (
3680
- in_array($node->getAttribute('type'),
3681
- array('text', 'password', 'hidden'))
3682
- || !$node->getAttribute('type')
3683
- );
3684
- $isRadio = $node->tagName == 'input'
3685
- && $node->getAttribute('type') == 'radio';
3686
- $isCheckbox = $node->tagName == 'input'
3687
- && $node->getAttribute('type') == 'checkbox';
3688
- $isOption = $node->tagName == 'option';
3689
- if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) {
3690
- $event = new DOMEvent(array(
3691
- 'target' => $node,
3692
- 'type' => 'change'
3693
- ));
3694
- } else if (($isRadio || $isCheckbox) && $attr == 'checked' && (
3695
- // check
3696
- (! $oldAttr && $node->hasAttribute($attr))
3697
- // un-check
3698
- || (! $node->hasAttribute($attr) && $oldAttr)
3699
- )) {
3700
- $event = new DOMEvent(array(
3701
- 'target' => $node,
3702
- 'type' => 'change'
3703
- ));
3704
- } else if ($isOption && $node->parentNode && $attr == 'selected' && (
3705
- // select
3706
- (! $oldAttr && $node->hasAttribute($attr))
3707
- // un-select
3708
- || (! $node->hasAttribute($attr) && $oldAttr)
3709
- )) {
3710
- $event = new DOMEvent(array(
3711
- 'target' => $node->parentNode,
3712
- 'type' => 'change'
3713
- ));
3714
- }
3715
- if ($event) {
3716
- phpQueryEvents::trigger($this->getDocumentID(),
3717
- $event->type, array($event), $node
3718
- );
3719
- }
3720
- }
3721
- public function attr($attr = null, $value = null) {
3722
- foreach($this->stack(1) as $node) {
3723
- if (! is_null($value)) {
3724
- $loop = $attr == '*'
3725
- ? $this->getNodeAttrs($node)
3726
- : array($attr);
3727
- foreach($loop as $a) {
3728
- $oldValue = $node->getAttribute($a);
3729
- $oldAttr = $node->hasAttribute($a);
3730
- // TODO raises an error when charset other than UTF-8
3731
- // while document's charset is also not UTF-8
3732
- @$node->setAttribute($a, $value);
3733
- $this->attrEvents($a, $oldAttr, $oldValue, $node);
3734
- }
3735
- } else if ($attr == '*') {
3736
- // jQuery difference
3737
- $return = array();
3738
- foreach($node->attributes as $n => $v)
3739
- $return[$n] = $v->value;
3740
- return $return;
3741
- } else
3742
- return $node->hasAttribute($attr)
3743
- ? $node->getAttribute($attr)
3744
- : null;
3745
- }
3746
- return is_null($value)
3747
- ? '' : $this;
3748
- }
3749
- /**
3750
- * @access private
3751
- */
3752
- protected function getNodeAttrs($node) {
3753
- $return = array();
3754
- foreach($node->attributes as $n => $o)
3755
- $return[] = $n;
3756
- return $return;
3757
- }
3758
- /**
3759
- * Enter description here...
3760
- *
3761
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3762
- * @todo check CDATA ???
3763
- */
3764
- public function attrPHP($attr, $code) {
3765
- if (! is_null($code)) {
3766
- $value = '<'.'?php '.$code.' ?'.'>';
3767
- // TODO tempolary solution
3768
- // http://code.google.com/p/phpquery/issues/detail?id=17
3769
- // if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII')
3770
- // $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES');
3771
- }
3772
- foreach($this->stack(1) as $node) {
3773
- if (! is_null($code)) {
3774
- // $attrNode = $this->DOM->createAttribute($attr);
3775
- $node->setAttribute($attr, $value);
3776
- // $attrNode->value = $value;
3777
- // $node->appendChild($attrNode);
3778
- } else if ( $attr == '*') {
3779
- // jQuery diff
3780
- $return = array();
3781
- foreach($node->attributes as $n => $v)
3782
- $return[$n] = $v->value;
3783
- return $return;
3784
- } else
3785
- return $node->getAttribute($attr);
3786
- }
3787
- return $this;
3788
- }
3789
- /**
3790
- * Enter description here...
3791
- *
3792
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3793
- */
3794
- public function removeAttr($attr) {
3795
- foreach($this->stack(1) as $node) {
3796
- $loop = $attr == '*'
3797
- ? $this->getNodeAttrs($node)
3798
- : array($attr);
3799
- foreach($loop as $a) {
3800
- $oldValue = $node->getAttribute($a);
3801
- $node->removeAttribute($a);
3802
- $this->attrEvents($a, $oldValue, null, $node);
3803
- }
3804
- }
3805
- return $this;
3806
- }
3807
- /**
3808
- * Return form element value.
3809
- *
3810
- * @return String Fields value.
3811
- */
3812
- public function val($val = null) {
3813
- if (! isset($val)) {
3814
- if ($this->eq(0)->is('select')) {
3815
- $selected = $this->eq(0)->find('option[selected=selected]');
3816
- if ($selected->is('[value]'))
3817
- return $selected->attr('value');
3818
- else
3819
- return $selected->text();
3820
- } else if ($this->eq(0)->is('textarea'))
3821
- return $this->eq(0)->markup();
3822
- else
3823
- return $this->eq(0)->attr('value');
3824
- } else {
3825
- $_val = null;
3826
- foreach($this->stack(1) as $node) {
3827
- $node = pq($node, $this->getDocumentID());
3828
- if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) {
3829
- $isChecked = in_array($node->attr('value'), $val)
3830
- || in_array($node->attr('name'), $val);
3831
- if ($isChecked)
3832
- $node->attr('checked', 'checked');
3833
- else
3834
- $node->removeAttr('checked');
3835
- } else if ($node->get(0)->tagName == 'select') {
3836
- if (! isset($_val)) {
3837
- $_val = array();
3838
- if (! is_array($val))
3839
- $_val = array((string)$val);
3840
- else
3841
- foreach($val as $v)
3842
- $_val[] = $v;
3843
- }
3844
- foreach($node['option']->stack(1) as $option) {
3845
- $option = pq($option, $this->getDocumentID());
3846
- $selected = false;
3847
- // XXX: workaround for string comparsion, see issue #96
3848
- // http://code.google.com/p/phpquery/issues/detail?id=96
3849
- $selected = is_null($option->attr('value'))
3850
- ? in_array($option->markup(), $_val)
3851
- : in_array($option->attr('value'), $_val);
3852
- // $optionValue = $option->attr('value');
3853
- // $optionText = $option->text();
3854
- // $optionTextLenght = mb_strlen($optionText);
3855
- // foreach($_val as $v)
3856
- // if ($optionValue == $v)
3857
- // $selected = true;
3858
- // else if ($optionText == $v && $optionTextLenght == mb_strlen($v))
3859
- // $selected = true;
3860
- if ($selected)
3861
- $option->attr('selected', 'selected');
3862
- else
3863
- $option->removeAttr('selected');
3864
- }
3865
- } else if ($node->get(0)->tagName == 'textarea')
3866
- $node->markup($val);
3867
- else
3868
- $node->attr('value', $val);
3869
- }
3870
- }
3871
- return $this;
3872
- }
3873
- /**
3874
- * Enter description here...
3875
- *
3876
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3877
- */
3878
- public function andSelf() {
3879
- if ( $this->previous )
3880
- $this->elements = array_merge($this->elements, $this->previous->elements);
3881
- return $this;
3882
- }
3883
- /**
3884
- * Enter description here...
3885
- *
3886
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3887
- */
3888
- public function addClass( $className) {
3889
- if (! $className)
3890
- return $this;
3891
- foreach($this->stack(1) as $node) {
3892
- if (! $this->is(".$className", $node))
3893
- $node->setAttribute(
3894
- 'class',
3895
- trim($node->getAttribute('class').' '.$className)
3896
- );
3897
- }
3898
- return $this;
3899
- }
3900
- /**
3901
- * Enter description here...
3902
- *
3903
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3904
- */
3905
- public function addClassPHP( $className) {
3906
- foreach($this->stack(1) as $node) {
3907
- $classes = $node->getAttribute('class');
3908
- $newValue = $classes
3909
- ? $classes.' <'.'?php '.$className.' ?'.'>'
3910
- : '<'.'?php '.$className.' ?'.'>';
3911
- $node->setAttribute('class', $newValue);
3912
- }
3913
- return $this;
3914
- }
3915
- /**
3916
- * Enter description here...
3917
- *
3918
- * @param string $className
3919
- * @return bool
3920
- */
3921
- public function hasClass($className) {
3922
- foreach($this->stack(1) as $node) {
3923
- if ( $this->is(".$className", $node))
3924
- return true;
3925
- }
3926
- return false;
3927
- }
3928
- /**
3929
- * Enter description here...
3930
- *
3931
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3932
- */
3933
- public function removeClass($className) {
3934
- foreach($this->stack(1) as $node) {
3935
- $classes = explode( ' ', $node->getAttribute('class'));
3936
- if ( in_array($className, $classes)) {
3937
- $classes = array_diff($classes, array($className));
3938
- if ( $classes )
3939
- $node->setAttribute('class', implode(' ', $classes));
3940
- else
3941
- $node->removeAttribute('class');
3942
- }
3943
- }
3944
- return $this;
3945
- }
3946
- /**
3947
- * Enter description here...
3948
- *
3949
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3950
- */
3951
- public function toggleClass($className) {
3952
- foreach($this->stack(1) as $node) {
3953
- if ( $this->is( $node, '.'.$className ))
3954
- $this->removeClass($className);
3955
- else
3956
- $this->addClass($className);
3957
- }
3958
- return $this;
3959
- }
3960
- /**
3961
- * Proper name without underscore (just ->empty()) also works.
3962
- *
3963
- * Removes all child nodes from the set of matched elements.
3964
- *
3965
- * Example:
3966
- * pq("p")._empty()
3967
- *
3968
- * HTML:
3969
- * <p>Hello, <span>Person</span> <a href="#">and person</a></p>
3970
- *
3971
- * Result:
3972
- * [ <p></p> ]
3973
- *
3974
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3975
- * @access private
3976
- */
3977
- public function _empty() {
3978
- foreach($this->stack(1) as $node) {
3979
- // thx to 'dave at dgx dot cz'
3980
- $node->nodeValue = '';
3981
- }
3982
- return $this;
3983
- }
3984
- /**
3985
- * Enter description here...
3986
- *
3987
- * @param array|string $callback Expects $node as first param, $index as second
3988
- * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope)
3989
- * @param array $arg1 Will ba passed as third and futher args to callback.
3990
- * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on...
3991
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3992
- */
3993
- public function each($callback, $param1 = null, $param2 = null, $param3 = null) {
3994
- $paramStructure = null;
3995
- if (func_num_args() > 1) {
3996
- $paramStructure = func_get_args();
3997
- $paramStructure = array_slice($paramStructure, 1);
3998
- }
3999
- foreach($this->elements as $v)
4000
- phpQuery::callbackRun($callback, array($v), $paramStructure);
4001
- return $this;
4002
- }
4003
- /**
4004
- * Run callback on actual object.
4005
- *
4006
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4007
- */
4008
- public function callback($callback, $param1 = null, $param2 = null, $param3 = null) {
4009
- $params = func_get_args();
4010
- $params[0] = $this;
4011
- phpQuery::callbackRun($callback, $params);
4012
- return $this;
4013
- }
4014
- /**
4015
- * Enter description here...
4016
- *
4017
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4018
- * @todo add $scope and $args as in each() ???
4019
- */
4020
- public function map($callback, $param1 = null, $param2 = null, $param3 = null) {
4021
- // $stack = array();
4022
- //// foreach($this->newInstance() as $node) {
4023
- // foreach($this->newInstance() as $node) {
4024
- // $result = call_user_func($callback, $node);
4025
- // if ($result)
4026
- // $stack[] = $result;
4027
- // }
4028
- $params = func_get_args();
4029
- array_unshift($params, $this->elements);
4030
- return $this->newInstance(
4031
- call_user_func_array(array('phpQuery', 'map'), $params)
4032
- // phpQuery::map($this->elements, $callback)
4033
- );
4034
- }
4035
- /**
4036
- * Enter description here...
4037
- *
4038
- * @param <type> $key
4039
- * @param <type> $value
4040
- */
4041
- public function data($key, $value = null) {
4042
- if (! isset($value)) {
4043
- // TODO? implement specific jQuery behavior od returning parent values
4044
- // is child which we look up doesn't exist
4045
- return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID());
4046
- } else {
4047
- foreach($this as $node)
4048
- phpQuery::data($node, $key, $value, $this->getDocumentID());
4049
- return $this;
4050
- }
4051
- }
4052
- /**
4053
- * Enter description here...
4054
- *
4055
- * @param <type> $key
4056
- */
4057
- public function removeData($key) {
4058
- foreach($this as $node)
4059
- phpQuery::removeData($node, $key, $this->getDocumentID());
4060
- return $this;
4061
- }
4062
- // INTERFACE IMPLEMENTATIONS
4063
-
4064
- // ITERATOR INTERFACE
4065
- /**
4066
- * @access private
4067
- */
4068
- public function rewind(){
4069
- $this->debug('iterating foreach');
4070
- // phpQuery::selectDocument($this->getDocumentID());
4071
- $this->elementsBackup = $this->elements;
4072
- $this->elementsInterator = $this->elements;
4073
- $this->valid = isset( $this->elements[0] )
4074
- ? 1 : 0;
4075
- // $this->elements = $this->valid
4076
- // ? array($this->elements[0])
4077
- // : array();
4078
- $this->current = 0;
4079
- }
4080
- /**
4081
- * @access private
4082
- */
4083
- public function current(){
4084
- return $this->elementsInterator[ $this->current ];
4085
- }
4086
- /**
4087
- * @access private
4088
- */
4089
- public function key(){
4090
- return $this->current;
4091
- }
4092
- /**
4093
- * Double-function method.
4094
- *
4095
- * First: main iterator interface method.
4096
- * Second: Returning next sibling, alias for _next().
4097
- *
4098
- * Proper functionality is choosed automagicaly.
4099
- *
4100
- * @see phpQueryObject::_next()
4101
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4102
- */
4103
- public function next($cssSelector = null){
4104
- // if ($cssSelector || $this->valid)
4105
- // return $this->_next($cssSelector);
4106
- $this->valid = isset( $this->elementsInterator[ $this->current+1 ] )
4107
- ? true
4108
- : false;
4109
- if (! $this->valid && $this->elementsInterator) {
4110
- $this->elementsInterator = null;
4111
- } else if ($this->valid) {
4112
- $this->current++;
4113
- } else {
4114
- return $this->_next($cssSelector);
4115
- }
4116
- }
4117
- /**
4118
- * @access private
4119
- */
4120
- public function valid(){
4121
- return $this->valid;
4122
- }
4123
- // ITERATOR INTERFACE END
4124
- // ARRAYACCESS INTERFACE
4125
- /**
4126
- * @access private
4127
- */
4128
- public function offsetExists($offset) {
4129
- return $this->find($offset)->size() > 0;
4130
- }
4131
- /**
4132
- * @access private
4133
- */
4134
- public function offsetGet($offset) {
4135
- return $this->find($offset);
4136
- }
4137
- /**
4138
- * @access private
4139
- */
4140
- public function offsetSet($offset, $value) {
4141
- // $this->find($offset)->replaceWith($value);
4142
- $this->find($offset)->html($value);
4143
- }
4144
- /**
4145
- * @access private
4146
- */
4147
- public function offsetUnset($offset) {
4148
- // empty
4149
- throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML.");
4150
- }
4151
- // ARRAYACCESS INTERFACE END
4152
- /**
4153
- * Returns node's XPath.
4154
- *
4155
- * @param unknown_type $oneNode
4156
- * @return string
4157
- * @TODO use native getNodePath is avaible
4158
- * @access private
4159
- */
4160
- protected function getNodeXpath($oneNode = null, $namespace = null) {
4161
- $return = array();
4162
- $loop = $oneNode
4163
- ? array($oneNode)
4164
- : $this->elements;
4165
- // if ($namespace)
4166
- // $namespace .= ':';
4167
- foreach($loop as $node) {
4168
- if ($node instanceof DOMDOCUMENT) {
4169
- $return[] = '';
4170
- continue;
4171
- }
4172
- $xpath = array();
4173
- while(! ($node instanceof DOMDOCUMENT)) {
4174
- $i = 1;
4175
- $sibling = $node;
4176
- while($sibling->previousSibling) {
4177
- $sibling = $sibling->previousSibling;
4178
- $isElement = $sibling instanceof DOMELEMENT;
4179
- if ($isElement && $sibling->tagName == $node->tagName)
4180
- $i++;
4181
- }
4182
- $xpath[] = $this->isXML()
4183
- ? "*[local-name()='{$node->tagName}'][{$i}]"
4184
- : "{$node->tagName}[{$i}]";
4185
- $node = $node->parentNode;
4186
- }
4187
- $xpath = join('/', array_reverse($xpath));
4188
- $return[] = '/'.$xpath;
4189
- }
4190
- return $oneNode
4191
- ? $return[0]
4192
- : $return;
4193
- }
4194
- // HELPERS
4195
- public function whois($oneNode = null) {
4196
- $return = array();
4197
- $loop = $oneNode
4198
- ? array( $oneNode )
4199
- : $this->elements;
4200
- foreach($loop as $node) {
4201
- if (isset($node->tagName)) {
4202
- $tag = in_array($node->tagName, array('php', 'js'))
4203
- ? strtoupper($node->tagName)
4204
- : $node->tagName;
4205
- $return[] = $tag
4206
- .($node->getAttribute('id')
4207
- ? '#'.$node->getAttribute('id'):'')
4208
- .($node->getAttribute('class')
4209
- ? '.'.join('.', split(' ', $node->getAttribute('class'))):'')
4210
- .($node->getAttribute('name')
4211
- ? '[name="'.$node->getAttribute('name').'"]':'')
4212
- .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false
4213
- ? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'')
4214
- .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false
4215
- ? '[value=PHP]':'')
4216
- .($node->getAttribute('selected')
4217
- ? '[selected]':'')
4218
- .($node->getAttribute('checked')
4219
- ? '[checked]':'')
4220
- ;
4221
- } else if ($node instanceof DOMTEXT) {
4222
- if (trim($node->textContent))
4223
- $return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15);
4224
- } else {
4225
-
4226
- }
4227
- }
4228
- return $oneNode && isset($return[0])
4229
- ? $return[0]
4230
- : $return;
4231
- }
4232
- /**
4233
- * Dump htmlOuter and preserve chain. Usefull for debugging.
4234
- *
4235
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4236
- *
4237
- */
4238
- public function dump() {
4239
- print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4240
- $debug = phpQuery::$debug;
4241
- phpQuery::$debug = false;
4242
- // print __FILE__.':'.__LINE__."\n";
4243
- var_dump($this->htmlOuter());
4244
- return $this;
4245
- }
4246
- public function dumpWhois() {
4247
- print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4248
- $debug = phpQuery::$debug;
4249
- phpQuery::$debug = false;
4250
- // print __FILE__.':'.__LINE__."\n";
4251
- var_dump('whois', $this->whois());
4252
- phpQuery::$debug = $debug;
4253
- return $this;
4254
- }
4255
- public function dumpLength() {
4256
- print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4257
- $debug = phpQuery::$debug;
4258
- phpQuery::$debug = false;
4259
- // print __FILE__.':'.__LINE__."\n";
4260
- var_dump('length', $this->length());
4261
- phpQuery::$debug = $debug;
4262
- return $this;
4263
- }
4264
- public function dumpTree($html = true, $title = true) {
4265
- $output = $title
4266
- ? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : '';
4267
- $debug = phpQuery::$debug;
4268
- phpQuery::$debug = false;
4269
- foreach($this->stack() as $node)
4270
- $output .= $this->__dumpTree($node);
4271
- phpQuery::$debug = $debug;
4272
- print $html
4273
- ? nl2br(str_replace(' ', '&nbsp;', $output))
4274
- : $output;
4275
- return $this;
4276
- }
4277
- private function __dumpTree($node, $intend = 0) {
4278
- $whois = $this->whois($node);
4279
- $return = '';
4280
- if ($whois)
4281
- $return .= str_repeat(' - ', $intend).$whois."\n";
4282
- if (isset($node->childNodes))
4283
- foreach($node->childNodes as $chNode)
4284
- $return .= $this->__dumpTree($chNode, $intend+1);
4285
- return $return;
4286
- }
4287
- /**
4288
- * Dump htmlOuter and stop script execution. Usefull for debugging.
4289
- *
4290
- */
4291
- public function dumpDie() {
4292
- print __FILE__.':'.__LINE__;
4293
- var_dump($this->htmlOuter());
4294
- die();
4295
- }
4296
- }
4297
-
4298
-
4299
- // -- Multibyte Compatibility functions ---------------------------------------
4300
- // http://svn.iphonewebdev.com/lace/lib/mb_compat.php
4301
-
4302
- /**
4303
- * mb_internal_encoding()
4304
- *
4305
- * Included for mbstring pseudo-compatability.
4306
- */
4307
- if (!function_exists('mb_internal_encoding'))
4308
- {
4309
- function mb_internal_encoding($enc) {return true; }
4310
- }
4311
-
4312
- /**
4313
- * mb_regex_encoding()
4314
- *
4315
- * Included for mbstring pseudo-compatability.
4316
- */
4317
- if (!function_exists('mb_regex_encoding'))
4318
- {
4319
- function mb_regex_encoding($enc) {return true; }
4320
- }
4321
-
4322
- /**
4323
- * mb_strlen()
4324
- *
4325
- * Included for mbstring pseudo-compatability.
4326
- */
4327
- if (!function_exists('mb_strlen'))
4328
- {
4329
- function mb_strlen($str)
4330
- {
4331
- return strlen($str);
4332
- }
4333
- }
4334
-
4335
- /**
4336
- * mb_strpos()
4337
- *
4338
- * Included for mbstring pseudo-compatability.
4339
- */
4340
- if (!function_exists('mb_strpos'))
4341
- {
4342
- function mb_strpos($haystack, $needle, $offset=0)
4343
- {
4344
- return strpos($haystack, $needle, $offset);
4345
- }
4346
- }
4347
- /**
4348
- * mb_stripos()
4349
- *
4350
- * Included for mbstring pseudo-compatability.
4351
- */
4352
- if (!function_exists('mb_stripos'))
4353
- {
4354
- function mb_stripos($haystack, $needle, $offset=0)
4355
- {
4356
- return stripos($haystack, $needle, $offset);
4357
- }
4358
- }
4359
-
4360
- /**
4361
- * mb_substr()
4362
- *
4363
- * Included for mbstring pseudo-compatability.
4364
- */
4365
- if (!function_exists('mb_substr'))
4366
- {
4367
- function mb_substr($str, $start, $length=0)
4368
- {
4369
- return substr($str, $start, $length);
4370
- }
4371
- }
4372
-
4373
- /**
4374
- * mb_substr_count()
4375
- *
4376
- * Included for mbstring pseudo-compatability.
4377
- */
4378
- if (!function_exists('mb_substr_count'))
4379
- {
4380
- function mb_substr_count($haystack, $needle)
4381
- {
4382
- return substr_count($haystack, $needle);
4383
- }
4384
- }
4385
-
4386
-
4387
- /**
4388
- * Static namespace for phpQuery functions.
4389
- *
4390
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
4391
- * @package phpQuery
4392
- */
4393
- abstract class phpQuery {
4394
- /**
4395
- * XXX: Workaround for mbstring problems
4396
- *
4397
- * @var bool
4398
- */
4399
- public static $mbstringSupport = true;
4400
- public static $debug = false;
4401
- public static $documents = array();
4402
- public static $defaultDocumentID = null;
4403
- // public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"';
4404
- /**
4405
- * Applies only to HTML.
4406
- *
4407
- * @var unknown_type
4408
- */
4409
- public static $defaultDoctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
4410
- "http://www.w3.org/TR/html4/loose.dtd">';
4411
- public static $defaultCharset = 'UTF-8';
4412
- /**
4413
- * Static namespace for plugins.
4414
- *
4415
- * @var object
4416
- */
4417
- public static $plugins = array();
4418
- /**
4419
- * List of loaded plugins.
4420
- *
4421
- * @var unknown_type
4422
- */
4423
- public static $pluginsLoaded = array();
4424
- public static $pluginsMethods = array();
4425
- public static $pluginsStaticMethods = array();
4426
- public static $extendMethods = array();
4427
- /**
4428
- * @TODO implement
4429
- */
4430
- public static $extendStaticMethods = array();
4431
- /**
4432
- * Hosts allowed for AJAX connections.
4433
- * Dot '.' means $_SERVER['HTTP_HOST'] (if any).
4434
- *
4435
- * @var array
4436
- */
4437
- public static $ajaxAllowedHosts = array(
4438
- '.'
4439
- );
4440
- /**
4441
- * AJAX settings.
4442
- *
4443
- * @var array
4444
- * XXX should it be static or not ?
4445
- */
4446
- public static $ajaxSettings = array(
4447
- 'url' => '',//TODO
4448
- 'global' => true,
4449
- 'type' => "GET",
4450
- 'timeout' => null,
4451
- 'contentType' => "application/x-www-form-urlencoded",
4452
- 'processData' => true,
4453
- // 'async' => true,
4454
- 'data' => null,
4455
- 'username' => null,
4456
- 'password' => null,
4457
- 'accepts' => array(
4458
- 'xml' => "application/xml, text/xml",
4459
- 'html' => "text/html",
4460
- 'script' => "text/javascript, application/javascript",
4461
- 'json' => "application/json, text/javascript",
4462
- 'text' => "text/plain",
4463
- '_default' => "*/*"
4464
- )
4465
- );
4466
- public static $lastModified = null;
4467
- public static $active = 0;
4468
- public static $dumpCount = 0;
4469
- /**
4470
- * Multi-purpose function.
4471
- * Use pq() as shortcut.
4472
- *
4473
- * In below examples, $pq is any result of pq(); function.
4474
- *
4475
- * 1. Import markup into existing document (without any attaching):
4476
- * - Import into selected document:
4477
- * pq('<div/>') // DOESNT accept text nodes at beginning of input string !
4478
- * - Import into document with ID from $pq->getDocumentID():
4479
- * pq('<div/>', $pq->getDocumentID())
4480
- * - Import into same document as DOMNode belongs to:
4481
- * pq('<div/>', DOMNode)
4482
- * - Import into document from phpQuery object:
4483
- * pq('<div/>', $pq)
4484
- *
4485
- * 2. Run query:
4486
- * - Run query on last selected document:
4487
- * pq('div.myClass')
4488
- * - Run query on document with ID from $pq->getDocumentID():
4489
- * pq('div.myClass', $pq->getDocumentID())
4490
- * - Run query on same document as DOMNode belongs to and use node(s)as root for query:
4491
- * pq('div.myClass', DOMNode)
4492
- * - Run query on document from phpQuery object
4493
- * and use object's stack as root node(s) for query:
4494
- * pq('div.myClass', $pq)
4495
- *
4496
- * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes
4497
- * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root)
4498
- *
4499
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false
4500
- * phpQuery object or false in case of error.
4501
- */
4502
- public static function pq($arg1, $context = null) {
4503
- if ($arg1 instanceof DOMNODE && ! isset($context)) {
4504
- foreach(phpQuery::$documents as $documentWrapper) {
4505
- $compare = $arg1 instanceof DOMDocument
4506
- ? $arg1 : $arg1->ownerDocument;
4507
- if ($documentWrapper->document->isSameNode($compare))
4508
- $context = $documentWrapper->id;
4509
- }
4510
- }
4511
- if (! $context) {
4512
- $domId = self::$defaultDocumentID;
4513
- if (! $domId)
4514
- throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first.");
4515
- // } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
4516
- } else if (is_object($context) && $context instanceof phpQueryObject)
4517
- $domId = $context->getDocumentID();
4518
- else if ($context instanceof DOMDOCUMENT) {
4519
- $domId = self::getDocumentID($context);
4520
- if (! $domId) {
4521
- //throw new Exception('Orphaned DOMDocument');
4522
- $domId = self::newDocument($context)->getDocumentID();
4523
- }
4524
- } else if ($context instanceof DOMNODE) {
4525
- $domId = self::getDocumentID($context);
4526
- if (! $domId) {
4527
- throw new Exception('Orphaned DOMNode');
4528
- // $domId = self::newDocument($context->ownerDocument);
4529
- }
4530
- } else
4531
- $domId = $context;
4532
- if ($arg1 instanceof phpQueryObject) {
4533
- // if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) {
4534
- /**
4535
- * Return $arg1 or import $arg1 stack if document differs:
4536
- * pq(pq('<div/>'))
4537
- */
4538
- if ($arg1->getDocumentID() == $domId)
4539
- return $arg1;
4540
- $class = get_class($arg1);
4541
- // support inheritance by passing old object to overloaded constructor
4542
- $phpQuery = $class != 'phpQuery'
4543
- ? new $class($arg1, $domId)
4544
- : new phpQueryObject($domId);
4545
- $phpQuery->elements = array();
4546
- foreach($arg1->elements as $node)
4547
- $phpQuery->elements[] = $phpQuery->document->importNode($node, true);
4548
- return $phpQuery;
4549
- } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) {
4550
- /*
4551
- * Wrap DOM nodes with phpQuery object, import into document when needed:
4552
- * pq(array($domNode1, $domNode2))
4553
- */
4554
- $phpQuery = new phpQueryObject($domId);
4555
- if (!($arg1 instanceof DOMNODELIST) && ! is_array($arg1))
4556
- $arg1 = array($arg1);
4557
- $phpQuery->elements = array();
4558
- foreach($arg1 as $node) {
4559
- $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT
4560
- && ! $node->ownerDocument->isSameNode($phpQuery->document);
4561
- $phpQuery->elements[] = $sameDocument
4562
- ? $phpQuery->document->importNode($node, true)
4563
- : $node;
4564
- }
4565
- return $phpQuery;
4566
- } else if (self::isMarkup($arg1)) {
4567
- /**
4568
- * Import HTML:
4569
- * pq('<div/>')
4570
- */
4571
- $phpQuery = new phpQueryObject($domId);
4572
- return $phpQuery->newInstance(
4573
- $phpQuery->documentWrapper->import($arg1)
4574
- );
4575
- } else {
4576
- /**
4577
- * Run CSS query:
4578
- * pq('div.myClass')
4579
- */
4580
- $phpQuery = new phpQueryObject($domId);
4581
- // if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
4582
- if ($context && $context instanceof phpQueryObject)
4583
- $phpQuery->elements = $context->elements;
4584
- else if ($context && $context instanceof DOMNODELIST) {
4585
- $phpQuery->elements = array();
4586
- foreach($context as $node)
4587
- $phpQuery->elements[] = $node;
4588
- } else if ($context && $context instanceof DOMNODE)
4589
- $phpQuery->elements = array($context);
4590
- return $phpQuery->find($arg1);
4591
- }
4592
- }
4593
- /**
4594
- * Sets default document to $id. Document has to be loaded prior
4595
- * to using this method.
4596
- * $id can be retrived via getDocumentID() or getDocumentIDRef().
4597
- *
4598
- * @param unknown_type $id
4599
- */
4600
- public static function selectDocument($id) {
4601
- $id = self::getDocumentID($id);
4602
- self::debug("Selecting document '$id' as default one");
4603
- self::$defaultDocumentID = self::getDocumentID($id);
4604
- }
4605
- /**
4606
- * Returns document with id $id or last used as phpQueryObject.
4607
- * $id can be retrived via getDocumentID() or getDocumentIDRef().
4608
- * Chainable.
4609
- *
4610
- * @see phpQuery::selectDocument()
4611
- * @param unknown_type $id
4612
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4613
- */
4614
- public static function getDocument($id = null) {
4615
- if ($id)
4616
- phpQuery::selectDocument($id);
4617
- else
4618
- $id = phpQuery::$defaultDocumentID;
4619
- return new phpQueryObject($id);
4620
- }
4621
- /**
4622
- * Creates new document from markup.
4623
- * Chainable.
4624
- *
4625
- * @param unknown_type $markup
4626
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4627
- */
4628
- public static function newDocument($markup = null, $contentType = null) {
4629
- if (! $markup)
4630
- $markup = '';
4631
- $documentID = phpQuery::createDocumentWrapper($markup, $contentType);
4632
- return new phpQueryObject($documentID);
4633
- }
4634
- /**
4635
- * Creates new document from markup.
4636
- * Chainable.
4637
- *
4638
- * @param unknown_type $markup
4639
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4640
- */
4641
- public static function newDocumentHTML($markup = null, $charset = null) {
4642
- $contentType = $charset
4643
- ? ";charset=$charset"
4644
- : '';
4645
- return self::newDocument($markup, "text/html{$contentType}");
4646
- }
4647
- /**
4648
- * Creates new document from markup.
4649
- * Chainable.
4650
- *
4651
- * @param unknown_type $markup
4652
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4653
- */
4654
- public static function newDocumentXML($markup = null, $charset = null) {
4655
- $contentType = $charset
4656
- ? ";charset=$charset"
4657
- : '';
4658
- return self::newDocument($markup, "text/xml{$contentType}");
4659
- }
4660
- /**
4661
- * Creates new document from markup.
4662
- * Chainable.
4663
- *
4664
- * @param unknown_type $markup
4665
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4666
- */
4667
- public static function newDocumentXHTML($markup = null, $charset = null) {
4668
- $contentType = $charset
4669
- ? ";charset=$charset"
4670
- : '';
4671
- return self::newDocument($markup, "application/xhtml+xml{$contentType}");
4672
- }
4673
- /**
4674
- * Creates new document from markup.
4675
- * Chainable.
4676
- *
4677
- * @param unknown_type $markup
4678
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4679
- */
4680
- public static function newDocumentPHP($markup = null, $contentType = "text/html") {
4681
- // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function)
4682
- $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset);
4683
- return self::newDocument($markup, $contentType);
4684
- }
4685
- public static function phpToMarkup($php, $charset = 'utf-8') {
4686
- $regexes = array(
4687
- '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?>)([^\']*)\'@s',
4688
- '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?>)([^"]*)"@s',
4689
- );
4690
- foreach($regexes as $regex)
4691
- while (preg_match($regex, $php, $matches)) {
4692
- $php = preg_replace_callback(
4693
- $regex,
4694
- // create_function('$m, $charset = "'.$charset.'"',
4695
- // 'return $m[1].$m[2]
4696
- // .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
4697
- // .$m[5].$m[2];'
4698
- // ),
4699
- array('phpQuery', '_phpToMarkupCallback'),
4700
- $php
4701
- );
4702
- }
4703
- $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s';
4704
- //preg_match_all($regex, $php, $matches);
4705
- //var_dump($matches);
4706
- $php = preg_replace($regex, '\\1<php><!-- \\3 --></php>', $php);
4707
- return $php;
4708
- }
4709
- public static function _phpToMarkupCallback($php, $charset = 'utf-8') {
4710
- return $m[1].$m[2]
4711
- .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
4712
- .$m[5].$m[2];
4713
- }
4714
- public static function _markupToPHPCallback($m) {
4715
- return "<"."?php ".htmlspecialchars_decode($m[1])." ?".">";
4716
- }
4717
- /**
4718
- * Converts document markup containing PHP code generated by phpQuery::php()
4719
- * into valid (executable) PHP code syntax.
4720
- *
4721
- * @param string|phpQueryObject $content
4722
- * @return string PHP code.
4723
- */
4724
- public static function markupToPHP($content) {
4725
- if ($content instanceof phpQueryObject)
4726
- $content = $content->markupOuter();
4727
- /* <php>...</php> to <?php...? > */
4728
- $content = preg_replace_callback(
4729
- '@<php>\s*<!--(.*?)-->\s*</php>@s',
4730
- // create_function('$m',
4731
- // 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";'
4732
- // ),
4733
- array('phpQuery', '_markupToPHPCallback'),
4734
- $content
4735
- );
4736
- /* <node attr='< ?php ? >'> extra space added to save highlighters */
4737
- $regexes = array(
4738
- '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^\']*)\'@s',
4739
- '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^"]*)"@s',
4740
- );
4741
- foreach($regexes as $regex)
4742
- while (preg_match($regex, $content))
4743
- $content = preg_replace_callback(
4744
- $regex,
4745
- create_function('$m',
4746
- 'return $m[1].$m[2].$m[3]."<?php "
4747
- .str_replace(
4748
- array("%20", "%3E", "%09", "&#10;", "&#9;", "%7B", "%24", "%7D", "%22", "%5B", "%5D"),
4749
- array(" ", ">", " ", "\n", " ", "{", "$", "}", \'"\', "[", "]"),
4750
- htmlspecialchars_decode($m[4])
4751
- )
4752
- ." ?>".$m[5].$m[2];'
4753
- ),
4754
- $content
4755
- );
4756
- return $content;
4757
- }
4758
- /**
4759
- * Creates new document from file $file.
4760
- * Chainable.
4761
- *
4762
- * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources.
4763
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4764
- */
4765
- public static function newDocumentFile($file, $contentType = null) {
4766
- $documentID = self::createDocumentWrapper(
4767
- file_get_contents($file), $contentType
4768
- );
4769
- return new phpQueryObject($documentID);
4770
- }
4771
- /**
4772
- * Creates new document from markup.
4773
- * Chainable.
4774
- *
4775
- * @param unknown_type $markup
4776
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4777
- */
4778
- public static function newDocumentFileHTML($file, $charset = null) {
4779
- $contentType = $charset
4780
- ? ";charset=$charset"
4781
- : '';
4782
- return self::newDocumentFile($file, "text/html{$contentType}");
4783
- }
4784
- /**
4785
- * Creates new document from markup.
4786
- * Chainable.
4787
- *
4788
- * @param unknown_type $markup
4789
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4790
- */
4791
- public static function newDocumentFileXML($file, $charset = null) {
4792
- $contentType = $charset
4793
- ? ";charset=$charset"
4794
- : '';
4795
- return self::newDocumentFile($file, "text/xml{$contentType}");
4796
- }
4797
- /**
4798
- * Creates new document from markup.
4799
- * Chainable.
4800
- *
4801
- * @param unknown_type $markup
4802
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4803
- */
4804
- public static function newDocumentFileXHTML($file, $charset = null) {
4805
- $contentType = $charset
4806
- ? ";charset=$charset"
4807
- : '';
4808
- return self::newDocumentFile($file, "application/xhtml+xml{$contentType}");
4809
- }
4810
- /**
4811
- * Creates new document from markup.
4812
- * Chainable.
4813
- *
4814
- * @param unknown_type $markup
4815
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4816
- */
4817
- public static function newDocumentFilePHP($file, $contentType = null) {
4818
- return self::newDocumentPHP(file_get_contents($file), $contentType);
4819
- }
4820
- /**
4821
- * Reuses existing DOMDocument object.
4822
- * Chainable.
4823
- *
4824
- * @param $document DOMDocument
4825
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4826
- * @TODO support DOMDocument
4827
- */
4828
- public static function loadDocument($document) {
4829
- // TODO
4830
- die('TODO loadDocument');
4831
- }
4832
- /**
4833
- * Enter description here...
4834
- *
4835
- * @param unknown_type $html
4836
- * @param unknown_type $domId
4837
- * @return unknown New DOM ID
4838
- * @todo support PHP tags in input
4839
- * @todo support passing DOMDocument object from self::loadDocument
4840
- */
4841
- protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) {
4842
- if (function_exists('domxml_open_mem'))
4843
- throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled.");
4844
- // $id = $documentID
4845
- // ? $documentID
4846
- // : md5(microtime());
4847
- $document = null;
4848
- if ($html instanceof DOMDOCUMENT) {
4849
- if (self::getDocumentID($html)) {
4850
- // document already exists in phpQuery::$documents, make a copy
4851
- $document = clone $html;
4852
- } else {
4853
- // new document, add it to phpQuery::$documents
4854
- $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
4855
- }
4856
- } else {
4857
- $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
4858
- }
4859
- // $wrapper->id = $id;
4860
- // bind document
4861
- phpQuery::$documents[$wrapper->id] = $wrapper;
4862
- // remember last loaded document
4863
- phpQuery::selectDocument($wrapper->id);
4864
- return $wrapper->id;
4865
- }
4866
- /**
4867
- * Extend class namespace.
4868
- *
4869
- * @param string|array $target
4870
- * @param array $source
4871
- * @TODO support string $source
4872
- * @return unknown_type
4873
- */
4874
- public static function extend($target, $source) {
4875
- switch($target) {
4876
- case 'phpQueryObject':
4877
- $targetRef = &self::$extendMethods;
4878
- $targetRef2 = &self::$pluginsMethods;
4879
- break;
4880
- case 'phpQuery':
4881
- $targetRef = &self::$extendStaticMethods;
4882
- $targetRef2 = &self::$pluginsStaticMethods;
4883
- break;
4884
- default:
4885
- throw new Exception("Unsupported \$target type");
4886
- }
4887
- if (is_string($source))
4888
- $source = array($source => $source);
4889
- foreach($source as $method => $callback) {
4890
- if (isset($targetRef[$method])) {
4891
- // throw new Exception
4892
- self::debug("Duplicate method '{$method}', can\'t extend '{$target}'");
4893
- continue;
4894
- }
4895
- if (isset($targetRef2[$method])) {
4896
- // throw new Exception
4897
- self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}',"
4898
- ." can\'t extend '{$target}'");
4899
- continue;
4900
- }
4901
- $targetRef[$method] = $callback;
4902
- }
4903
- return true;
4904
- }
4905
- /**
4906
- * Extend phpQuery with $class from $file.
4907
- *
4908
- * @param string $class Extending class name. Real class name can be prepended phpQuery_.
4909
- * @param string $file Filename to include. Defaults to "{$class}.php".
4910
- */
4911
- public static function plugin($class, $file = null) {
4912
- // TODO $class checked agains phpQuery_$class
4913
- // if (strpos($class, 'phpQuery') === 0)
4914
- // $class = substr($class, 8);
4915
- if (in_array($class, self::$pluginsLoaded))
4916
- return true;
4917
- if (! $file)
4918
- $file = $class.'.php';
4919
- $objectClassExists = class_exists('phpQueryObjectPlugin_'.$class);
4920
- $staticClassExists = class_exists('phpQueryPlugin_'.$class);
4921
- if (! $objectClassExists && ! $staticClassExists)
4922
- require_once($file);
4923
- self::$pluginsLoaded[] = $class;
4924
- // static methods
4925
- if (class_exists('phpQueryPlugin_'.$class)) {
4926
- $realClass = 'phpQueryPlugin_'.$class;
4927
- $vars = get_class_vars($realClass);
4928
- $loop = isset($vars['phpQueryMethods'])
4929
- && ! is_null($vars['phpQueryMethods'])
4930
- ? $vars['phpQueryMethods']
4931
- : get_class_methods($realClass);
4932
- foreach($loop as $method) {
4933
- if ($method == '__initialize')
4934
- continue;
4935
- if (! is_callable(array($realClass, $method)))
4936
- continue;
4937
- if (isset(self::$pluginsStaticMethods[$method])) {
4938
- throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsStaticMethods[$method]."'");
4939
- return;
4940
- }
4941
- self::$pluginsStaticMethods[$method] = $class;
4942
- }
4943
- if (method_exists($realClass, '__initialize'))
4944
- call_user_func_array(array($realClass, '__initialize'), array());
4945
- }
4946
- // object methods
4947
- if (class_exists('phpQueryObjectPlugin_'.$class)) {
4948
- $realClass = 'phpQueryObjectPlugin_'.$class;
4949
- $vars = get_class_vars($realClass);
4950
- $loop = isset($vars['phpQueryMethods'])
4951
- && ! is_null($vars['phpQueryMethods'])
4952
- ? $vars['phpQueryMethods']
4953
- : get_class_methods($realClass);
4954
- foreach($loop as $method) {
4955
- if (! is_callable(array($realClass, $method)))
4956
- continue;
4957
- if (isset(self::$pluginsMethods[$method])) {
4958
- throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsMethods[$method]."'");
4959
- continue;
4960
- }
4961
- self::$pluginsMethods[$method] = $class;
4962
- }
4963
- }
4964
- return true;
4965
- }
4966
- /**
4967
- * Unloades all or specified document from memory.
4968
- *
4969
- * @param mixed $documentID @see phpQuery::getDocumentID() for supported types.
4970
- */
4971
- public static function unloadDocuments($id = null) {
4972
- if (isset($id)) {
4973
- if ($id = self::getDocumentID($id))
4974
- unset(phpQuery::$documents[$id]);
4975
- } else {
4976
- foreach(phpQuery::$documents as $k => $v) {
4977
- unset(phpQuery::$documents[$k]);
4978
- }
4979
- }
4980
- }
4981
- /**
4982
- * Parses phpQuery object or HTML result against PHP tags and makes them active.
4983
- *
4984
- * @param phpQuery|string $content
4985
- * @deprecated
4986
- * @return string
4987
- */
4988
- public static function unsafePHPTags($content) {
4989
- return self::markupToPHP($content);
4990
- }
4991
- public static function DOMNodeListToArray($DOMNodeList) {
4992
- $array = array();
4993
- if (! $DOMNodeList)
4994
- return $array;
4995
- foreach($DOMNodeList as $node)
4996
- $array[] = $node;
4997
- return $array;
4998
- }
4999
- /**
5000
- * Checks if $input is HTML string, which has to start with '<'.
5001
- *
5002
- * @deprecated
5003
- * @param String $input
5004
- * @return Bool
5005
- * @todo still used ?
5006
- */
5007
- public static function isMarkup($input) {
5008
- return ! is_array($input) && substr(trim($input), 0, 1) == '<';
5009
- }
5010
- public static function debug($text) {
5011
- if (self::$debug)
5012
- print var_dump($text);
5013
- }
5014
- /**
5015
- * Make an AJAX request.
5016
- *
5017
- * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions
5018
- * Additional options are:
5019
- * 'document' - document for global events, @see phpQuery::getDocumentID()
5020
- * 'referer' - implemented
5021
- * 'requested_with' - TODO; not implemented (X-Requested-With)
5022
- * @return Zend_Http_Client
5023
- * @link http://docs.jquery.com/Ajax/jQuery.ajax
5024
- *
5025
- * @TODO $options['cache']
5026
- * @TODO $options['processData']
5027
- * @TODO $options['xhr']
5028
- * @TODO $options['data'] as string
5029
- * @TODO XHR interface
5030
- */
5031
- public static function ajax($options = array(), $xhr = null) {
5032
- $options = array_merge(
5033
- self::$ajaxSettings, $options
5034
- );
5035
- $documentID = isset($options['document'])
5036
- ? self::getDocumentID($options['document'])
5037
- : null;
5038
- if ($xhr) {
5039
- // reuse existing XHR object, but clean it up
5040
- $client = $xhr;
5041
- // $client->setParameterPost(null);
5042
- // $client->setParameterGet(null);
5043
- $client->setAuth(false);
5044
- $client->setHeaders("If-Modified-Since", null);
5045
- $client->setHeaders("Referer", null);
5046
- $client->resetParameters();
5047
- } else {
5048
- // create new XHR object
5049
- require_once('Zend/Http/Client.php');
5050
- $client = new Zend_Http_Client();
5051
- $client->setCookieJar();
5052
- }
5053
- if (isset($options['timeout']))
5054
- $client->setConfig(array(
5055
- 'timeout' => $options['timeout'],
5056
- ));
5057
- // 'maxredirects' => 0,
5058
- foreach(self::$ajaxAllowedHosts as $k => $host)
5059
- if ($host == '.' && isset($_SERVER['HTTP_HOST']))
5060
- self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST'];
5061
- $host = parse_url($options['url'], PHP_URL_HOST);
5062
- if (! in_array($host, self::$ajaxAllowedHosts)) {
5063
- throw new Exception("Request not permitted, host '$host' not present in "
5064
- ."phpQuery::\$ajaxAllowedHosts");
5065
- }
5066
- // JSONP
5067
- $jsre = "/=\\?(&|$)/";
5068
- if (isset($options['dataType']) && $options['dataType'] == 'jsonp') {
5069
- $jsonpCallbackParam = $options['jsonp']
5070
- ? $options['jsonp'] : 'callback';
5071
- if (strtolower($options['type']) == 'get') {
5072
- if (! preg_match($jsre, $options['url'])) {
5073
- $sep = strpos($options['url'], '?')
5074
- ? '&' : '?';
5075
- $options['url'] .= "$sep$jsonpCallbackParam=?";
5076
- }
5077
- } else if ($options['data']) {
5078
- $jsonp = false;
5079
- foreach($options['data'] as $n => $v) {
5080
- if ($v == '?')
5081
- $jsonp = true;
5082
- }
5083
- if (! $jsonp) {
5084
- $options['data'][$jsonpCallbackParam] = '?';
5085
- }
5086
- }
5087
- $options['dataType'] = 'json';
5088
- }
5089
- if (isset($options['dataType']) && $options['dataType'] == 'json') {
5090
- $jsonpCallback = 'json_'.md5(microtime());
5091
- $jsonpData = $jsonpUrl = false;
5092
- if ($options['data']) {
5093
- foreach($options['data'] as $n => $v) {
5094
- if ($v == '?')
5095
- $jsonpData = $n;
5096
- }
5097
- }
5098
- if (preg_match($jsre, $options['url']))
5099
- $jsonpUrl = true;
5100
- if ($jsonpData !== false || $jsonpUrl) {
5101
- // remember callback name for httpData()
5102
- $options['_jsonp'] = $jsonpCallback;
5103
- if ($jsonpData !== false)
5104
- $options['data'][$jsonpData] = $jsonpCallback;
5105
- if ($jsonpUrl)
5106
- $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']);
5107
- }
5108
- }
5109
- $client->setUri($options['url']);
5110
- $client->setMethod(strtoupper($options['type']));
5111
- if (isset($options['referer']) && $options['referer'])
5112
- $client->setHeaders('Referer', $options['referer']);
5113
- $client->setHeaders(array(
5114
- // 'content-type' => $options['contentType'],
5115
- 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko'
5116
- .'/2008122010 Firefox/3.0.5',
5117
- // TODO custom charset
5118
- 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
5119
- // 'Connection' => 'keep-alive',
5120
- // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
5121
- 'Accept-Language' => 'en-us,en;q=0.5',
5122
- ));
5123
- if ($options['username'])
5124
- $client->setAuth($options['username'], $options['password']);
5125
- if (isset($options['ifModified']) && $options['ifModified'])
5126
- $client->setHeaders("If-Modified-Since",
5127
- self::$lastModified
5128
- ? self::$lastModified
5129
- : "Thu, 01 Jan 1970 00:00:00 GMT"
5130
- );
5131
- $client->setHeaders("Accept",
5132
- isset($options['dataType'])
5133
- && isset(self::$ajaxSettings['accepts'][ $options['dataType'] ])
5134
- ? self::$ajaxSettings['accepts'][ $options['dataType'] ].", */*"
5135
- : self::$ajaxSettings['accepts']['_default']
5136
- );
5137
- // TODO $options['processData']
5138
- if ($options['data'] instanceof phpQueryObject) {
5139
- $serialized = $options['data']->serializeArray($options['data']);
5140
- $options['data'] = array();
5141
- foreach($serialized as $r)
5142
- $options['data'][ $r['name'] ] = $r['value'];
5143
- }
5144
- if (strtolower($options['type']) == 'get') {
5145
- $client->setParameterGet($options['data']);
5146
- } else if (strtolower($options['type']) == 'post') {
5147
- $client->setEncType($options['contentType']);
5148
- $client->setParameterPost($options['data']);
5149
- }
5150
- if (self::$active == 0 && $options['global'])
5151
- phpQueryEvents::trigger($documentID, 'ajaxStart');
5152
- self::$active++;
5153
- // beforeSend callback
5154
- if (isset($options['beforeSend']) && $options['beforeSend'])
5155
- phpQuery::callbackRun($options['beforeSend'], array($client));
5156
- // ajaxSend event
5157
- if ($options['global'])
5158
- phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options));
5159
- if (phpQuery::$debug) {
5160
- self::debug("{$options['type']}: {$options['url']}\n");
5161
- self::debug("Options: <pre>".var_export($options, true)."</pre>\n");
5162
- // if ($client->getCookieJar())
5163
- // self::debug("Cookies: <pre>".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."</pre>\n");
5164
- }
5165
- // request
5166
- $response = $client->request();
5167
- if (phpQuery::$debug) {
5168
- self::debug('Status: '.$response->getStatus().' / '.$response->getMessage());
5169
- self::debug($client->getLastRequest());
5170
- self::debug($response->getHeaders());
5171
- }
5172
- if ($response->isSuccessful()) {
5173
- // XXX tempolary
5174
- self::$lastModified = $response->getHeader('Last-Modified');
5175
- $data = self::httpData($response->getBody(), $options['dataType'], $options);
5176
- if (isset($options['success']) && $options['success'])
5177
- phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options));
5178
- if ($options['global'])
5179
- phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options));
5180
- } else {
5181
- if (isset($options['error']) && $options['error'])
5182
- phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage()));
5183
- if ($options['global'])
5184
- phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/$response->getMessage(), $options));
5185
- }
5186
- if (isset($options['complete']) && $options['complete'])
5187
- phpQuery::callbackRun($options['complete'], array($client, $response->getStatus()));
5188
- if ($options['global'])
5189
- phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options));
5190
- if ($options['global'] && ! --self::$active)
5191
- phpQueryEvents::trigger($documentID, 'ajaxStop');
5192
- return $client;
5193
- // if (is_null($domId))
5194
- // $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false;
5195
- // return new phpQueryAjaxResponse($response, $domId);
5196
- }
5197
- protected static function httpData($data, $type, $options) {
5198
- if (isset($options['dataFilter']) && $options['dataFilter'])
5199
- $data = self::callbackRun($options['dataFilter'], array($data, $type));
5200
- if (is_string($data)) {
5201
- if ($type == "json") {
5202
- if (isset($options['_jsonp']) && $options['_jsonp']) {
5203
- $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data);
5204
- }
5205
- $data = self::parseJSON($data);
5206
- }
5207
- }
5208
- return $data;
5209
- }
5210
- /**
5211
- * Enter description here...
5212
- *
5213
- * @param array|phpQuery $data
5214
- *
5215
- */
5216
- public static function param($data) {
5217
- return http_build_query($data, null, '&');
5218
- }
5219
- public static function get($url, $data = null, $callback = null, $type = null) {
5220
- if (!is_array($data)) {
5221
- $callback = $data;
5222
- $data = null;
5223
- }
5224
- // TODO some array_values on this shit
5225
- return phpQuery::ajax(array(
5226
- 'type' => 'GET',
5227
- 'url' => $url,
5228
- 'data' => $data,
5229
- 'success' => $callback,
5230
- 'dataType' => $type,
5231
- ));
5232
- }
5233
- public static function post($url, $data = null, $callback = null, $type = null) {
5234
- if (!is_array($data)) {
5235
- $callback = $data;
5236
- $data = null;
5237
- }
5238
- return phpQuery::ajax(array(
5239
- 'type' => 'POST',
5240
- 'url' => $url,
5241
- 'data' => $data,
5242
- 'success' => $callback,
5243
- 'dataType' => $type,
5244
- ));
5245
- }
5246
- public static function getJSON($url, $data = null, $callback = null) {
5247
- if (!is_array($data)) {
5248
- $callback = $data;
5249
- $data = null;
5250
- }
5251
- // TODO some array_values on this shit
5252
- return phpQuery::ajax(array(
5253
- 'type' => 'GET',
5254
- 'url' => $url,
5255
- 'data' => $data,
5256
- 'success' => $callback,
5257
- 'dataType' => 'json',
5258
- ));
5259
- }
5260
- public static function ajaxSetup($options) {
5261
- self::$ajaxSettings = array_merge(
5262
- self::$ajaxSettings,
5263
- $options
5264
- );
5265
- }
5266
- public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) {
5267
- $loop = is_array($host1)
5268
- ? $host1
5269
- : func_get_args();
5270
- foreach($loop as $host) {
5271
- if ($host && ! in_array($host, phpQuery::$ajaxAllowedHosts)) {
5272
- phpQuery::$ajaxAllowedHosts[] = $host;
5273
- }
5274
- }
5275
- }
5276
- public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) {
5277
- $loop = is_array($url1)
5278
- ? $url1
5279
- : func_get_args();
5280
- foreach($loop as $url)
5281
- phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST));
5282
- }
5283
- /**
5284
- * Returns JSON representation of $data.
5285
- *
5286
- * @static
5287
- * @param mixed $data
5288
- * @return string
5289
- */
5290
- public static function toJSON($data) {
5291
- if (function_exists('json_encode'))
5292
- return json_encode($data);
5293
- require_once('Zend/Json/Encoder.php');
5294
- return Zend_Json_Encoder::encode($data);
5295
- }
5296
- /**
5297
- * Parses JSON into proper PHP type.
5298
- *
5299
- * @static
5300
- * @param string $json
5301
- * @return mixed
5302
- */
5303
- public static function parseJSON($json) {
5304
- if (function_exists('json_decode')) {
5305
- $return = json_decode(trim($json), true);
5306
- // json_decode and UTF8 issues
5307
- if (isset($return))
5308
- return $return;
5309
- }
5310
- require_once('Zend/Json/Decoder.php');
5311
- return Zend_Json_Decoder::decode($json);
5312
- }
5313
- /**
5314
- * Returns source's document ID.
5315
- *
5316
- * @param $source DOMNode|phpQueryObject
5317
- * @return string
5318
- */
5319
- public static function getDocumentID($source) {
5320
- if ($source instanceof DOMDOCUMENT) {
5321
- foreach(phpQuery::$documents as $id => $document) {
5322
- if ($source->isSameNode($document->document))
5323
- return $id;
5324
- }
5325
- } else if ($source instanceof DOMNODE) {
5326
- foreach(phpQuery::$documents as $id => $document) {
5327
- if ($source->ownerDocument->isSameNode($document->document))
5328
- return $id;
5329
- }
5330
- } else if ($source instanceof phpQueryObject)
5331
- return $source->getDocumentID();
5332
- else if (is_string($source) && isset(phpQuery::$documents[$source]))
5333
- return $source;
5334
- }
5335
- /**
5336
- * Get DOMDocument object related to $source.
5337
- * Returns null if such document doesn't exist.
5338
- *
5339
- * @param $source DOMNode|phpQueryObject|string
5340
- * @return string
5341
- */
5342
- public static function getDOMDocument($source) {
5343
- if ($source instanceof DOMDOCUMENT)
5344
- return $source;
5345
- $source = self::getDocumentID($source);
5346
- return $source
5347
- ? self::$documents[$id]['document']
5348
- : null;
5349
- }
5350
-
5351
- // UTILITIES
5352
- // http://docs.jquery.com/Utilities
5353
-
5354
- /**
5355
- *
5356
- * @return unknown_type
5357
- * @link http://docs.jquery.com/Utilities/jQuery.makeArray
5358
- */
5359
- public static function makeArray($obj) {
5360
- $array = array();
5361
- if (is_object($object) && $object instanceof DOMNODELIST) {
5362
- foreach($object as $value)
5363
- $array[] = $value;
5364
- } else if (is_object($object) && ! ($object instanceof Iterator)) {
5365
- foreach(get_object_vars($object) as $name => $value)
5366
- $array[0][$name] = $value;
5367
- } else {
5368
- foreach($object as $name => $value)
5369
- $array[0][$name] = $value;
5370
- }
5371
- return $array;
5372
- }
5373
- public static function inArray($value, $array) {
5374
- return in_array($value, $array);
5375
- }
5376
- /**
5377
- *
5378
- * @param $object
5379
- * @param $callback
5380
- * @return unknown_type
5381
- * @link http://docs.jquery.com/Utilities/jQuery.each
5382
- */
5383
- public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) {
5384
- $paramStructure = null;
5385
- if (func_num_args() > 2) {
5386
- $paramStructure = func_get_args();
5387
- $paramStructure = array_slice($paramStructure, 2);
5388
- }
5389
- if (is_object($object) && ! ($object instanceof Iterator)) {
5390
- foreach(get_object_vars($object) as $name => $value)
5391
- phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
5392
- } else {
5393
- foreach($object as $name => $value)
5394
- phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
5395
- }
5396
- }
5397
- /**
5398
- *
5399
- * @link http://docs.jquery.com/Utilities/jQuery.map
5400
- */
5401
- public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) {
5402
- $result = array();
5403
- $paramStructure = null;
5404
- if (func_num_args() > 2) {
5405
- $paramStructure = func_get_args();
5406
- $paramStructure = array_slice($paramStructure, 2);
5407
- }
5408
- foreach($array as $v) {
5409
- $vv = phpQuery::callbackRun($callback, array($v), $paramStructure);
5410
- // $callbackArgs = $args;
5411
- // foreach($args as $i => $arg) {
5412
- // $callbackArgs[$i] = $arg instanceof CallbackParam
5413
- // ? $v
5414
- // : $arg;
5415
- // }
5416
- // $vv = call_user_func_array($callback, $callbackArgs);
5417
- if (is_array($vv)) {
5418
- foreach($vv as $vvv)
5419
- $result[] = $vvv;
5420
- } else if ($vv !== null) {
5421
- $result[] = $vv;
5422
- }
5423
- }
5424
- return $result;
5425
- }
5426
- /**
5427
- *
5428
- * @param $callback Callback
5429
- * @param $params
5430
- * @param $paramStructure
5431
- * @return unknown_type
5432
- */
5433
- public static function callbackRun($callback, $params = array(), $paramStructure = null) {
5434
- if (! $callback)
5435
- return;
5436
- if ($callback instanceof CallbackParameterToReference) {
5437
- // TODO support ParamStructure to select which $param push to reference
5438
- if (isset($params[0]))
5439
- $callback->callback = $params[0];
5440
- return true;
5441
- }
5442
- if ($callback instanceof Callback) {
5443
- $paramStructure = $callback->params;
5444
- $callback = $callback->callback;
5445
- }
5446
- if (! $paramStructure)
5447
- return call_user_func_array($callback, $params);
5448
- $p = 0;
5449
- foreach($paramStructure as $i => $v) {
5450
- $paramStructure[$i] = $v instanceof CallbackParam
5451
- ? $params[$p++]
5452
- : $v;
5453
- }
5454
- return call_user_func_array($callback, $paramStructure);
5455
- }
5456
- /**
5457
- * Merge 2 phpQuery objects.
5458
- * @param array $one
5459
- * @param array $two
5460
- * @protected
5461
- * @todo node lists, phpQueryObject
5462
- */
5463
- public static function merge($one, $two) {
5464
- $elements = $one->elements;
5465
- foreach($two->elements as $node) {
5466
- $exists = false;
5467
- foreach($elements as $node2) {
5468
- if ($node2->isSameNode($node))
5469
- $exists = true;
5470
- }
5471
- if (! $exists)
5472
- $elements[] = $node;
5473
- }
5474
- return $elements;
5475
- // $one = $one->newInstance();
5476
- // $one->elements = $elements;
5477
- // return $one;
5478
- }
5479
- /**
5480
- *
5481
- * @param $array
5482
- * @param $callback
5483
- * @param $invert
5484
- * @return unknown_type
5485
- * @link http://docs.jquery.com/Utilities/jQuery.grep
5486
- */
5487
- public static function grep($array, $callback, $invert = false) {
5488
- $result = array();
5489
- foreach($array as $k => $v) {
5490
- $r = call_user_func_array($callback, array($v, $k));
5491
- if ($r === !(bool)$invert)
5492
- $result[] = $v;
5493
- }
5494
- return $result;
5495
- }
5496
- public static function unique($array) {
5497
- return array_unique($array);
5498
- }
5499
- /**
5500
- *
5501
- * @param $function
5502
- * @return unknown_type
5503
- * @TODO there are problems with non-static methods, second parameter pass it
5504
- * but doesnt verify is method is really callable
5505
- */
5506
- public static function isFunction($function) {
5507
- return is_callable($function);
5508
- }
5509
- public static function trim($str) {
5510
- return trim($str);
5511
- }
5512
- /* PLUGINS NAMESPACE */
5513
- /**
5514
- *
5515
- * @param $url
5516
- * @param $callback
5517
- * @param $param1
5518
- * @param $param2
5519
- * @param $param3
5520
- * @return phpQueryObject
5521
- */
5522
- public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) {
5523
- if (self::plugin('WebBrowser')) {
5524
- $params = func_get_args();
5525
- return self::callbackRun(array(self::$plugins, 'browserGet'), $params);
5526
- } else {
5527
- self::debug('WebBrowser plugin not available...');
5528
- }
5529
- }
5530
- /**
5531
- *
5532
- * @param $url
5533
- * @param $data
5534
- * @param $callback
5535
- * @param $param1
5536
- * @param $param2
5537
- * @param $param3
5538
- * @return phpQueryObject
5539
- */
5540
- public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) {
5541
- if (self::plugin('WebBrowser')) {
5542
- $params = func_get_args();
5543
- return self::callbackRun(array(self::$plugins, 'browserPost'), $params);
5544
- } else {
5545
- self::debug('WebBrowser plugin not available...');
5546
- }
5547
- }
5548
- /**
5549
- *
5550
- * @param $ajaxSettings
5551
- * @param $callback
5552
- * @param $param1
5553
- * @param $param2
5554
- * @param $param3
5555
- * @return phpQueryObject
5556
- */
5557
- public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) {
5558
- if (self::plugin('WebBrowser')) {
5559
- $params = func_get_args();
5560
- return self::callbackRun(array(self::$plugins, 'browser'), $params);
5561
- } else {
5562
- self::debug('WebBrowser plugin not available...');
5563
- }
5564
- }
5565
- /**
5566
- *
5567
- * @param $code
5568
- * @return string
5569
- */
5570
- public static function php($code) {
5571
- return self::code('php', $code);
5572
- }
5573
- /**
5574
- *
5575
- * @param $type
5576
- * @param $code
5577
- * @return string
5578
- */
5579
- public static function code($type, $code) {
5580
- return "<$type><!-- ".trim($code)." --></$type>";
5581
- }
5582
-
5583
- public static function __callStatic($method, $params) {
5584
- return call_user_func_array(
5585
- array(phpQuery::$plugins, $method),
5586
- $params
5587
- );
5588
- }
5589
- protected static function dataSetupNode($node, $documentID) {
5590
- // search are return if alredy exists
5591
- foreach(phpQuery::$documents[$documentID]->dataNodes as $dataNode) {
5592
- if ($node->isSameNode($dataNode))
5593
- return $dataNode;
5594
- }
5595
- // if doesn't, add it
5596
- phpQuery::$documents[$documentID]->dataNodes[] = $node;
5597
- return $node;
5598
- }
5599
- protected static function dataRemoveNode($node, $documentID) {
5600
- // search are return if alredy exists
5601
- foreach(phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) {
5602
- if ($node->isSameNode($dataNode)) {
5603
- unset(self::$documents[$documentID]->dataNodes[$k]);
5604
- unset(self::$documents[$documentID]->data[ $dataNode->dataID ]);
5605
- }
5606
- }
5607
- }
5608
- public static function data($node, $name, $data, $documentID = null) {
5609
- if (! $documentID)
5610
- // TODO check if this works
5611
- $documentID = self::getDocumentID($node);
5612
- $document = phpQuery::$documents[$documentID];
5613
- $node = self::dataSetupNode($node, $documentID);
5614
- if (! isset($node->dataID))
5615
- $node->dataID = ++phpQuery::$documents[$documentID]->uuid;
5616
- $id = $node->dataID;
5617
- if (! isset($document->data[$id]))
5618
- $document->data[$id] = array();
5619
- if (! is_null($data))
5620
- $document->data[$id][$name] = $data;
5621
- if ($name) {
5622
- if (isset($document->data[$id][$name]))
5623
- return $document->data[$id][$name];
5624
- } else
5625
- return $id;
5626
- }
5627
- public static function removeData($node, $name, $documentID) {
5628
- if (! $documentID)
5629
- // TODO check if this works
5630
- $documentID = self::getDocumentID($node);
5631
- $document = phpQuery::$documents[$documentID];
5632
- $node = self::dataSetupNode($node, $documentID);
5633
- $id = $node->dataID;
5634
- if ($name) {
5635
- if (isset($document->data[$id][$name]))
5636
- unset($document->data[$id][$name]);
5637
- $name = null;
5638
- foreach($document->data[$id] as $name)
5639
- break;
5640
- if (! $name)
5641
- self::removeData($node, $name, $documentID);
5642
- } else {
5643
- self::dataRemoveNode($node, $documentID);
5644
- }
5645
- }
5646
- }
5647
- /**
5648
- * Plugins static namespace class.
5649
- *
5650
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
5651
- * @package phpQuery
5652
- * @todo move plugin methods here (as statics)
5653
- */
5654
- class phpQueryPlugins {
5655
- public function __call($method, $args) {
5656
- if (isset(phpQuery::$extendStaticMethods[$method])) {
5657
- $return = call_user_func_array(
5658
- phpQuery::$extendStaticMethods[$method],
5659
- $args
5660
- );
5661
- } else if (isset(phpQuery::$pluginsStaticMethods[$method])) {
5662
- $class = phpQuery::$pluginsStaticMethods[$method];
5663
- $realClass = "phpQueryPlugin_$class";
5664
- $return = call_user_func_array(
5665
- array($realClass, $method),
5666
- $args
5667
- );
5668
- return isset($return)
5669
- ? $return
5670
- : $this;
5671
- } else
5672
- throw new Exception("Method '{$method}' doesnt exist");
5673
- }
5674
- }
5675
- /**
5676
- * Shortcut to phpQuery::pq($arg1, $context)
5677
- * Chainable.
5678
- *
5679
- * @see phpQuery::pq()
5680
- * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
5681
- * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
5682
- * @package phpQuery
5683
- */
5684
- function pq($arg1, $context = null) {
5685
- $args = func_get_args();
5686
- return call_user_func_array(
5687
- array('phpQuery', 'pq'),
5688
- $args
5689
- );
5690
- }
5691
- // add plugins dir and Zend framework to include path
5692
- set_include_path(
5693
- get_include_path()
5694
- .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/'
5695
- .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/plugins/'
5696
- );
5697
- // why ? no __call nor __get for statics in php...
5698
- // XXX __callStatic will be available in PHP 5.3
5699
- phpQuery::$plugins = new phpQueryPlugins();
5700
- // include bootstrap file (personal library config)
5701
- if (file_exists(dirname(__FILE__).'/phpQuery/bootstrap.php'))
5702
  require_once dirname(__FILE__).'/phpQuery/bootstrap.php';
1
+ <?php
2
+ /**
3
+ * phpQuery is a server-side, chainable, CSS3 selector driven
4
+ * Document Object Model (DOM) API based on jQuery JavaScript Library.
5
+ *
6
+ * @version 0.9.5
7
+ * @link http://code.google.com/p/phpquery/
8
+ * @link http://phpquery-library.blogspot.com/
9
+ * @link http://jquery.com/
10
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
11
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
12
+ * @package phpQuery
13
+ */
14
+
15
+ // class names for instanceof
16
+ // TODO move them as class constants into phpQuery
17
+ define('DOMDOCUMENT', 'DOMDocument');
18
+ define('DOMELEMENT', 'DOMElement');
19
+ define('DOMNODELIST', 'DOMNodeList');
20
+ define('DOMNODE', 'DOMNode');
21
+
22
+ /**
23
+ * DOMEvent class.
24
+ *
25
+ * Based on
26
+ * @link http://developer.mozilla.org/En/DOM:event
27
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
28
+ * @package phpQuery
29
+ * @todo implement ArrayAccess ?
30
+ */
31
+ class DOMEvent {
32
+ /**
33
+ * Returns a boolean indicating whether the event bubbles up through the DOM or not.
34
+ *
35
+ * @var unknown_type
36
+ */
37
+ public $bubbles = true;
38
+ /**
39
+ * Returns a boolean indicating whether the event is cancelable.
40
+ *
41
+ * @var unknown_type
42
+ */
43
+ public $cancelable = true;
44
+ /**
45
+ * Returns a reference to the currently registered target for the event.
46
+ *
47
+ * @var unknown_type
48
+ */
49
+ public $currentTarget;
50
+ /**
51
+ * Returns detail about the event, depending on the type of event.
52
+ *
53
+ * @var unknown_type
54
+ * @link http://developer.mozilla.org/en/DOM/event.detail
55
+ */
56
+ public $detail; // ???
57
+ /**
58
+ * Used to indicate which phase of the event flow is currently being evaluated.
59
+ *
60
+ * NOT IMPLEMENTED
61
+ *
62
+ * @var unknown_type
63
+ * @link http://developer.mozilla.org/en/DOM/event.eventPhase
64
+ */
65
+ public $eventPhase; // ???
66
+ /**
67
+ * The explicit original target of the event (Mozilla-specific).
68
+ *
69
+ * NOT IMPLEMENTED
70
+ *
71
+ * @var unknown_type
72
+ */
73
+ public $explicitOriginalTarget; // moz only
74
+ /**
75
+ * The original target of the event, before any retargetings (Mozilla-specific).
76
+ *
77
+ * NOT IMPLEMENTED
78
+ *
79
+ * @var unknown_type
80
+ */
81
+ public $originalTarget; // moz only
82
+ /**
83
+ * Identifies a secondary target for the event.
84
+ *
85
+ * @var unknown_type
86
+ */
87
+ public $relatedTarget;
88
+ /**
89
+ * Returns a reference to the target to which the event was originally dispatched.
90
+ *
91
+ * @var unknown_type
92
+ */
93
+ public $target;
94
+ /**
95
+ * Returns the time that the event was created.
96
+ *
97
+ * @var unknown_type
98
+ */
99
+ public $timeStamp;
100
+ /**
101
+ * Returns the name of the event (case-insensitive).
102
+ */
103
+ public $type;
104
+ public $runDefault = true;
105
+ public $data = null;
106
+ public function __construct($data) {
107
+ foreach($data as $k => $v) {
108
+ $this->$k = $v;
109
+ }
110
+ if (! $this->timeStamp)
111
+ $this->timeStamp = time();
112
+ }
113
+ /**
114
+ * Cancels the event (if it is cancelable).
115
+ *
116
+ */
117
+ public function preventDefault() {
118
+ $this->runDefault = false;
119
+ }
120
+ /**
121
+ * Stops the propagation of events further along in the DOM.
122
+ *
123
+ */
124
+ public function stopPropagation() {
125
+ $this->bubbles = false;
126
+ }
127
+ }
128
+
129
+
130
+ /**
131
+ * DOMDocumentWrapper class simplifies work with DOMDocument.
132
+ *
133
+ * Know bug:
134
+ * - in XHTML fragments, <br /> changes to <br clear="none" />
135
+ *
136
+ * @todo check XML catalogs compatibility
137
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
138
+ * @package phpQuery
139
+ */
140
+ class DOMDocumentWrapper {
141
+ /**
142
+ * @var DOMDocument
143
+ */
144
+ public $document;
145
+ public $id;
146
+ /**
147
+ * @todo Rewrite as method and quess if null.
148
+ * @var unknown_type
149
+ */
150
+ public $contentType = '';
151
+ public $xpath;
152
+ public $uuid = 0;
153
+ public $data = array();
154
+ public $dataNodes = array();
155
+ public $events = array();
156
+ public $eventsNodes = array();
157
+ public $eventsGlobal = array();
158
+ /**
159
+ * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28
160
+ * @var unknown_type
161
+ */
162
+ public $frames = array();
163
+ /**
164
+ * Document root, by default equals to document itself.
165
+ * Used by documentFragments.
166
+ *
167
+ * @var DOMNode
168
+ */
169
+ public $root;
170
+ public $isDocumentFragment;
171
+ public $isXML = false;
172
+ public $isXHTML = false;
173
+ public $isHTML = false;
174
+ public $charset;
175
+ public function __construct($markup = null, $contentType = null, $newDocumentID = null) {
176
+ if (isset($markup))
177
+ $this->load($markup, $contentType, $newDocumentID);
178
+ $this->id = $newDocumentID
179
+ ? $newDocumentID
180
+ : md5(microtime());
181
+ }
182
+ public function load($markup, $contentType = null, $newDocumentID = null) {
183
+ // phpQuery::$documents[$id] = $this;
184
+ $this->contentType = strtolower($contentType);
185
+ if ($markup instanceof DOMDOCUMENT) {
186
+ $this->document = $markup;
187
+ $this->root = $this->document;
188
+ $this->charset = $this->document->encoding;
189
+ // TODO isDocumentFragment
190
+ } else {
191
+ $loaded = $this->loadMarkup($markup);
192
+ }
193
+ if ($loaded) {
194
+ // $this->document->formatOutput = true;
195
+ $this->document->preserveWhiteSpace = true;
196
+ $this->xpath = new DOMXPath($this->document);
197
+ $this->afterMarkupLoad();
198
+ return true;
199
+ // remember last loaded document
200
+ // return phpQuery::selectDocument($id);
201
+ }
202
+ return false;
203
+ }
204
+ protected function afterMarkupLoad() {
205
+ if ($this->isXHTML) {
206
+ $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml");
207
+ }
208
+ }
209
+ protected function loadMarkup($markup) {
210
+ $loaded = false;
211
+ if ($this->contentType) {
212
+ self::debug("Load markup for content type {$this->contentType}");
213
+ // content determined by contentType
214
+ list($contentType, $charset) = $this->contentTypeToArray($this->contentType);
215
+ switch($contentType) {
216
+ case 'text/html':
217
+ phpQuery::debug("Loading HTML, content type '{$this->contentType}'");
218
+ $loaded = $this->loadMarkupHTML($markup, $charset);
219
+ break;
220
+ case 'text/xml':
221
+ case 'application/xhtml+xml':
222
+ phpQuery::debug("Loading XML, content type '{$this->contentType}'");
223
+ $loaded = $this->loadMarkupXML($markup, $charset);
224
+ break;
225
+ default:
226
+ // for feeds or anything that sometimes doesn't use text/xml
227
+ if (strpos('xml', $this->contentType) !== false) {
228
+ phpQuery::debug("Loading XML, content type '{$this->contentType}'");
229
+ $loaded = $this->loadMarkupXML($markup, $charset);
230
+ } else
231
+ phpQuery::debug("Could not determine document type from content type '{$this->contentType}'");
232
+ }
233
+ } else {
234
+ // content type autodetection
235
+ if ($this->isXML($markup)) {
236
+ phpQuery::debug("Loading XML, isXML() == true");
237
+ $loaded = $this->loadMarkupXML($markup);
238
+ if (! $loaded && $this->isXHTML) {
239
+ phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true');
240
+ $loaded = $this->loadMarkupHTML($markup);
241
+ }
242
+ } else {
243
+ phpQuery::debug("Loading HTML, isXML() == false");
244
+ $loaded = $this->loadMarkupHTML($markup);
245
+ }
246
+ }
247
+ return $loaded;
248
+ }
249
+ protected function loadMarkupReset() {
250
+ $this->isXML = $this->isXHTML = $this->isHTML = false;
251
+ }
252
+ protected function documentCreate($charset, $version = '1.0') {
253
+ if (! $version)
254
+ $version = '1.0';
255
+ $this->document = new DOMDocument($version, $charset);
256
+ $this->charset = $this->document->encoding;
257
+ // $this->document->encoding = $charset;
258
+ $this->document->formatOutput = true;
259
+ $this->document->preserveWhiteSpace = true;
260
+ }
261
+ protected function loadMarkupHTML($markup, $requestedCharset = null) {
262
+ if (phpQuery::$debug)
263
+ phpQuery::debug('Full markup load (HTML): '.substr($markup, 0, 250));
264
+ $this->loadMarkupReset();
265
+ $this->isHTML = true;
266
+ if (!isset($this->isDocumentFragment))
267
+ $this->isDocumentFragment = self::isDocumentFragmentHTML($markup);
268
+ $charset = null;
269
+ $documentCharset = $this->charsetFromHTML($markup);
270
+ $addDocumentCharset = false;
271
+ if ($documentCharset) {
272
+ $charset = $documentCharset;
273
+ $markup = $this->charsetFixHTML($markup);
274
+ } else if ($requestedCharset) {
275
+ $charset = $requestedCharset;
276
+ }
277
+ if (! $charset)
278
+ $charset = phpQuery::$defaultCharset;
279
+ // HTTP 1.1 says that the default charset is ISO-8859-1
280
+ // @see http://www.w3.org/International/O-HTTP-charset
281
+ if (! $documentCharset) {
282
+ $documentCharset = 'ISO-8859-1';
283
+ $addDocumentCharset = true;
284
+ }
285
+ // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding'
286
+ // Worse, some pages can have mixed encodings... we'll try not to worry about that
287
+ $requestedCharset = strtoupper($requestedCharset);
288
+ $documentCharset = strtoupper($documentCharset);
289
+ phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset");
290
+ if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) {
291
+ phpQuery::debug("CHARSET CONVERT");
292
+ // Document Encoding Conversion
293
+ // http://code.google.com/p/phpquery/issues/detail?id=86
294
+ if (function_exists('mb_detect_encoding')) {
295
+ $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO');
296
+ $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets));
297
+ if (! $docEncoding)
298
+ $docEncoding = $documentCharset; // ok trust the document
299
+ phpQuery::debug("DETECTED '$docEncoding'");
300
+ // Detected does not match what document says...
301
+ if ($docEncoding !== $documentCharset) {
302
+ // Tricky..
303
+ }
304
+ if ($docEncoding !== $requestedCharset) {
305
+ phpQuery::debug("CONVERT $docEncoding => $requestedCharset");
306
+ $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding);
307
+ $markup = $this->charsetAppendToHTML($markup, $requestedCharset);
308
+ $charset = $requestedCharset;
309
+ }
310
+ } else {
311
+ phpQuery::debug("TODO: charset conversion without mbstring...");
312
+ }
313
+ }
314
+ $return = false;
315
+ if ($this->isDocumentFragment) {
316
+ phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'");
317
+ $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
318
+ } else {
319
+ if ($addDocumentCharset) {
320
+ phpQuery::debug("Full markup load (HTML), appending charset: '$charset'");
321
+ $markup = $this->charsetAppendToHTML($markup, $charset);
322
+ }
323
+ phpQuery::debug("Full markup load (HTML), documentCreate('$charset')");
324
+ $this->documentCreate($charset);
325
+ $return = phpQuery::$debug === 2
326
+ ? $this->document->loadHTML($markup)
327
+ : @$this->document->loadHTML($markup);
328
+ if ($return)
329
+ $this->root = $this->document;
330
+ }
331
+ if ($return && ! $this->contentType)
332
+ $this->contentType = 'text/html';
333
+ return $return;
334
+ }
335
+ protected function loadMarkupXML($markup, $requestedCharset = null) {
336
+ if (phpQuery::$debug)
337
+ phpQuery::debug('Full markup load (XML): '.substr($markup, 0, 250));
338
+ $this->loadMarkupReset();
339
+ $this->isXML = true;
340
+ // check agains XHTML in contentType or markup
341
+ $isContentTypeXHTML = $this->isXHTML();
342
+ $isMarkupXHTML = $this->isXHTML($markup);
343
+ if ($isContentTypeXHTML || $isMarkupXHTML) {
344
+ self::debug('Full markup load (XML), XHTML detected');
345
+ $this->isXHTML = true;
346
+ }
347
+ // determine document fragment
348
+ if (! isset($this->isDocumentFragment))
349
+ $this->isDocumentFragment = $this->isXHTML
350
+ ? self::isDocumentFragmentXHTML($markup)
351
+ : self::isDocumentFragmentXML($markup);
352
+ // this charset will be used
353
+ $charset = null;
354
+ // charset from XML declaration @var string
355
+ $documentCharset = $this->charsetFromXML($markup);
356
+ if (! $documentCharset) {
357
+ if ($this->isXHTML) {
358
+ // this is XHTML, try to get charset from content-type meta header
359
+ $documentCharset = $this->charsetFromHTML($markup);
360
+ if ($documentCharset) {
361
+ phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'");
362
+ $this->charsetAppendToXML($markup, $documentCharset);
363
+ $charset = $documentCharset;
364
+ }
365
+ }
366
+ if (! $documentCharset) {
367
+ // if still no document charset...
368
+ $charset = $requestedCharset;
369
+ }
370
+ } else if ($requestedCharset) {
371
+ $charset = $requestedCharset;
372
+ }
373
+ if (! $charset) {
374
+ $charset = phpQuery::$defaultCharset;
375
+ }
376
+ if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) {
377
+ // TODO place for charset conversion
378
+ // $charset = $requestedCharset;
379
+ }
380
+ $return = false;
381
+ if ($this->isDocumentFragment) {
382
+ phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'");
383
+ $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
384
+ } else {
385
+ // FIXME ???
386
+ if ($isContentTypeXHTML && ! $isMarkupXHTML)
387
+ if (! $documentCharset) {
388
+ phpQuery::debug("Full markup load (XML), appending charset '$charset'");
389
+ $markup = $this->charsetAppendToXML($markup, $charset);
390
+ }
391
+ // see http://pl2.php.net/manual/en/book.dom.php#78929
392
+ // LIBXML_DTDLOAD (>= PHP 5.1)
393
+ // does XML ctalogues works with LIBXML_NONET
394
+ // $this->document->resolveExternals = true;
395
+ // TODO test LIBXML_COMPACT for performance improvement
396
+ // create document
397
+ $this->documentCreate($charset);
398
+ if (phpversion() < 5.1) {
399
+ $this->document->resolveExternals = true;
400
+ $return = phpQuery::$debug === 2
401
+ ? $this->document->loadXML($markup)
402
+ : @$this->document->loadXML($markup);
403
+ } else {
404
+ /** @link http://pl2.php.net/manual/en/libxml.constants.php */
405
+ $libxmlStatic = phpQuery::$debug === 2
406
+ ? LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET
407
+ : LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOERROR;
408
+ $return = $this->document->loadXML($markup, $libxmlStatic);
409
+ // if (! $return)
410
+ // $return = $this->document->loadHTML($markup);
411
+ }
412
+ if ($return)
413
+ $this->root = $this->document;
414
+ }
415
+ if ($return) {
416
+ if (! $this->contentType) {
417
+ if ($this->isXHTML)
418
+ $this->contentType = 'application/xhtml+xml';
419
+ else
420
+ $this->contentType = 'text/xml';
421
+ }
422
+ return $return;
423
+ } else {
424
+ throw new Exception("Error loading XML markup");
425
+ }
426
+ }
427
+ protected function isXHTML($markup = null) {
428
+ if (! isset($markup)) {
429
+ return strpos($this->contentType, 'xhtml') !== false;
430
+ }
431
+ // XXX ok ?
432
+ return strpos($markup, "<!DOCTYPE html") !== false;
433
+ // return stripos($doctype, 'xhtml') !== false;
434
+ // $doctype = isset($dom->doctype) && is_object($dom->doctype)
435
+ // ? $dom->doctype->publicId
436
+ // : self::$defaultDoctype;
437
+ }
438
+ protected function isXML($markup) {
439
+ // return strpos($markup, '<?xml') !== false && stripos($markup, 'xhtml') === false;
440
+ return strpos(substr($markup, 0, 100), '<'.'?xml') !== false;
441
+ }
442
+ protected function contentTypeToArray($contentType) {
443
+ $matches = explode(';', trim(strtolower($contentType)));
444
+ if (isset($matches[1])) {
445
+ $matches[1] = explode('=', $matches[1]);
446
+ // strip 'charset='
447
+ $matches[1] = isset($matches[1][1]) && trim($matches[1][1])
448
+ ? $matches[1][1]
449
+ : $matches[1][0];
450
+ } else
451
+ $matches[1] = null;
452
+ return $matches;
453
+ }
454
+ /**
455
+ *
456
+ * @param $markup
457
+ * @return array contentType, charset
458
+ */
459
+ protected function contentTypeFromHTML($markup) {
460
+ $matches = array();
461
+ // find meta tag
462
+ preg_match('@<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
463
+ $markup, $matches
464
+ );
465
+ if (! isset($matches[0]))
466
+ return array(null, null);
467
+ // get attr 'content'
468
+ preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches);
469
+ if (! isset($matches[0]))
470
+ return array(null, null);
471
+ return $this->contentTypeToArray($matches[2]);
472
+ }
473
+ protected function charsetFromHTML($markup) {
474
+ $contentType = $this->contentTypeFromHTML($markup);
475
+ return $contentType[1];
476
+ }
477
+ protected function charsetFromXML($markup) {
478
+ $matches;
479
+ // find declaration
480
+ preg_match('@<'.'?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i',
481
+ $markup, $matches
482
+ );
483
+ return isset($matches[2])
484
+ ? strtolower($matches[2])
485
+ : null;
486
+ }
487
+ /**
488
+ * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug.
489
+ *
490
+ * @link http://code.google.com/p/phpquery/issues/detail?id=80
491
+ * @param $html
492
+ */
493
+ protected function charsetFixHTML($markup) {
494
+ $matches = array();
495
+ // find meta tag
496
+ preg_match('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
497
+ $markup, $matches, PREG_OFFSET_CAPTURE
498
+ );
499
+ if (! isset($matches[0]))
500
+ return;
501
+ $metaContentType = $matches[0][0];
502
+ $markup = substr($markup, 0, $matches[0][1])
503
+ .substr($markup, $matches[0][1]+strlen($metaContentType));
504
+ $headStart = stripos($markup, '<head>');
505
+ $markup = substr($markup, 0, $headStart+6).$metaContentType
506
+ .substr($markup, $headStart+6);
507
+ return $markup;
508
+ }
509
+ protected function charsetAppendToHTML($html, $charset, $xhtml = false) {
510
+ // remove existing meta[type=content-type]
511
+ $html = preg_replace('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html);
512
+ $meta = '<meta http-equiv="Content-Type" content="text/html;charset='
513
+ .$charset.'" '
514
+ .($xhtml ? '/' : '')
515
+ .'>';
516
+ if (strpos($html, '<head') === false) {
517
+ if (strpos($hltml, '<html') === false) {
518
+ return $meta.$html;
519
+ } else {
520
+ return preg_replace(
521
+ '@<html(.*?)(?(?<!\?)>)@s',
522
+ "<html\\1><head>{$meta}</head>",
523
+ $html
524
+ );
525
+ }
526
+ } else {
527
+ return preg_replace(
528
+ '@<head(.*?)(?(?<!\?)>)@s',
529
+ '<head\\1>'.$meta,
530
+ $html
531
+ );
532
+ }
533
+ }
534
+ protected function charsetAppendToXML($markup, $charset) {
535
+ $declaration = '<'.'?xml version="1.0" encoding="'.$charset.'"?'.'>';
536
+ return $declaration.$markup;
537
+ }
538
+ public static function isDocumentFragmentHTML($markup) {
539
+ return stripos($markup, '<html') === false && stripos($markup, '<!doctype') === false;
540
+ }
541
+ public static function isDocumentFragmentXML($markup) {
542
+ return stripos($markup, '<'.'?xml') === false;
543
+ }
544
+ public static function isDocumentFragmentXHTML($markup) {
545
+ return self::isDocumentFragmentHTML($markup);
546
+ }
547
+ public function importAttr($value) {
548
+ // TODO
549
+ }
550
+ /**
551
+ *
552
+ * @param $source
553
+ * @param $target
554
+ * @param $sourceCharset
555
+ * @return array Array of imported nodes.
556
+ */
557
+ public function import($source, $sourceCharset = null) {
558
+ // TODO charset conversions
559
+ $return = array();
560
+ if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
561
+ $source = array($source);
562
+ // if (is_array($source)) {
563
+ // foreach($source as $node) {
564
+ // if (is_string($node)) {
565
+ // // string markup
566
+ // $fake = $this->documentFragmentCreate($node, $sourceCharset);
567
+ // if ($fake === false)
568
+ // throw new Exception("Error loading documentFragment markup");
569
+ // else
570
+ // $return = array_merge($return,
571
+ // $this->import($fake->root->childNodes)
572
+ // );
573
+ // } else {
574
+ // $return[] = $this->document->importNode($node, true);
575
+ // }
576
+ // }
577
+ // return $return;
578
+ // } else {
579
+ // // string markup
580
+ // $fake = $this->documentFragmentCreate($source, $sourceCharset);
581
+ // if ($fake === false)
582
+ // throw new Exception("Error loading documentFragment markup");
583
+ // else
584
+ // return $this->import($fake->root->childNodes);
585
+ // }
586
+ if (is_array($source) || $source instanceof DOMNODELIST) {
587
+ // dom nodes
588
+ self::debug('Importing nodes to document');
589
+ foreach($source as $node)
590
+ $return[] = $this->document->importNode($node, true);
591
+ } else {
592
+ // string markup
593
+ $fake = $this->documentFragmentCreate($source, $sourceCharset);
594
+ if ($fake === false)
595
+ throw new Exception("Error loading documentFragment markup");
596
+ else
597
+ return $this->import($fake->root->childNodes);
598
+ }
599
+ return $return;
600
+ }
601
+ /**
602
+ * Creates new document fragment.
603
+ *
604
+ * @param $source
605
+ * @return DOMDocumentWrapper
606
+ */
607
+ protected function documentFragmentCreate($source, $charset = null) {
608
+ $fake = new DOMDocumentWrapper();
609
+ $fake->contentType = $this->contentType;
610
+ $fake->isXML = $this->isXML;
611
+ $fake->isHTML = $this->isHTML;
612
+ $fake->isXHTML = $this->isXHTML;
613
+ $fake->root = $fake->document;
614
+ if (! $charset)
615
+ $charset = $this->charset;
616
+ // $fake->documentCreate($this->charset);
617
+ if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
618
+ $source = array($source);
619
+ if (is_array($source) || $source instanceof DOMNODELIST) {
620
+ // dom nodes
621
+ // load fake document
622
+ if (! $this->documentFragmentLoadMarkup($fake, $charset))
623
+ return false;
624
+ $nodes = $fake->import($source);
625
+ foreach($nodes as $node)
626
+ $fake->root->appendChild($node);
627
+ } else {
628
+ // string markup
629
+ $this->documentFragmentLoadMarkup($fake, $charset, $source);
630
+ }
631
+ return $fake;
632
+ }
633
+ /**
634
+ *
635
+ * @param $document DOMDocumentWrapper
636
+ * @param $markup
637
+ * @return $document
638
+ */
639
+ private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) {
640
+ // TODO error handling
641
+ // TODO copy doctype
642
+ // tempolary turn off
643
+ $fragment->isDocumentFragment = false;
644
+ if ($fragment->isXML) {
645
+ if ($fragment->isXHTML) {
646
+ // add FAKE element to set default namespace
647
+ $fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?>'
648
+ .'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
649
+ .'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
650
+ .'<fake xmlns="http://www.w3.org/1999/xhtml">'.$markup.'</fake>');
651
+ $fragment->root = $fragment->document->firstChild->nextSibling;
652
+ } else {
653
+ $fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?><fake>'.$markup.'</fake>');
654
+ $fragment->root = $fragment->document->firstChild;
655
+ }
656
+ } else {
657
+ $markup2 = phpQuery::$defaultDoctype.'<html><head><meta http-equiv="Content-Type" content="text/html;charset='
658
+ .$charset.'"></head>';
659
+ $noBody = strpos($markup, '<body') === false;
660
+ if ($noBody)
661
+ $markup2 .= '<body>';
662
+ $markup2 .= $markup;
663
+ if ($noBody)
664
+ $markup2 .= '</body>';
665
+ $markup2 .= '</html>';
666
+ $fragment->loadMarkupHTML($markup2);
667
+ // TODO resolv body tag merging issue
668
+ $fragment->root = $noBody
669
+ ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling
670
+ : $fragment->document->firstChild->nextSibling->firstChild->nextSibling;
671
+ }
672
+ if (! $fragment->root)
673
+ return false;
674
+ $fragment->isDocumentFragment = true;
675
+ return true;
676
+ }
677
+ protected function documentFragmentToMarkup($fragment) {
678
+ phpQuery::debug('documentFragmentToMarkup');
679
+ $tmp = $fragment->isDocumentFragment;
680
+ $fragment->isDocumentFragment = false;
681
+ $markup = $fragment->markup();
682
+ if ($fragment->isXML) {
683
+ $markup = substr($markup, 0, strrpos($markup, '</fake>'));
684
+ if ($fragment->isXHTML) {
685
+ $markup = substr($markup, strpos($markup, '<fake')+43);
686
+ } else {
687
+ $markup = substr($markup, strpos($markup, '<fake>')+6);
688
+ }
689
+ } else {
690
+ $markup = substr($markup, strpos($markup, '<body>')+6);
691
+ $markup = substr($markup, 0, strrpos($markup, '</body>'));
692
+ }
693
+ $fragment->isDocumentFragment = $tmp;
694
+ if (phpQuery::$debug)
695
+ phpQuery::debug('documentFragmentToMarkup: '.substr($markup, 0, 150));
696
+ return $markup;
697
+ }
698
+ /**
699
+ * Return document markup, starting with optional $nodes as root.
700
+ *
701
+ * @param $nodes DOMNode|DOMNodeList
702
+ * @return string
703
+ */
704
+ public function markup($nodes = null, $innerMarkup = false) {
705
+ if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT)
706
+ $nodes = null;
707
+ if (isset($nodes)) {
708
+ $markup = '';
709
+ if (!is_array($nodes) && !($nodes instanceof DOMNODELIST) )
710
+ $nodes = array($nodes);
711
+ if ($this->isDocumentFragment && ! $innerMarkup)
712
+ foreach($nodes as $i => $node)
713
+ if ($node->isSameNode($this->root)) {
714
+ // var_dump($node);
715
+ $nodes = array_slice($nodes, 0, $i)
716
+ + phpQuery::DOMNodeListToArray($node->childNodes)
717
+ + array_slice($nodes, $i+1);
718
+ }
719
+ if ($this->isXML && ! $innerMarkup) {
720
+ self::debug("Getting outerXML with charset '{$this->charset}'");
721
+ // we need outerXML, so we can benefit from
722
+ // $node param support in saveXML()
723
+ foreach($nodes as $node)
724
+ $markup .= $this->document->saveXML($node);
725
+ } else {
726
+ $loop = array();
727
+ if ($innerMarkup)
728
+ foreach($nodes as $node) {
729
+ if ($node->childNodes)
730
+ foreach($node->childNodes as $child)
731
+ $loop[] = $child;
732
+ else
733
+ $loop[] = $node;
734
+ }
735
+ else
736
+ $loop = $nodes;
737
+ self::debug("Getting markup, moving selected nodes (".count($loop).") to new DocumentFragment");
738
+ $fake = $this->documentFragmentCreate($loop);
739
+ $markup = $this->documentFragmentToMarkup($fake);
740
+ }
741
+ if ($this->isXHTML) {
742
+ self::debug("Fixing XHTML");
743
+ $markup = self::markupFixXHTML($markup);
744
+ }
745
+ self::debug("Markup: ".substr($markup, 0, 250));
746
+ return $markup;
747
+ } else {
748
+ if ($this->isDocumentFragment) {
749
+ // documentFragment, html only...
750
+ self::debug("Getting markup, DocumentFragment detected");
751
+ // return $this->markup(
752
+ //// $this->document->getElementsByTagName('body')->item(0)
753
+ // $this->document->root, true
754
+ // );
755
+ $markup = $this->documentFragmentToMarkup($this);
756
+ // no need for markupFixXHTML, as it's done thought markup($nodes) method
757
+ return $markup;
758
+ } else {
759
+ self::debug("Getting markup (".($this->isXML?'XML':'HTML')."), final with charset '{$this->charset}'");
760
+ $markup = $this->isXML
761
+ ? $this->document->saveXML()
762
+ : $this->document->saveHTML();
763
+ if ($this->isXHTML) {
764
+ self::debug("Fixing XHTML");
765
+ $markup = self::markupFixXHTML($markup);
766
+ }
767
+ self::debug("Markup: ".substr($markup, 0, 250));
768
+ return $markup;
769
+ }
770
+ }
771
+ }
772
+ protected static function markupFixXHTML($markup) {
773
+ $markup = self::expandEmptyTag('script', $markup);
774
+ $markup = self::expandEmptyTag('select', $markup);
775
+ $markup = self::expandEmptyTag('textarea', $markup);
776
+ return $markup;
777
+ }
778
+ public static function debug($text) {
779
+ phpQuery::debug($text);
780
+ }
781
+ /**
782
+ * expandEmptyTag
783
+ *
784
+ * @param $tag
785
+ * @param $xml
786
+ * @return unknown_type
787
+ * @author mjaque at ilkebenson dot com
788
+ * @link http://php.net/manual/en/domdocument.savehtml.php#81256
789
+ */
790
+ public static function expandEmptyTag($tag, $xml){
791
+ $indice = 0;
792
+ while ($indice< strlen($xml)){
793
+ $pos = strpos($xml, "<$tag ", $indice);
794
+ if ($pos){
795
+ $posCierre = strpos($xml, ">", $pos);
796
+ if ($xml[$posCierre-1] == "/"){
797
+ $xml = substr_replace($xml, "></$tag>", $posCierre-1, 2);
798
+ }
799
+ $indice = $posCierre;
800
+ }
801
+ else break;
802
+ }
803
+ return $xml;
804
+ }
805
+ }
806
+
807
+ /**
808
+ * Event handling class.
809
+ *
810
+ * @author Tobiasz Cudnik
811
+ * @package phpQuery
812
+ * @static
813
+ */
814
+ abstract class phpQueryEvents {
815
+ /**
816
+ * Trigger a type of event on every matched element.
817
+ *
818
+ * @param DOMNode|phpQueryObject|string $document
819
+ * @param unknown_type $type
820
+ * @param unknown_type $data
821
+ *
822
+ * @TODO exclusive events (with !)
823
+ * @TODO global events (test)
824
+ * @TODO support more than event in $type (space-separated)
825
+ */
826
+ public static function trigger($document, $type, $data = array(), $node = null) {
827
+ // trigger: function(type, data, elem, donative, extra) {
828
+ $documentID = phpQuery::getDocumentID($document);
829
+ $namespace = null;
830
+ if (strpos($type, '.') !== false)
831
+ list($name, $namespace) = explode('.', $type);
832
+ else
833
+ $name = $type;
834
+ if (! $node) {
835
+ if (self::issetGlobal($documentID, $type)) {
836
+ $pq = phpQuery::getDocument($documentID);
837
+ // TODO check add($pq->document)
838
+ $pq->find('*')->add($pq->document)
839
+ ->trigger($type, $data);
840
+ }
841
+ } else {
842
+ if (isset($data[0]) && $data[0] instanceof DOMEvent) {
843
+ $event = $data[0];
844
+ $event->relatedTarget = $event->target;
845
+ $event->target = $node;
846
+ $data = array_slice($data, 1);
847
+ } else {
848
+ $event = new DOMEvent(array(
849
+ 'type' => $type,
850
+ 'target' => $node,
851
+ 'timeStamp' => time(),
852
+ ));
853
+ }
854
+ $i = 0;
855
+ while($node) {
856
+ // TODO whois
857
+ phpQuery::debug("Triggering ".($i?"bubbled ":'')."event '{$type}' on "
858
+ ."node \n");//.phpQueryObject::whois($node)."\n");
859
+ $event->currentTarget = $node;
860
+ $eventNode = self::getNode($documentID, $node);
861
+ if (isset($eventNode->eventHandlers)) {
862
+ foreach($eventNode->eventHandlers as $eventType => $handlers) {
863
+ $eventNamespace = null;
864
+ if (strpos($type, '.') !== false)
865
+ list($eventName, $eventNamespace) = explode('.', $eventType);
866
+ else
867
+ $eventName = $eventType;
868
+ if ($name != $eventName)
869
+ continue;
870
+ if ($namespace && $eventNamespace && $namespace != $eventNamespace)
871
+ continue;
872
+ foreach($handlers as $handler) {
873
+ phpQuery::debug("Calling event handler\n");
874
+ $event->data = $handler['data']
875
+ ? $handler['data']
876
+ : null;
877
+ $params = array_merge(array($event), $data);
878
+ $return = phpQuery::callbackRun($handler['callback'], $params);
879
+ if ($return === false) {
880
+ $event->bubbles = false;
881
+ }
882
+ }
883
+ }
884
+ }
885
+ // to bubble or not to bubble...
886
+ if (! $event->bubbles)
887
+ break;
888
+ $node = $node->parentNode;
889
+ $i++;
890
+ }
891
+ }
892
+ }
893
+ /**
894
+ * Binds a handler to one or more events (like click) for each matched element.
895
+ * Can also bind custom events.
896
+ *
897
+ * @param DOMNode|phpQueryObject|string $document
898
+ * @param unknown_type $type
899
+ * @param unknown_type $data Optional
900
+ * @param unknown_type $callback
901
+ *
902
+ * @TODO support '!' (exclusive) events
903
+ * @TODO support more than event in $type (space-separated)
904
+ * @TODO support binding to global events
905
+ */
906
+ public static function add($document, $node, $type, $data, $callback = null) {
907
+ phpQuery::debug("Binding '$type' event");
908
+ $documentID = phpQuery::getDocumentID($document);
909
+ // if (is_null($callback) && is_callable($data)) {
910
+ // $callback = $data;
911
+ // $data = null;
912
+ // }
913
+ $eventNode = self::getNode($documentID, $node);
914
+ if (! $eventNode)
915
+ $eventNode = self::setNode($documentID, $node);
916
+ if (!isset($eventNode->eventHandlers[$type]))
917
+ $eventNode->eventHandlers[$type] = array();
918
+ $eventNode->eventHandlers[$type][] = array(
919
+ 'callback' => $callback,
920
+ 'data' => $data,
921
+ );
922
+ }
923
+ /**
924
+ * Enter description here...
925
+ *
926
+ * @param DOMNode|phpQueryObject|string $document
927
+ * @param unknown_type $type
928
+ * @param unknown_type $callback
929
+ *
930
+ * @TODO namespace events
931
+ * @TODO support more than event in $type (space-separated)
932
+ */
933
+ public static function remove($document, $node, $type = null, $callback = null) {
934
+ $documentID = phpQuery::getDocumentID($document);
935
+ $eventNode = self::getNode($documentID, $node);
936
+ if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) {
937
+ if ($callback) {
938
+ foreach($eventNode->eventHandlers[$type] as $k => $handler)
939
+ if ($handler['callback'] == $callback)
940
+ unset($eventNode->eventHandlers[$type][$k]);
941
+ } else {
942
+ unset($eventNode->eventHandlers[$type]);
943
+ }
944
+ }
945
+ }
946
+ protected static function getNode($documentID, $node) {
947
+ foreach(phpQuery::$documents[$documentID]->eventsNodes as $eventNode) {
948
+ if ($node->isSameNode($eventNode))
949
+ return $eventNode;
950
+ }
951
+ }
952
+ protected static function setNode($documentID, $node) {
953
+ phpQuery::$documents[$documentID]->eventsNodes[] = $node;
954
+ return phpQuery::$documents[$documentID]->eventsNodes[
955
+ count(phpQuery::$documents[$documentID]->eventsNodes)-1
956
+ ];
957
+ }
958
+ protected static function issetGlobal($documentID, $type) {
959
+ return isset(phpQuery::$documents[$documentID])
960
+ ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal)
961
+ : false;
962
+ }
963
+ }
964
+
965
+
966
+ interface ICallbackNamed {
967
+ function hasName();
968
+ function getName();
969
+ }
970
+ /**
971
+ * Callback class introduces currying-like pattern.
972
+ *
973
+ * Example:
974
+ * function foo($param1, $param2, $param3) {
975
+ * var_dump($param1, $param2, $param3);
976
+ * }
977
+ * $fooCurried = new Callback('foo',
978
+ * 'param1 is now statically set',
979
+ * new CallbackParam, new CallbackParam
980
+ * );
981
+ * phpQuery::callbackRun($fooCurried,
982
+ * array('param2 value', 'param3 value'
983
+ * );
984
+ *
985
+ * Callback class is supported in all phpQuery methods which accepts callbacks.
986
+ *
987
+ * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures
988
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
989
+ *
990
+ * @TODO??? return fake forwarding function created via create_function
991
+ * @TODO honor paramStructure
992
+ */
993
+ class Callback
994
+ implements ICallbackNamed {
995
+ public $callback = null;
996
+ public $params = null;
997
+ protected $name;
998
+ public function __construct($callback, $param1 = null, $param2 = null,
999
+ $param3 = null) {
1000
+ $params = func_get_args();
1001
+ $params = array_slice($params, 1);
1002
+ if ($callback instanceof Callback) {
1003
+ // TODO implement recurention
1004
+ } else {
1005
+ $this->callback = $callback;
1006
+ $this->params = $params;
1007
+ }
1008
+ }
1009
+ public function getName() {
1010
+ return 'Callback: '.$this->name;
1011
+ }
1012
+ public function hasName() {
1013
+ return isset($this->name) && $this->name;
1014
+ }
1015
+ public function setName($name) {
1016
+ $this->name = $name;
1017
+ return $this;
1018
+ }
1019
+ // TODO test me
1020
+ // public function addParams() {
1021
+ // $params = func_get_args();
1022
+ // return new Callback($this->callback, $this->params+$params);
1023
+ // }
1024
+ }
1025
+ /**
1026
+ * Shorthand for new Callback(create_function(...), ...);
1027
+ *
1028
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1029
+ */
1030
+ class CallbackBody extends Callback {
1031
+ public function __construct($paramList, $code, $param1 = null, $param2 = null,
1032
+ $param3 = null) {
1033
+ $params = func_get_args();
1034
+ $params = array_slice($params, 2);
1035
+ $this->callback = create_function($paramList, $code);
1036
+ $this->params = $params;
1037
+ }
1038
+ }
1039
+ /**
1040
+ * Callback type which on execution returns reference passed during creation.
1041
+ *
1042
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1043
+ */
1044
+ class CallbackReturnReference extends Callback
1045
+ implements ICallbackNamed {
1046
+ protected $reference;
1047
+ public function __construct(&$reference, $name = null){
1048
+ $this->reference =& $reference;
1049
+ $this->callback = array($this, 'callback');
1050
+ }
1051
+ public function callback() {
1052
+ return $this->reference;
1053
+ }
1054
+ public function getName() {
1055
+ return 'Callback: '.$this->name;
1056
+ }
1057
+ public function hasName() {
1058
+ return isset($this->name) && $this->name;
1059
+ }
1060
+ }
1061
+ /**
1062
+ * Callback type which on execution returns value passed during creation.
1063
+ *
1064
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1065
+ */
1066
+ class CallbackReturnValue extends Callback
1067
+ implements ICallbackNamed {
1068
+ protected $value;
1069
+ protected $name;
1070
+ public function __construct($value, $name = null){
1071
+ $this->value =& $value;
1072
+ $this->name = $name;
1073
+ $this->callback = array($this, 'callback');
1074
+ }
1075
+ public function callback() {
1076
+ return $this->value;
1077
+ }
1078
+ public function __toString() {
1079
+ return $this->getName();
1080
+ }
1081
+ public function getName() {
1082
+ return 'Callback: '.$this->name;
1083
+ }
1084
+ public function hasName() {
1085
+ return isset($this->name) && $this->name;
1086
+ }
1087
+ }
1088
+ /**
1089
+ * CallbackParameterToReference can be used when we don't really want a callback,
1090
+ * only parameter passed to it. CallbackParameterToReference takes first
1091
+ * parameter's value and passes it to reference.
1092
+ *
1093
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1094
+ */
1095
+ class CallbackParameterToReference extends Callback {
1096
+ /**
1097
+ * @param $reference
1098
+ * @TODO implement $paramIndex;
1099
+ * param index choose which callback param will be passed to reference
1100
+ */
1101
+ public function __construct(&$reference){
1102
+ $this->callback =& $reference;
1103
+ }
1104
+ }
1105
+ //class CallbackReference extends Callback {
1106
+ // /**
1107
+ // *
1108
+ // * @param $reference
1109
+ // * @param $paramIndex
1110
+ // * @todo implement $paramIndex; param index choose which callback param will be passed to reference
1111
+ // */
1112
+ // public function __construct(&$reference, $name = null){
1113
+ // $this->callback =& $reference;
1114
+ // }
1115
+ //}
1116
+ class CallbackParam {}
1117
+
1118
+ /**
1119
+ * Class representing phpQuery objects.
1120
+ *
1121
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1122
+ * @package phpQuery
1123
+ * @method phpQueryObject clone() clone()
1124
+ * @method phpQueryObject empty() empty()
1125
+ * @method phpQueryObject next() next($selector = null)
1126
+ * @method phpQueryObject prev() prev($selector = null)
1127
+ * @property Int $length
1128
+ */
1129
+ class phpQueryObject
1130
+ implements Iterator, Countable, ArrayAccess {
1131
+ public $documentID = null;
1132
+ /**
1133
+ * DOMDocument class.
1134
+ *
1135
+ * @var DOMDocument
1136
+ */
1137
+ public $document = null;
1138
+ public $charset = null;
1139
+ /**
1140
+ *
1141
+ * @var DOMDocumentWrapper
1142
+ */
1143
+ public $documentWrapper = null;
1144
+ /**
1145
+ * XPath interface.
1146
+ *
1147
+ * @var DOMXPath
1148
+ */
1149
+ public $xpath = null;
1150
+ /**
1151
+ * Stack of selected elements.
1152
+ * @TODO refactor to ->nodes
1153
+ * @var array
1154
+ */
1155
+ public $elements = array();
1156
+ /**
1157
+ * @access private
1158
+ */
1159
+ protected $elementsBackup = array();
1160
+ /**
1161
+ * @access private
1162
+ */
1163
+ protected $previous = null;
1164
+ /**
1165
+ * @access private
1166
+ * @TODO deprecate
1167
+ */
1168
+ protected $root = array();
1169
+ /**
1170
+ * Indicated if doument is just a fragment (no <html> tag).
1171
+ *
1172
+ * Every document is realy a full document, so even documentFragments can
1173
+ * be queried against <html>, but getDocument(id)->htmlOuter() will return
1174
+ * only contents of <body>.
1175
+ *
1176
+ * @var bool
1177
+ */
1178
+ public $documentFragment = true;
1179
+ /**
1180
+ * Iterator interface helper
1181
+ * @access private
1182
+ */
1183
+ protected $elementsInterator = array();
1184
+ /**
1185
+ * Iterator interface helper
1186
+ * @access private
1187
+ */
1188
+ protected $valid = false;
1189
+ /**
1190
+ * Iterator interface helper
1191
+ * @access private
1192
+ */
1193
+ protected $current = null;
1194
+ /**
1195
+ * Enter description here...
1196
+ *
1197
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1198
+ */
1199
+ public function __construct($documentID) {
1200
+ // if ($documentID instanceof self)
1201
+ // var_dump($documentID->getDocumentID());
1202
+ $id = $documentID instanceof self
1203
+ ? $documentID->getDocumentID()
1204
+ : $documentID;
1205
+ // var_dump($id);
1206
+ if (! isset(phpQuery::$documents[$id] )) {
1207
+ // var_dump(phpQuery::$documents);
1208
+ throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first.");
1209
+ }
1210
+ $this->documentID = $id;
1211
+ $this->documentWrapper =& phpQuery::$documents[$id];
1212
+ $this->document =& $this->documentWrapper->document;
1213
+ $this->xpath =& $this->documentWrapper->xpath;
1214
+ $this->charset =& $this->documentWrapper->charset;
1215
+ $this->documentFragment =& $this->documentWrapper->isDocumentFragment;
1216
+ // TODO check $this->DOM->documentElement;
1217
+ // $this->root = $this->document->documentElement;
1218
+ $this->root =& $this->documentWrapper->root;
1219
+ // $this->toRoot();
1220
+ $this->elements = array($this->root);
1221
+ }
1222
+ /**
1223
+ *
1224
+ * @access private
1225
+ * @param $attr
1226
+ * @return unknown_type
1227
+ */
1228
+ public function __get($attr) {
1229
+ switch($attr) {
1230
+ // FIXME doesnt work at all ?
1231
+ case 'length':
1232
+ return $this->size();
1233
+ break;
1234
+ default:
1235
+ return $this->$attr;
1236
+ }
1237
+ }
1238
+ /**
1239
+ * Saves actual object to $var by reference.
1240
+ * Useful when need to break chain.
1241
+ * @param phpQueryObject $var
1242
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1243
+ */
1244
+ public function toReference(&$var) {
1245
+ return $var = $this;
1246
+ }
1247
+ public function documentFragment($state = null) {
1248
+ if ($state) {
1249
+ phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state;
1250
+ return $this;
1251
+ }
1252
+ return $this->documentFragment;
1253
+ }
1254
+ /**
1255
+ * @access private
1256
+ * @TODO documentWrapper
1257
+ */
1258
+ protected function isRoot( $node) {
1259
+ // return $node instanceof DOMDOCUMENT || $node->tagName == 'html';
1260
+ return $node instanceof DOMDOCUMENT
1261
+ || ($node instanceof DOMELEMENT && $node->tagName == 'html')
1262
+ || $this->root->isSameNode($node);
1263
+ }
1264
+ /**
1265
+ * @access private
1266
+ */
1267
+ protected function stackIsRoot() {
1268
+ return $this->size() == 1 && $this->isRoot($this->elements[0]);
1269
+ }
1270
+ /**
1271
+ * Enter description here...
1272
+ * NON JQUERY METHOD
1273
+ *
1274
+ * Watch out, it doesn't creates new instance, can be reverted with end().
1275
+ *
1276
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1277
+ */
1278
+ public function toRoot() {
1279
+ $this->elements = array($this->root);
1280
+ return $this;
1281
+ // return $this->newInstance(array($this->root));
1282
+ }
1283
+ /**
1284
+ * Saves object's DocumentID to $var by reference.
1285
+ * <code>
1286
+ * $myDocumentId;
1287
+ * phpQuery::newDocument('<div/>')
1288
+ * ->getDocumentIDRef($myDocumentId)
1289
+ * ->find('div')->...
1290
+ * </code>
1291
+ *
1292
+ * @param unknown_type $domId
1293
+ * @see phpQuery::newDocument
1294
+ * @see phpQuery::newDocumentFile
1295
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1296
+ */
1297
+ public function getDocumentIDRef(&$documentID) {
1298
+ $documentID = $this->getDocumentID();
1299
+ return $this;
1300
+ }
1301
+ /**
1302
+ * Returns object with stack set to document root.
1303
+ *
1304
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1305
+ */
1306
+ public function getDocument() {
1307
+ return phpQuery::getDocument($this->getDocumentID());
1308
+ }
1309
+ /**
1310
+ *
1311
+ * @return DOMDocument
1312
+ */
1313
+ public function getDOMDocument() {
1314
+ return $this->document;
1315
+ }
1316
+ /**
1317
+ * Get object's Document ID.
1318
+ *
1319
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1320
+ */
1321
+ public function getDocumentID() {
1322
+ return $this->documentID;
1323
+ }
1324
+ /**
1325
+ * Unloads whole document from memory.
1326
+ * CAUTION! None further operations will be possible on this document.
1327
+ * All objects refering to it will be useless.
1328
+ *
1329
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1330
+ */
1331
+ public function unloadDocument() {
1332
+ phpQuery::unloadDocuments($this->getDocumentID());
1333
+ }
1334
+ public function isHTML() {
1335
+ return $this->documentWrapper->isHTML;
1336
+ }
1337
+ public function isXHTML() {
1338
+ return $this->documentWrapper->isXHTML;
1339
+ }
1340
+ public function isXML() {
1341
+ return $this->documentWrapper->isXML;
1342
+ }
1343
+ /**
1344
+ * Enter description here...
1345
+ *
1346
+ * @link http://docs.jquery.com/Ajax/serialize
1347
+ * @return string
1348
+ */
1349
+ public function serialize() {
1350
+ return phpQuery::param($this->serializeArray());
1351
+ }
1352
+ /**
1353
+ * Enter description here...
1354
+ *
1355
+ * @link http://docs.jquery.com/Ajax/serializeArray
1356
+ * @return array
1357
+ */
1358
+ public function serializeArray($submit = null) {
1359
+ $source = $this->filter('form, input, select, textarea')
1360
+ ->find('input, select, textarea')
1361
+ ->andSelf()
1362
+ ->not('form');
1363
+ $return = array();
1364
+ // $source->dumpDie();
1365
+ foreach($source as $input) {
1366
+ $input = phpQuery::pq($input);
1367
+ if ($input->is('[disabled]'))
1368
+ continue;
1369
+ if (!$input->is('[name]'))
1370
+ continue;
1371
+ if ($input->is('[type=checkbox]') && !$input->is('[checked]'))
1372
+ continue;
1373
+ // jquery diff
1374
+ if ($submit && $input->is('[type=submit]')) {
1375
+ if ($submit instanceof DOMELEMENT && ! $input->elements[0]->isSameNode($submit))
1376
+ continue;
1377
+ else if (is_string($submit) && $input->attr('name') != $submit)
1378
+ continue;
1379
+ }
1380
+ $return[] = array(
1381
+ 'name' => $input->attr('name'),
1382
+ 'value' => $input->val(),
1383
+ );
1384
+ }
1385
+ return $return;
1386
+ }
1387
+ /**
1388
+ * @access private
1389
+ */
1390
+ protected function debug($in) {
1391
+ if (! phpQuery::$debug )
1392
+ return;
1393
+ print('<pre>');
1394
+ print_r($in);
1395
+ // file debug
1396
+ // file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
1397
+ // quite handy debug trace
1398
+ // if ( is_array($in))
1399
+ // print_r(array_slice(debug_backtrace(), 3));
1400
+ print("</pre>\n");
1401
+ }
1402
+ /**
1403
+ * @access private
1404
+ */
1405
+ protected function isRegexp($pattern) {
1406
+ return in_array(
1407
+ $pattern[ mb_strlen($pattern)-1 ],
1408
+ array('^','*','$')
1409
+ );
1410
+ }
1411
+ /**
1412
+ * Determines if $char is really a char.
1413
+ *
1414
+ * @param string $char
1415
+ * @return bool
1416
+ * @todo rewrite me to charcode range ! ;)
1417
+ * @access private
1418
+ */
1419
+ protected function isChar($char) {
1420
+ return extension_loaded('mbstring') && phpQuery::$mbstringSupport
1421
+ ? mb_eregi('\w', $char)
1422
+ : preg_match('@\w@', $char);
1423
+ }
1424
+ /**
1425
+ * @access private
1426
+ */
1427
+ protected function parseSelector($query) {
1428
+ // clean spaces
1429
+ // TODO include this inside parsing ?
1430
+ $query = trim(
1431
+ preg_replace('@\s+@', ' ',
1432
+ preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query)
1433
+ )
1434
+ );
1435
+ $queries = array(array());
1436
+ if (! $query)
1437
+ return $queries;
1438
+ $return =& $queries[0];
1439
+ $specialChars = array('>',' ');
1440
+ // $specialCharsMapping = array('/' => '>');
1441
+ $specialCharsMapping = array();
1442
+ $strlen = mb_strlen($query);
1443
+ $classChars = array('.', '-');
1444
+ $pseudoChars = array('-');
1445
+ $tagChars = array('*', '|', '-');
1446
+ // split multibyte string
1447
+ // http://code.google.com/p/phpquery/issues/detail?id=76
1448
+ $_query = array();
1449
+ for ($i=0; $i<$strlen; $i++)
1450
+ $_query[] = mb_substr($query, $i, 1);
1451
+ $query = $_query;
1452
+ // it works, but i dont like it...
1453
+ $i = 0;
1454
+ while( $i < $strlen) {
1455
+ $c = $query[$i];
1456
+ $tmp = '';
1457
+ // TAG
1458
+ if ($this->isChar($c) || in_array($c, $tagChars)) {
1459
+ while(isset($query[$i])
1460
+ && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))) {
1461
+ $tmp .= $query[$i];
1462
+ $i++;
1463
+ }
1464
+ $return[] = $tmp;
1465
+ // IDs
1466
+ } else if ( $c == '#') {
1467
+ $i++;
1468
+ while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) {
1469
+ $tmp .= $query[$i];
1470
+ $i++;
1471
+ }
1472
+ $return[] = '#'.$tmp;
1473
+ // SPECIAL CHARS
1474
+ } else if (in_array($c, $specialChars)) {
1475
+ $return[] = $c;
1476
+ $i++;
1477
+ // MAPPED SPECIAL MULTICHARS
1478
+ // } else if ( $c.$query[$i+1] == '//') {
1479
+ // $return[] = ' ';
1480
+ // $i = $i+2;
1481
+ // MAPPED SPECIAL CHARS
1482
+ } else if ( isset($specialCharsMapping[$c])) {
1483
+ $return[] = $specialCharsMapping[$c];
1484
+ $i++;
1485
+ // COMMA
1486
+ } else if ( $c == ',') {
1487
+ $queries[] = array();
1488
+ $return =& $queries[ count($queries)-1 ];
1489
+ $i++;
1490
+ while( isset($query[$i]) && $query[$i] == ' ')
1491
+ $i++;
1492
+ // CLASSES
1493
+ } else if ($c == '.') {
1494
+ while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) {
1495
+ $tmp .= $query[$i];
1496
+ $i++;
1497
+ }
1498
+ $return[] = $tmp;
1499
+ // ~ General Sibling Selector
1500
+ } else if ($c == '~') {
1501
+ $spaceAllowed = true;
1502
+ $tmp .= $query[$i++];
1503
+ while( isset($query[$i])
1504
+ && ($this->isChar($query[$i])
1505
+ || in_array($query[$i], $classChars)
1506
+ || $query[$i] == '*'
1507
+ || ($query[$i] == ' ' && $spaceAllowed)
1508
+ )) {
1509
+ if ($query[$i] != ' ')
1510
+ $spaceAllowed = false;
1511
+ $tmp .= $query[$i];
1512
+ $i++;
1513
+ }
1514
+ $return[] = $tmp;
1515
+ // + Adjacent sibling selectors
1516
+ } else if ($c == '+') {
1517
+ $spaceAllowed = true;
1518
+ $tmp .= $query[$i++];
1519
+ while( isset($query[$i])
1520
+ && ($this->isChar($query[$i])
1521
+ || in_array($query[$i], $classChars)
1522
+ || $query[$i] == '*'
1523
+ || ($spaceAllowed && $query[$i] == ' ')
1524
+ )) {
1525
+ if ($query[$i] != ' ')
1526
+ $spaceAllowed = false;
1527
+ $tmp .= $query[$i];
1528
+ $i++;
1529
+ }
1530
+ $return[] = $tmp;
1531
+ // ATTRS
1532
+ } else if ($c == '[') {
1533
+ $stack = 1;
1534
+ $tmp .= $c;
1535
+ while( isset($query[++$i])) {
1536
+ $tmp .= $query[$i];
1537
+ if ( $query[$i] == '[') {
1538
+ $stack++;
1539
+ } else if ( $query[$i] == ']') {
1540
+ $stack--;
1541
+ if (! $stack )
1542
+ break;
1543
+ }
1544
+ }
1545
+ $return[] = $tmp;
1546
+ $i++;
1547
+ // PSEUDO CLASSES
1548
+ } else if ($c == ':') {
1549
+ $stack = 1;
1550
+ $tmp .= $query[$i++];
1551
+ while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) {
1552
+ $tmp .= $query[$i];
1553
+ $i++;
1554
+ }
1555
+ // with arguments ?
1556
+ if ( isset($query[$i]) && $query[$i] == '(') {
1557
+ $tmp .= $query[$i];
1558
+ $stack = 1;
1559
+ while( isset($query[++$i])) {
1560
+ $tmp .= $query[$i];
1561
+ if ( $query[$i] == '(') {
1562
+ $stack++;
1563
+ } else if ( $query[$i] == ')') {
1564
+ $stack--;
1565
+ if (! $stack )
1566
+ break;
1567
+ }
1568
+ }
1569
+ $return[] = $tmp;
1570
+ $i++;
1571
+ } else {
1572
+ $return[] = $tmp;
1573
+ }
1574
+ } else {
1575
+ $i++;
1576
+ }
1577
+ }
1578
+ foreach($queries as $k => $q) {
1579
+ if (isset($q[0])) {
1580
+ if (isset($q[0][0]) && $q[0][0] == ':')
1581
+ array_unshift($queries[$k], '*');
1582
+ if ($q[0] != '>')
1583
+ array_unshift($queries[$k], ' ');
1584
+ }
1585
+ }
1586
+ return $queries;
1587
+ }
1588
+ /**
1589
+ * Return matched DOM nodes.
1590
+ *
1591
+ * @param int $index
1592
+ * @return array|DOMElement Single DOMElement or array of DOMElement.
1593
+ */
1594
+ public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1595
+ $return = isset($index)
1596
+ ? (isset($this->elements[$index]) ? $this->elements[$index] : null)
1597
+ : $this->elements;
1598
+ // pass thou callbacks
1599
+ $args = func_get_args();
1600
+ $args = array_slice($args, 1);
1601
+ foreach($args as $callback) {
1602
+ if (is_array($return))
1603
+ foreach($return as $k => $v)
1604
+ $return[$k] = phpQuery::callbackRun($callback, array($v));
1605
+ else
1606
+ $return = phpQuery::callbackRun($callback, array($return));
1607
+ }
1608
+ return $return;
1609
+ }
1610
+ /**
1611
+ * Return matched DOM nodes.
1612
+ * jQuery difference.
1613
+ *
1614
+ * @param int $index
1615
+ * @return array|string Returns string if $index != null
1616
+ * @todo implement callbacks
1617
+ * @todo return only arrays ?
1618
+ * @todo maybe other name...
1619
+ */
1620
+ public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1621
+ if ($index)
1622
+ $return = $this->eq($index)->text();
1623
+ else {
1624
+ $return = array();
1625
+ for($i = 0; $i < $this->size(); $i++) {
1626
+ $return[] = $this->eq($i)->text();
1627
+ }
1628
+ }
1629
+ // pass thou callbacks
1630
+ $args = func_get_args();
1631
+ $args = array_slice($args, 1);
1632
+ foreach($args as $callback) {
1633
+ $return = phpQuery::callbackRun($callback, array($return));
1634
+ }
1635
+ return $return;
1636
+ }
1637
+ /**
1638
+ * Return matched DOM nodes.
1639
+ * jQuery difference.
1640
+ *
1641
+ * @param int $index
1642
+ * @return array|string Returns string if $index != null
1643
+ * @todo implement callbacks
1644
+ * @todo return only arrays ?
1645
+ * @todo maybe other name...
1646
+ */
1647
+ public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1648
+ if ($index)
1649
+ $return = $this->eq($index)->text();
1650
+ else {
1651
+ $return = array();
1652
+ for($i = 0; $i < $this->size(); $i++) {
1653
+ $return[] = $this->eq($i)->text();
1654
+ }
1655
+ // pass thou callbacks
1656
+ $args = func_get_args();
1657
+ $args = array_slice($args, 1);
1658
+ }
1659
+ foreach($args as $callback) {
1660
+ if (is_array($return))
1661
+ foreach($return as $k => $v)
1662
+ $return[$k] = phpQuery::callbackRun($callback, array($v));
1663
+ else
1664
+ $return = phpQuery::callbackRun($callback, array($return));
1665
+ }
1666
+ return $return;
1667
+ }
1668
+ /**
1669
+ * Returns new instance of actual class.
1670
+ *
1671
+ * @param array $newStack Optional. Will replace old stack with new and move old one to history.c
1672
+ */
1673
+ public function newInstance($newStack = null) {
1674
+ $class = get_class($this);
1675
+ // support inheritance by passing old object to overloaded constructor
1676
+ $new = $class != 'phpQuery'
1677
+ ? new $class($this, $this->getDocumentID())
1678
+ : new phpQueryObject($this->getDocumentID());
1679
+ $new->previous = $this;
1680
+ if (is_null($newStack)) {
1681
+ $new->elements = $this->elements;
1682
+ if ($this->elementsBackup)
1683
+ $this->elements = $this->elementsBackup;
1684
+ } else if (is_string($newStack)) {
1685
+ $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack();
1686
+ } else {
1687
+ $new->elements = $newStack;
1688
+ }
1689
+ return $new;
1690
+ }
1691
+ /**
1692
+ * Enter description here...
1693
+ *
1694
+ * In the future, when PHP will support XLS 2.0, then we would do that this way:
1695
+ * contains(tokenize(@class, '\s'), "something")
1696
+ * @param unknown_type $class
1697
+ * @param unknown_type $node
1698
+ * @return boolean
1699
+ * @access private
1700
+ */
1701
+ protected function matchClasses($class, $node) {
1702
+ // multi-class
1703
+ if ( mb_strpos($class, '.', 1)) {
1704
+ $classes = explode('.', substr($class, 1));
1705
+ $classesCount = count( $classes );
1706
+ $nodeClasses = explode(' ', $node->getAttribute('class') );
1707
+ $nodeClassesCount = count( $nodeClasses );
1708
+ if ( $classesCount > $nodeClassesCount )
1709
+ return false;
1710
+ $diff = count(
1711
+ array_diff(
1712
+ $classes,
1713
+ $nodeClasses
1714
+ )
1715
+ );
1716
+ if (! $diff )
1717
+ return true;
1718
+ // single-class
1719
+ } else {
1720
+ return in_array(
1721
+ // strip leading dot from class name
1722
+ substr($class, 1),
1723
+ // get classes for element as array
1724
+ explode(' ', $node->getAttribute('class') )
1725
+ );
1726
+ }
1727
+ }
1728
+ /**
1729
+ * @access private
1730
+ */
1731
+ protected function runQuery($XQuery, $selector = null, $compare = null) {
1732
+ if ($compare && ! method_exists($this, $compare))
1733
+ return false;
1734
+ $stack = array();
1735
+ if (! $this->elements)
1736
+ $this->debug('Stack empty, skipping...');
1737
+ // var_dump($this->elements[0]->nodeType);
1738
+ // element, document
1739
+ foreach($this->stack(array(1, 9, 13)) as $k => $stackNode) {
1740
+ $detachAfter = false;
1741
+ // to work on detached nodes we need temporary place them somewhere
1742
+ // thats because context xpath queries sucks ;]
1743
+ $testNode = $stackNode;
1744
+ while ($testNode) {
1745
+ if (! $testNode->parentNode && ! $this->isRoot($testNode)) {
1746
+ $this->root->appendChild($testNode);
1747
+ $detachAfter = $testNode;
1748
+ break;
1749
+ }
1750
+ $testNode = isset($testNode->parentNode)
1751
+ ? $testNode->parentNode
1752
+ : null;
1753
+ }
1754
+ // XXX tmp ?
1755
+ $xpath = $this->documentWrapper->isXHTML
1756
+ ? $this->getNodeXpath($stackNode, 'html')
1757
+ : $this->getNodeXpath($stackNode);
1758
+ // FIXME pseudoclasses-only query, support XML
1759
+ $query = $XQuery == '//' && $xpath == '/html[1]'
1760
+ ? '//*'
1761
+ : $xpath.$XQuery;
1762
+ $this->debug("XPATH: {$query}");
1763
+ // run query, get elements
1764
+ $nodes = $this->xpath->query($query);
1765
+ $this->debug("QUERY FETCHED");
1766
+ if (! $nodes->length )
1767
+ $this->debug('Nothing found');
1768
+ $debug = array();
1769
+ foreach($nodes as $node) {
1770
+ $matched = false;
1771
+ if ( $compare) {
1772
+ phpQuery::$debug ?
1773
+ $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()")
1774
+ : null;
1775
+ $phpQueryDebug = phpQuery::$debug;
1776
+ phpQuery::$debug = false;
1777
+ // TODO ??? use phpQuery::callbackRun()
1778
+ if (call_user_func_array(array($this, $compare), array($selector, $node)))
1779
+ $matched = true;
1780
+ phpQuery::$debug = $phpQueryDebug;
1781
+ } else {
1782
+ $matched = true;
1783
+ }
1784
+ if ( $matched) {
1785
+ if (phpQuery::$debug)
1786
+ $debug[] = $this->whois( $node );
1787
+ $stack[] = $node;
1788
+ }
1789
+ }
1790
+ if (phpQuery::$debug) {
1791
+ $this->debug("Matched ".count($debug).": ".implode(', ', $debug));
1792
+ }
1793
+ if ($detachAfter)
1794
+ $this->root->removeChild($detachAfter);
1795
+ }
1796
+ $this->elements = $stack;
1797
+ }
1798
+ /**
1799
+ * Enter description here...
1800
+ *
1801
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1802
+ */
1803
+ public function find($selectors, $context = null, $noHistory = false) {
1804
+ if (!$noHistory)
1805
+ // backup last stack /for end()/
1806
+ $this->elementsBackup = $this->elements;
1807
+ // allow to define context
1808
+ // TODO combine code below with phpQuery::pq() context guessing code
1809
+ // as generic function
1810
+ if ($context) {
1811
+ if (! is_array($context) && $context instanceof DOMELEMENT)
1812
+ $this->elements = array($context);
1813
+ else if (is_array($context)) {
1814
+ $this->elements = array();
1815
+ foreach ($context as $c)
1816
+ if ($c instanceof DOMELEMENT)
1817
+ $this->elements[] = $c;
1818
+ } else if ( $context instanceof self )
1819
+ $this->elements = $context->elements;
1820
+ }
1821
+ $queries = $this->parseSelector($selectors);
1822
+ $this->debug(array('FIND', $selectors, $queries));
1823
+ $XQuery = '';
1824
+ // remember stack state because of multi-queries
1825
+ $oldStack = $this->elements;
1826
+ // here we will be keeping found elements
1827
+ $stack = array();
1828
+ foreach($queries as $selector) {
1829
+ $this->elements = $oldStack;
1830
+ $delimiterBefore = false;
1831
+ foreach($selector as $s) {
1832
+ // TAG
1833
+ $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport
1834
+ ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*'
1835
+ : preg_match('@^[\w|\||-]+$@', $s) || $s == '*';
1836
+ if ($isTag) {
1837
+ if ($this->isXML()) {
1838
+ // namespace support
1839
+ if (mb_strpos($s, '|') !== false) {
1840
+ $ns = $tag = null;
1841
+ list($ns, $tag) = explode('|', $s);
1842
+ $XQuery .= "$ns:$tag";
1843
+ } else if ($s == '*') {
1844
+ $XQuery .= "*";
1845
+ } else {
1846
+ $XQuery .= "*[local-name()='$s']";
1847
+ }
1848
+ } else {
1849
+ $XQuery .= $s;
1850
+ }
1851
+ // ID
1852
+ } else if ($s[0] == '#') {
1853
+ if ($delimiterBefore)
1854
+ $XQuery .= '*';
1855
+ $XQuery .= "[@id='".substr($s, 1)."']";
1856
+ // ATTRIBUTES
1857
+ } else if ($s[0] == '[') {
1858
+ if ($delimiterBefore)
1859
+ $XQuery .= '*';
1860
+ // strip side brackets
1861
+ $attr = trim($s, '][');
1862
+ $execute = false;
1863
+ // attr with specifed value
1864
+ if (mb_strpos($s, '=')) {
1865
+ $value = null;
1866
+ list($attr, $value) = explode('=', $attr);
1867
+ $value = trim($value, "'\"");
1868
+ if ($this->isRegexp($attr)) {
1869
+ // cut regexp character
1870
+ $attr = substr($attr, 0, -1);
1871
+ $execute = true;
1872
+ $XQuery .= "[@{$attr}]";
1873
+ } else {
1874
+ $XQuery .= "[@{$attr}='{$value}']";
1875
+ }
1876
+ // attr without specified value
1877
+ } else {
1878
+ $XQuery .= "[@{$attr}]";
1879
+ }
1880
+ if ($execute) {
1881
+ $this->runQuery($XQuery, $s, 'is');
1882
+ $XQuery = '';
1883
+ if (! $this->length())
1884
+ break;
1885
+ }
1886
+ // CLASSES
1887
+ } else if ($s[0] == '.') {
1888
+ // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]");
1889
+ // thx wizDom ;)
1890
+ if ($delimiterBefore)
1891
+ $XQuery .= '*';
1892
+ $XQuery .= '[@class]';
1893
+ $this->runQuery($XQuery, $s, 'matchClasses');
1894
+ $XQuery = '';
1895
+ if (! $this->length() )
1896
+ break;
1897
+ // ~ General Sibling Selector
1898
+ } else if ($s[0] == '~') {
1899
+ $this->runQuery($XQuery);
1900
+ $XQuery = '';
1901
+ $this->elements = $this
1902
+ ->siblings(
1903
+ substr($s, 1)
1904
+ )->elements;
1905
+ if (! $this->length() )
1906
+ break;
1907
+ // + Adjacent sibling selectors
1908
+ } else if ($s[0] == '+') {
1909
+ // TODO /following-sibling::
1910
+ $this->runQuery($XQuery);
1911
+ $XQuery = '';
1912
+ $subSelector = substr($s, 1);
1913
+ $subElements = $this->elements;
1914
+ $this->elements = array();
1915
+ foreach($subElements as $node) {
1916
+ // search first DOMElement sibling
1917
+ $test = $node->nextSibling;
1918
+ while($test && ! ($test instanceof DOMELEMENT))
1919
+ $test = $test->nextSibling;
1920
+ if ($test && $this->is($subSelector, $test))
1921
+ $this->elements[] = $test;
1922
+ }
1923
+ if (! $this->length() )
1924
+ break;
1925
+ // PSEUDO CLASSES
1926
+ } else if ($s[0] == ':') {
1927
+ // TODO optimization for :first :last
1928
+ if ($XQuery) {
1929
+ $this->runQuery($XQuery);
1930
+ $XQuery = '';
1931
+ }
1932
+ if (! $this->length())
1933
+ break;
1934
+ $this->pseudoClasses($s);
1935
+ if (! $this->length())
1936
+ break;
1937
+ // DIRECT DESCENDANDS
1938
+ } else if ($s == '>') {
1939
+ $XQuery .= '/';
1940
+ $delimiterBefore = 2;
1941
+ // ALL DESCENDANDS
1942
+ } else if ($s == ' ') {
1943
+ $XQuery .= '//';
1944
+ $delimiterBefore = 2;
1945
+ // ERRORS
1946
+ } else {
1947
+ phpQuery::debug("Unrecognized token '$s'");
1948
+ }
1949
+ $delimiterBefore = $delimiterBefore === 2;
1950
+ }
1951
+ // run query if any
1952
+ if ($XQuery && $XQuery != '//') {
1953
+ $this->runQuery($XQuery);
1954
+ $XQuery = '';
1955
+ }
1956
+ foreach($this->elements as $node)
1957
+ if (! $this->elementsContainsNode($node, $stack))
1958
+ $stack[] = $node;
1959
+ }
1960
+ $this->elements = $stack;
1961
+ return $this->newInstance();
1962
+ }
1963
+ /**
1964
+ * @todo create API for classes with pseudoselectors
1965
+ * @access private
1966
+ */
1967
+ protected function pseudoClasses($class) {
1968
+ // TODO clean args parsing ?
1969
+ $class = ltrim($class, ':');
1970
+ $haveArgs = mb_strpos($class, '(');
1971
+ if ($haveArgs !== false) {
1972
+ $args = substr($class, $haveArgs+1, -1);
1973
+ $class = substr($class, 0, $haveArgs);
1974
+ }
1975
+ switch($class) {
1976
+ case 'even':
1977
+ case 'odd':
1978
+ $stack = array();
1979
+ foreach($this->elements as $i => $node) {
1980
+ if ($class == 'even' && ($i%2) == 0)
1981
+ $stack[] = $node;
1982
+ else if ( $class == 'odd' && $i % 2 )
1983
+ $stack[] = $node;
1984
+ }
1985
+ $this->elements = $stack;
1986
+ break;
1987
+ case 'eq':
1988
+ $k = intval($args);
1989
+ $this->elements = isset( $this->elements[$k] )
1990
+ ? array( $this->elements[$k] )
1991
+ : array();
1992
+ break;
1993
+ case 'gt':
1994
+ $this->elements = array_slice($this->elements, $args+1);
1995
+ break;
1996
+ case 'lt':
1997
+ $this->elements = array_slice($this->elements, 0, $args+1);
1998
+ break;
1999
+ case 'first':
2000
+ if (isset($this->elements[0]))
2001
+ $this->elements = array($this->elements[0]);
2002
+ break;
2003
+ case 'last':
2004
+ if ($this->elements)
2005
+ $this->elements = array($this->elements[count($this->elements)-1]);
2006
+ break;
2007
+ /*case 'parent':
2008
+ $stack = array();
2009
+ foreach($this->elements as $node) {
2010
+ if ( $node->childNodes->length )
2011
+ $stack[] = $node;
2012
+ }
2013
+ $this->elements = $stack;
2014
+ break;*/
2015
+ case 'contains':
2016
+ $text = trim($args, "\"'");
2017
+ $stack = array();
2018
+ foreach($this->elements as $node) {
2019
+ if (mb_stripos($node->textContent, $text) === false)
2020
+ continue;
2021
+ $stack[] = $node;
2022
+ }
2023
+ $this->elements = $stack;
2024
+ break;
2025
+ case 'not':
2026
+ $selector = self::unQuote($args);
2027
+ $this->elements = $this->not($selector)->stack();
2028
+ break;
2029
+ case 'slice':
2030
+ // TODO jQuery difference ?
2031
+ $args = explode(',',
2032
+ str_replace(', ', ',', trim($args, "\"'"))
2033
+ );
2034
+ $start = $args[0];
2035
+ $end = isset($args[1])
2036
+ ? $args[1]
2037
+ : null;
2038
+ if ($end > 0)
2039
+ $end = $end-$start;
2040
+ $this->elements = array_slice($this->elements, $start, $end);
2041
+ break;
2042
+ case 'has':
2043
+ $selector = trim($args, "\"'");
2044
+ $stack = array();
2045
+ foreach($this->stack(1) as $el) {
2046
+ if ($this->find($selector, $el, true)->length)
2047
+ $stack[] = $el;
2048
+ }
2049
+ $this->elements = $stack;
2050
+ break;
2051
+ case 'submit':
2052
+ case 'reset':
2053
+ $this->elements = phpQuery::merge(
2054
+ $this->map(array($this, 'is'),
2055
+ "input[type=$class]", new CallbackParam()
2056
+ ),
2057
+ $this->map(array($this, 'is'),
2058
+ "button[type=$class]", new CallbackParam()
2059
+ )
2060
+ );
2061
+ break;
2062
+ // $stack = array();
2063
+ // foreach($this->elements as $node)
2064
+ // if ($node->is('input[type=submit]') || $node->is('button[type=submit]'))
2065
+ // $stack[] = $el;
2066
+ // $this->elements = $stack;
2067
+ case 'input':
2068
+ $this->elements = $this->map(
2069
+ array($this, 'is'),
2070
+ 'input', new CallbackParam()
2071
+ )->elements;
2072
+ break;
2073
+ case 'password':
2074
+ case 'checkbox':
2075
+ case 'radio':
2076
+ case 'hidden':
2077
+ case 'image':
2078
+ case 'file':
2079
+ $this->elements = $this->map(
2080
+ array($this, 'is'),
2081
+ "input[type=$class]", new CallbackParam()
2082
+ )->elements;
2083
+ break;
2084
+ case 'parent':
2085
+ $this->elements = $this->map(
2086
+ create_function('$node', '
2087
+ return $node instanceof DOMELEMENT && $node->childNodes->length
2088
+ ? $node : null;')
2089
+ )->elements;
2090
+ break;
2091
+ case 'empty':
2092
+ $this->elements = $this->map(
2093
+ create_function('$node', '
2094
+ return $node instanceof DOMELEMENT && $node->childNodes->length
2095
+ ? null : $node;')
2096
+ )->elements;
2097
+ break;
2098
+ case 'disabled':
2099
+ case 'selected':
2100
+ case 'checked':
2101
+ $this->elements = $this->map(
2102
+ array($this, 'is'),
2103
+ "[$class]", new CallbackParam()
2104
+ )->elements;
2105
+ break;
2106
+ case 'enabled':
2107
+ $this->elements = $this->map(
2108
+ create_function('$node', '
2109
+ return pq($node)->not(":disabled") ? $node : null;')
2110
+ )->elements;
2111
+ break;
2112
+ case 'header':
2113
+ $this->elements = $this->map(
2114
+ create_function('$node',
2115
+ '$isHeader = isset($node->tagName) && in_array($node->tagName, array(
2116
+ "h1", "h2", "h3", "h4", "h5", "h6", "h7"
2117
+ ));
2118
+ return $isHeader
2119
+ ? $node
2120
+ : null;')
2121
+ )->elements;
2122
+ // $this->elements = $this->map(
2123
+ // create_function('$node', '$node = pq($node);
2124
+ // return $node->is("h1")
2125
+ // || $node->is("h2")
2126
+ // || $node->is("h3")
2127
+ // || $node->is("h4")
2128
+ // || $node->is("h5")
2129
+ // || $node->is("h6")
2130
+ // || $node->is("h7")
2131
+ // ? $node
2132
+ // : null;')
2133
+ // )->elements;
2134
+ break;
2135
+ case 'only-child':
2136
+ $this->elements = $this->map(
2137
+ create_function('$node',
2138
+ 'return pq($node)->siblings()->size() == 0 ? $node : null;')
2139
+ )->elements;
2140
+ break;
2141
+ case 'first-child':
2142
+ $this->elements = $this->map(
2143
+ create_function('$node', 'return pq($node)->prevAll()->size() == 0 ? $node : null;')
2144
+ )->elements;
2145
+ break;
2146
+ case 'last-child':
2147
+ $this->elements = $this->map(
2148
+ create_function('$node', 'return pq($node)->nextAll()->size() == 0 ? $node : null;')
2149
+ )->elements;
2150
+ break;
2151
+ case 'nth-child':
2152
+ $param = trim($args, "\"'");
2153
+ if (! $param)
2154
+ break;
2155
+ // nth-child(n+b) to nth-child(1n+b)
2156
+ if ($param{0} == 'n')
2157
+ $param = '1'.$param;
2158
+ // :nth-child(index/even/odd/equation)
2159
+ if ($param == 'even' || $param == 'odd')
2160
+ $mapped = $this->map(
2161
+ create_function('$node, $param',
2162
+ '$index = pq($node)->prevAll()->size()+1;
2163
+ if ($param == "even" && ($index%2) == 0)
2164
+ return $node;
2165
+ else if ($param == "odd" && $index%2 == 1)
2166
+ return $node;
2167
+ else
2168
+ return null;'),
2169
+ new CallbackParam(), $param
2170
+ );
2171
+ else if (mb_strlen($param) > 1 && $param{1} == 'n')
2172
+ // an+b
2173
+ $mapped = $this->map(
2174
+ create_function('$node, $param',
2175
+ '$prevs = pq($node)->prevAll()->size();
2176
+ $index = 1+$prevs;
2177
+ $b = mb_strlen($param) > 3
2178
+ ? $param{3}
2179
+ : 0;
2180
+ $a = $param{0};
2181
+ if ($b && $param{2} == "-")
2182
+ $b = -$b;
2183
+ if ($a > 0) {
2184
+ return ($index-$b)%$a == 0
2185
+ ? $node
2186
+ : null;
2187
+ phpQuery::debug($a."*".floor($index/$a)."+$b-1 == ".($a*floor($index/$a)+$b-1)." ?= $prevs");
2188
+ return $a*floor($index/$a)+$b-1 == $prevs
2189
+ ? $node
2190
+ : null;
2191
+ } else if ($a == 0)
2192
+ return $index == $b
2193
+ ? $node
2194
+ : null;
2195
+ else
2196
+ // negative value
2197
+ return $index <= $b
2198
+ ? $node
2199
+ : null;
2200
+ // if (! $b)
2201
+ // return $index%$a == 0
2202
+ // ? $node
2203
+ // : null;
2204
+ // else
2205
+ // return ($index-$b)%$a == 0
2206
+ // ? $node
2207
+ // : null;
2208
+ '),
2209
+ new CallbackParam(), $param
2210
+ );
2211
+ else
2212
+ // index
2213
+ $mapped = $this->map(
2214
+ create_function('$node, $index',
2215
+ '$prevs = pq($node)->prevAll()->size();
2216
+ if ($prevs && $prevs == $index-1)
2217
+ return $node;
2218
+ else if (! $prevs && $index == 1)
2219
+ return $node;
2220
+ else
2221
+ return null;'),
2222
+ new CallbackParam(), $param
2223
+ );
2224
+ $this->elements = $mapped->elements;
2225
+ break;
2226
+ default:
2227
+ $this->debug("Unknown pseudoclass '{$class}', skipping...");
2228
+ }
2229
+ }
2230
+ /**
2231
+ * @access private
2232
+ */
2233
+ protected function __pseudoClassParam($paramsString) {
2234
+ // TODO;
2235
+ }
2236
+ /**
2237
+ * Enter description here...
2238
+ *
2239
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2240
+ */
2241
+ public function is($selector, $nodes = null) {
2242
+ phpQuery::debug(array("Is:", $selector));
2243
+ if (! $selector)
2244
+ return false;
2245
+ $oldStack = $this->elements;
2246
+ $returnArray = false;
2247
+ if ($nodes && is_array($nodes)) {
2248
+ $this->elements = $nodes;
2249
+ } else if ($nodes)
2250
+ $this->elements = array($nodes);
2251
+ $this->filter($selector, true);
2252
+ $stack = $this->elements;
2253
+ $this->elements = $oldStack;
2254
+ if ($nodes)
2255
+ return $stack ? $stack : null;
2256
+ return (bool)count($stack);
2257
+ }
2258
+ /**
2259
+ * Enter description here...
2260
+ * jQuery difference.
2261
+ *
2262
+ * Callback:
2263
+ * - $index int
2264
+ * - $node DOMNode
2265
+ *
2266
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2267
+ * @link http://docs.jquery.com/Traversing/filter
2268
+ */
2269
+ public function filterCallback($callback, $_skipHistory = false) {
2270
+ if (! $_skipHistory) {
2271
+ $this->elementsBackup = $this->elements;
2272
+ $this->debug("Filtering by callback");
2273
+ }
2274
+ $newStack = array();
2275
+ foreach($this->elements as $index => $node) {
2276
+ $result = phpQuery::callbackRun($callback, array($index, $node));
2277
+ if (is_null($result) || (! is_null($result) && $result))
2278
+ $newStack[] = $node;
2279
+ }
2280
+ $this->elements = $newStack;
2281
+ return $_skipHistory
2282
+ ? $this
2283
+ : $this->newInstance();
2284
+ }
2285
+ /**
2286
+ * Enter description here...
2287
+ *
2288
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2289
+ * @link http://docs.jquery.com/Traversing/filter
2290
+ */
2291
+ public function filter($selectors, $_skipHistory = false) {
2292
+ if ($selectors instanceof Callback OR $selectors instanceof Closure)
2293
+ return $this->filterCallback($selectors, $_skipHistory);
2294
+ if (! $_skipHistory)
2295
+ $this->elementsBackup = $this->elements;
2296
+ $notSimpleSelector = array(' ', '>', '~', '+', '/');
2297
+ if (! is_array($selectors))
2298
+ $selectors = $this->parseSelector($selectors);
2299
+ if (! $_skipHistory)
2300
+ $this->debug(array("Filtering:", $selectors));
2301
+ $finalStack = array();
2302
+ foreach($selectors as $selector) {
2303
+ $stack = array();
2304
+ if (! $selector)
2305
+ break;
2306
+ // avoid first space or /
2307
+ if (in_array($selector[0], $notSimpleSelector))
2308
+ $selector = array_slice($selector, 1);
2309
+ // PER NODE selector chunks
2310
+ foreach($this->stack() as $node) {
2311
+ $break = false;
2312
+ foreach($selector as $s) {
2313
+ if (!($node instanceof DOMELEMENT)) {
2314
+ // all besides DOMElement
2315
+ if ( $s[0] == '[') {
2316
+ $attr = trim($s, '[]');
2317
+ if ( mb_strpos($attr, '=')) {
2318
+ list( $attr, $val ) = explode('=', $attr);
2319
+ if ($attr == 'nodeType' && $node->nodeType != $val)
2320
+ $break = true;
2321
+ }
2322
+ } else
2323
+ $break = true;
2324
+ } else {
2325
+ // DOMElement only
2326
+ // ID
2327
+ if ( $s[0] == '#') {
2328
+ if ( $node->getAttribute('id') != substr($s, 1) )
2329
+ $break = true;
2330
+ // CLASSES
2331
+ } else if ( $s[0] == '.') {
2332
+ if (! $this->matchClasses( $s, $node ) )
2333
+ $break = true;
2334
+ // ATTRS
2335
+ } else if ( $s[0] == '[') {
2336
+ // strip side brackets
2337
+ $attr = trim($s, '[]');
2338
+ if (mb_strpos($attr, '=')) {
2339
+ list($attr, $val) = explode('=', $attr);
2340
+ $val = self::unQuote($val);
2341
+ if ($attr == 'nodeType') {
2342
+ if ($val != $node->nodeType)
2343
+ $break = true;
2344
+ } else if ($this->isRegexp($attr)) {
2345
+ $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport
2346
+ ? quotemeta(trim($val, '"\''))
2347
+ : preg_quote(trim($val, '"\''), '@');
2348
+ // switch last character
2349
+ switch( substr($attr, -1)) {
2350
+ // quotemeta used insted of preg_quote
2351
+ // http://code.google.com/p/phpquery/issues/detail?id=76
2352
+ case '^':
2353
+ $pattern = '^'.$val;
2354
+ break;
2355
+ case '*':
2356
+ $pattern = '.*'.$val.'.*';
2357
+ break;
2358
+ case '$':
2359
+ $pattern = '.*'.$val.'$';
2360
+ break;
2361
+ }
2362
+ // cut last character
2363
+ $attr = substr($attr, 0, -1);
2364
+ $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport
2365
+ ? mb_ereg_match($pattern, $node->getAttribute($attr))
2366
+ : preg_match("@{$pattern}@", $node->getAttribute($attr));
2367
+ if (! $isMatch)
2368
+ $break = true;
2369
+ } else if ($node->getAttribute($attr) != $val)
2370
+ $break = true;
2371
+ } else if (! $node->hasAttribute($attr))
2372
+ $break = true;
2373
+ // PSEUDO CLASSES
2374
+ } else if ( $s[0] == ':') {
2375
+ // skip
2376
+ // TAG
2377
+ } else if (trim($s)) {
2378
+ if ($s != '*') {
2379
+ // TODO namespaces
2380
+ if (isset($node->tagName)) {
2381
+ if ($node->tagName != $s)
2382
+ $break = true;
2383
+ } else if ($s == 'html' && ! $this->isRoot($node))
2384
+ $break = true;
2385
+ }
2386
+ // AVOID NON-SIMPLE SELECTORS
2387
+ } else if (in_array($s, $notSimpleSelector)) {
2388
+ $break = true;
2389
+ $this->debug(array('Skipping non simple selector', $selector));
2390
+ }
2391
+ }
2392
+ if ($break)
2393
+ break;
2394
+ }
2395
+ // if element passed all chunks of selector - add it to new stack
2396
+ if (! $break )
2397
+ $stack[] = $node;
2398
+ }
2399
+ $tmpStack = $this->elements;
2400
+ $this->elements = $stack;
2401
+ // PER ALL NODES selector chunks
2402
+ foreach($selector as $s)
2403
+ // PSEUDO CLASSES
2404
+ if ($s[0] == ':')
2405
+ $this->pseudoClasses($s);
2406
+ foreach($this->elements as $node)
2407
+ // XXX it should be merged without duplicates
2408
+ // but jQuery doesnt do that
2409
+ $finalStack[] = $node;
2410
+ $this->elements = $tmpStack;
2411
+ }
2412
+ $this->elements = $finalStack;
2413
+ if ($_skipHistory) {
2414
+ return $this;
2415
+ } else {
2416
+ $this->debug("Stack length after filter(): ".count($finalStack));
2417
+ return $this->newInstance();
2418
+ }
2419
+ }
2420
+ /**
2421
+ *
2422
+ * @param $value
2423
+ * @return unknown_type
2424
+ * @TODO implement in all methods using passed parameters
2425
+ */
2426
+ protected static function unQuote($value) {
2427
+ return $value[0] == '\'' || $value[0] == '"'
2428
+ ? substr($value, 1, -1)
2429
+ : $value;
2430
+ }
2431
+ /**
2432
+ * Enter description here...
2433
+ *
2434
+ * @link http://docs.jquery.com/Ajax/load
2435
+ * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2436
+ * @todo Support $selector
2437
+ */
2438
+ public function load($url, $data = null, $callback = null) {
2439
+ if ($data && ! is_array($data)) {
2440
+ $callback = $data;
2441
+ $data = null;
2442
+ }
2443
+ if (mb_strpos($url, ' ') !== false) {
2444
+ $matches = null;
2445
+ if (extension_loaded('mbstring') && phpQuery::$mbstringSupport)
2446
+ mb_ereg('^([^ ]+) (.*)$', $url, $matches);
2447
+ else
2448
+ preg_match('^([^ ]+) (.*)$', $url, $matches);
2449
+ $url = $matches[1];
2450
+ $selector = $matches[2];
2451
+ // FIXME this sucks, pass as callback param
2452
+ $this->_loadSelector = $selector;
2453
+ }
2454
+ $ajax = array(
2455
+ 'url' => $url,
2456
+ 'type' => $data ? 'POST' : 'GET',
2457
+ 'data' => $data,
2458
+ 'complete' => $callback,
2459
+ 'success' => array($this, '__loadSuccess')
2460
+ );
2461
+ phpQuery::ajax($ajax);
2462
+ return $this;
2463
+ }
2464
+ /**
2465
+ * @access private
2466
+ * @param $html
2467
+ * @return unknown_type
2468
+ */
2469
+ public function __loadSuccess($html) {
2470
+ if ($this->_loadSelector) {
2471
+ $html = phpQuery::newDocument($html)->find($this->_loadSelector);
2472
+ unset($this->_loadSelector);
2473
+ }
2474
+ foreach($this->stack(1) as $node) {
2475
+ phpQuery::pq($node, $this->getDocumentID())
2476
+ ->markup($html);
2477
+ }
2478
+ }
2479
+ /**
2480
+ * Enter description here...
2481
+ *
2482
+ * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2483
+ * @todo
2484
+ */
2485
+ public function css() {
2486
+ // TODO
2487
+ return $this;
2488
+ }
2489
+ /**
2490
+ * @todo
2491
+ *
2492
+ */
2493
+ public function show(){
2494
+ // TODO
2495
+ return $this;
2496
+ }
2497
+ /**
2498
+ * @todo
2499
+ *
2500
+ */
2501
+ public function hide(){
2502
+ // TODO
2503
+ return $this;
2504
+ }
2505
+ /**
2506
+ * Trigger a type of event on every matched element.
2507
+ *
2508
+ * @param unknown_type $type
2509
+ * @param unknown_type $data
2510
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2511
+ * @TODO support more than event in $type (space-separated)
2512
+ */
2513
+ public function trigger($type, $data = array()) {
2514
+ foreach($this->elements as $node)
2515
+ phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node);
2516
+ return $this;
2517
+ }
2518
+ /**
2519
+ * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions.
2520
+ *
2521
+ * @param unknown_type $type
2522
+ * @param unknown_type $data
2523
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2524
+ * @TODO
2525
+ */
2526
+ public function triggerHandler($type, $data = array()) {
2527
+ // TODO;
2528
+ }
2529
+ /**
2530
+ * Binds a handler to one or more events (like click) for each matched element.
2531
+ * Can also bind custom events.
2532
+ *
2533
+ * @param unknown_type $type
2534
+ * @param unknown_type $data Optional
2535
+ * @param unknown_type $callback
2536
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2537
+ * @TODO support '!' (exclusive) events
2538
+ * @TODO support more than event in $type (space-separated)
2539
+ */
2540
+ public function bind($type, $data, $callback = null) {
2541
+ // TODO check if $data is callable, not using is_callable
2542
+ if (! isset($callback)) {
2543
+ $callback = $data;
2544
+ $data = null;
2545
+ }
2546
+ foreach($this->elements as $node)
2547
+ phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback);
2548
+ return $this;
2549
+ }
2550
+ /**
2551
+ * Enter description here...
2552
+ *
2553
+ * @param unknown_type $type
2554
+ * @param unknown_type $callback
2555
+ * @return unknown
2556
+ * @TODO namespace events
2557
+ * @TODO support more than event in $type (space-separated)
2558
+ */
2559
+ public function unbind($type = null, $callback = null) {
2560
+ foreach($this->elements as $node)
2561
+ phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback);
2562
+ return $this;
2563
+ }
2564
+ /**
2565
+ * Enter description here...
2566
+ *
2567
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2568
+ */
2569
+ public function change($callback = null) {
2570
+ if ($callback)
2571
+ return $this->bind('change', $callback);
2572
+ return $this->trigger('change');
2573
+ }
2574
+ /**
2575
+ * Enter description here...
2576
+ *
2577
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2578
+ */
2579
+ public function submit($callback = null) {
2580
+ if ($callback)
2581
+ return $this->bind('submit', $callback);
2582
+ return $this->trigger('submit');
2583
+ }
2584
+ /**
2585
+ * Enter description here...
2586
+ *
2587
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2588
+ */
2589
+ public function click($callback = null) {
2590
+ if ($callback)
2591
+ return $this->bind('click', $callback);
2592
+ return $this->trigger('click');
2593
+ }
2594
+ /**
2595
+ * Enter description here...
2596
+ *
2597
+ * @param String|phpQuery
2598
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2599
+ */
2600
+ public function wrapAllOld($wrapper) {
2601
+ $wrapper = pq($wrapper)->_clone();
2602
+ if (! $wrapper->length() || ! $this->length() )
2603
+ return $this;
2604
+ $wrapper->insertBefore($this->elements[0]);
2605
+ $deepest = $wrapper->elements[0];
2606
+ while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
2607
+ $deepest = $deepest->firstChild;
2608
+ pq($deepest)->append($this);
2609
+ return $this;
2610
+ }
2611
+ /**
2612
+ * Enter description here...
2613
+ *
2614
+ * TODO testme...
2615
+ * @param String|phpQuery
2616
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2617
+ */
2618
+ public function wrapAll($wrapper) {
2619
+ if (! $this->length())
2620
+ return $this;
2621
+ return phpQuery::pq($wrapper, $this->getDocumentID())
2622
+ ->clone()
2623
+ ->insertBefore($this->get(0))
2624
+ ->map(array($this, '___wrapAllCallback'))
2625
+ ->append($this);
2626
+ }
2627
+ /**
2628
+ *
2629
+ * @param $node
2630
+ * @return unknown_type
2631
+ * @access private
2632
+ */
2633
+ public function ___wrapAllCallback($node) {
2634
+ $deepest = $node;
2635
+ while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
2636
+ $deepest = $deepest->firstChild;
2637
+ return $deepest;
2638
+ }
2639
+ /**
2640
+ * Enter description here...
2641
+ * NON JQUERY METHOD
2642
+ *
2643
+ * @param String|phpQuery
2644
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2645
+ */
2646
+ public function wrapAllPHP($codeBefore, $codeAfter) {
2647
+ return $this
2648
+ ->slice(0, 1)
2649
+ ->beforePHP($codeBefore)
2650
+ ->end()
2651
+ ->slice(-1)
2652
+ ->afterPHP($codeAfter)
2653
+ ->end();
2654
+ }
2655
+ /**
2656
+ * Enter description here...
2657
+ *
2658
+ * @param String|phpQuery
2659
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2660
+ */
2661
+ public function wrap($wrapper) {
2662
+ foreach($this->stack() as $node)
2663
+ phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper);
2664
+ return $this;
2665
+ }
2666
+ /**
2667
+ * Enter description here...
2668
+ *
2669
+ * @param String|phpQuery
2670
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2671
+ */
2672
+ public function wrapPHP($codeBefore, $codeAfter) {
2673
+ foreach($this->stack() as $node)
2674
+ phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter);
2675
+ return $this;
2676
+ }
2677
+ /**
2678
+ * Enter description here...
2679
+ *
2680
+ * @param String|phpQuery
2681
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2682
+ */
2683
+ public function wrapInner($wrapper) {
2684
+ foreach($this->stack() as $node)
2685
+ phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper);
2686
+ return $this;
2687
+ }
2688
+ /**
2689
+ * Enter description here...
2690
+ *
2691
+ * @param String|phpQuery
2692
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2693
+ */
2694
+ public function wrapInnerPHP($codeBefore, $codeAfter) {
2695
+ foreach($this->stack(1) as $node)
2696
+ phpQuery::pq($node, $this->getDocumentID())->contents()
2697
+ ->wrapAllPHP($codeBefore, $codeAfter);
2698
+ return $this;
2699
+ }
2700
+ /**
2701
+ * Enter description here...
2702
+ *
2703
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2704
+ * @testme Support for text nodes
2705
+ */
2706
+ public function contents() {
2707
+ $stack = array();
2708
+ foreach($this->stack(1) as $el) {
2709
+ // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56
2710
+ // if (! isset($el->childNodes))
2711
+ // continue;
2712
+ foreach($el->childNodes as $node) {
2713
+ $stack[] = $node;
2714
+ }
2715
+ }
2716
+ return $this->newInstance($stack);
2717
+ }
2718
+ /**
2719
+ * Enter description here...
2720
+ *
2721
+ * jQuery difference.
2722
+ *
2723
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2724
+ */
2725
+ public function contentsUnwrap() {
2726
+ foreach($this->stack(1) as $node) {
2727
+ if (! $node->parentNode )
2728
+ continue;
2729
+ $childNodes = array();
2730
+ // any modification in DOM tree breaks childNodes iteration, so cache them first
2731
+ foreach($node->childNodes as $chNode )
2732
+ $childNodes[] = $chNode;
2733
+ foreach($childNodes as $chNode )
2734
+ // $node->parentNode->appendChild($chNode);
2735
+ $node->parentNode->insertBefore($chNode, $node);
2736
+ $node->parentNode->removeChild($node);
2737
+ }
2738
+ return $this;
2739
+ }
2740
+ /**
2741
+ * Enter description here...
2742
+ *
2743
+ * jQuery difference.
2744
+ */
2745
+ public function switchWith($markup) {
2746
+ $markup = pq($markup, $this->getDocumentID());
2747
+ $content = null;
2748
+ foreach($this->stack(1) as $node) {
2749
+ pq($node)
2750
+ ->contents()->toReference($content)->end()
2751
+ ->replaceWith($markup->clone()->append($content));
2752
+ }
2753
+ return $this;
2754
+ }
2755
+ /**
2756
+ * Enter description here...
2757
+ *
2758
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2759
+ */
2760
+ public function eq($num) {
2761
+ $oldStack = $this->elements;
2762
+ $this->elementsBackup = $this->elements;
2763
+ $this->elements = array();
2764
+ if ( isset($oldStack[$num]) )
2765
+ $this->elements[] = $oldStack[$num];
2766
+ return $this->newInstance();
2767
+ }
2768
+ /**
2769
+ * Enter description here...
2770
+ *
2771
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2772
+ */
2773
+ public function size() {
2774
+ return count($this->elements);
2775
+ }
2776
+ /**
2777
+ * Enter description here...
2778
+ *
2779
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2780
+ * @deprecated Use length as attribute
2781
+ */
2782
+ public function length() {
2783
+ return $this->size();
2784
+ }
2785
+ public function count() {
2786
+ return $this->size();
2787
+ }
2788
+ /**
2789
+ * Enter description here...
2790
+ *
2791
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2792
+ * @todo $level
2793
+ */
2794
+ public function end($level = 1) {
2795
+ // $this->elements = array_pop( $this->history );
2796
+ // return $this;
2797
+ // $this->previous->DOM = $this->DOM;
2798
+ // $this->previous->XPath = $this->XPath;
2799
+ return $this->previous
2800
+ ? $this->previous
2801
+ : $this;
2802
+ }
2803
+ /**
2804
+ * Enter description here...
2805
+ * Normal use ->clone() .
2806
+ *
2807
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2808
+ * @access private
2809
+ */
2810
+ public function _clone() {
2811
+ $newStack = array();
2812
+ //pr(array('copy... ', $this->whois()));
2813
+ //$this->dumpHistory('copy');
2814
+ $this->elementsBackup = $this->elements;
2815
+ foreach($this->elements as $node) {
2816
+ $newStack[] = $node->cloneNode(true);
2817
+ }
2818
+ $this->elements = $newStack;
2819
+ return $this->newInstance();
2820
+ }
2821
+ /**
2822
+ * Enter description here...
2823
+ *
2824
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2825
+ */
2826
+ public function replaceWithPHP($code) {
2827
+ return $this->replaceWith(phpQuery::php($code));
2828
+ }
2829
+ /**
2830
+ * Enter description here...
2831
+ *
2832
+ * @param String|phpQuery $content
2833
+ * @link http://docs.jquery.com/Manipulation/replaceWith#content
2834
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2835
+ */
2836
+ public function replaceWith($content) {
2837
+ return $this->after($content)->remove();
2838
+ }
2839
+ /**
2840
+ * Enter description here...
2841
+ *
2842
+ * @param String $selector
2843
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2844
+ * @todo this works ?
2845
+ */
2846
+ public function replaceAll($selector) {
2847
+ foreach(phpQuery::pq($selector, $this->getDocumentID()) as $node)
2848
+ phpQuery::pq($node, $this->getDocumentID())
2849
+ ->after($this->_clone())
2850
+ ->remove();
2851
+ return $this;
2852
+ }
2853
+ /**
2854
+ * Enter description here...
2855
+ *
2856
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2857
+ */
2858
+ public function remove($selector = null) {
2859
+ $loop = $selector
2860
+ ? $this->filter($selector)->elements
2861
+ : $this->elements;
2862
+ foreach($loop as $node) {
2863
+ if (! $node->parentNode )
2864
+ continue;
2865
+ if (isset($node->tagName))
2866
+ $this->debug("Removing '{$node->tagName}'");
2867
+ $node->parentNode->removeChild($node);
2868
+ // Mutation event
2869
+ $event = new DOMEvent(array(
2870
+ 'target' => $node,
2871
+ 'type' => 'DOMNodeRemoved'
2872
+ ));
2873
+ phpQueryEvents::trigger($this->getDocumentID(),
2874
+ $event->type, array($event), $node
2875
+ );
2876
+ }
2877
+ return $this;
2878
+ }
2879
+ protected function markupEvents($newMarkup, $oldMarkup, $node) {
2880
+ if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) {
2881
+ $event = new DOMEvent(array(
2882
+ 'target' => $node,
2883
+ 'type' => 'change'
2884
+ ));
2885
+ phpQueryEvents::trigger($this->getDocumentID(),
2886
+ $event->type, array($event), $node
2887
+ );
2888
+ }
2889
+ }
2890
+ /**
2891
+ * jQuey difference
2892
+ *
2893
+ * @param $markup
2894
+ * @return unknown_type
2895
+ * @TODO trigger change event for textarea
2896
+ */
2897
+ public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2898
+ $args = func_get_args();
2899
+ if ($this->documentWrapper->isXML)
2900
+ return call_user_func_array(array($this, 'xml'), $args);
2901
+ else
2902
+ return call_user_func_array(array($this, 'html'), $args);
2903
+ }
2904
+ /**
2905
+ * jQuey difference
2906
+ *
2907
+ * @param $markup
2908
+ * @return unknown_type
2909
+ */
2910
+ public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2911
+ $args = func_get_args();
2912
+ if ($this->documentWrapper->isXML)
2913
+ return call_user_func_array(array($this, 'xmlOuter'), $args);
2914
+ else
2915
+ return call_user_func_array(array($this, 'htmlOuter'), $args);
2916
+ }
2917
+ /**
2918
+ * Enter description here...
2919
+ *
2920
+ * @param unknown_type $html
2921
+ * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2922
+ * @TODO force html result
2923
+ */
2924
+ public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2925
+ if (isset($html)) {
2926
+ // INSERT
2927
+ $nodes = $this->documentWrapper->import($html);
2928
+ $this->empty();
2929
+ foreach($this->stack(1) as $alreadyAdded => $node) {
2930
+ // for now, limit events for textarea
2931
+ if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
2932
+ $oldHtml = pq($node, $this->getDocumentID())->markup();
2933
+ foreach($nodes as $newNode) {
2934
+ $node->appendChild($alreadyAdded
2935
+ ? $newNode->cloneNode(true)
2936
+ : $newNode
2937
+ );
2938
+ }
2939
+ // for now, limit events for textarea
2940
+ if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
2941
+ $this->markupEvents($html, $oldHtml, $node);
2942
+ }
2943
+ return $this;
2944
+ } else {
2945
+ // FETCH
2946
+ $return = $this->documentWrapper->markup($this->elements, true);
2947
+ $args = func_get_args();
2948
+ foreach(array_slice($args, 1) as $callback) {
2949
+ $return = phpQuery::callbackRun($callback, array($return));
2950
+ }
2951
+ return $return;
2952
+ }
2953
+ }
2954
+ /**
2955
+ * @TODO force xml result
2956
+ */
2957
+ public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2958
+ $args = func_get_args();
2959
+ return call_user_func_array(array($this, 'html'), $args);
2960
+ }
2961
+ /**
2962
+ * Enter description here...
2963
+ * @TODO force html result
2964
+ *
2965
+ * @return String
2966
+ */
2967
+ public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2968
+ $markup = $this->documentWrapper->markup($this->elements);
2969
+ // pass thou callbacks
2970
+ $args = func_get_args();
2971
+ foreach($args as $callback) {
2972
+ $markup = phpQuery::callbackRun($callback, array($markup));
2973
+ }
2974
+ return $markup;
2975
+ }
2976
+ /**
2977
+ * @TODO force xml result
2978
+ */
2979
+ public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2980
+ $args = func_get_args();
2981
+ return call_user_func_array(array($this, 'htmlOuter'), $args);
2982
+ }
2983
+ public function __toString() {
2984
+ return $this->markupOuter();
2985
+ }
2986
+ /**
2987
+ * Just like html(), but returns markup with VALID (dangerous) PHP tags.
2988
+ *
2989
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2990
+ * @todo support returning markup with PHP tags when called without param
2991
+ */
2992
+ public function php($code = null) {
2993
+ return $this->markupPHP($code);
2994
+ }
2995
+ /**
2996
+ * Enter description here...
2997
+ *
2998
+ * @param $code
2999
+ * @return unknown_type
3000
+ */
3001
+ public function markupPHP($code = null) {
3002
+ return isset($code)
3003
+ ? $this->markup(phpQuery::php($code))
3004
+ : phpQuery::markupToPHP($this->markup());
3005
+ }
3006
+ /**
3007
+ * Enter description here...
3008
+ *
3009
+ * @param $code
3010
+ * @return unknown_type
3011
+ */
3012
+ public function markupOuterPHP() {
3013
+ return phpQuery::markupToPHP($this->markupOuter());
3014
+ }
3015
+ /**
3016
+ * Enter description here...
3017
+ *
3018
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3019
+ */
3020
+ public function children($selector = null) {
3021
+ $stack = array();
3022
+ foreach($this->stack(1) as $node) {
3023
+ // foreach($node->getElementsByTagName('*') as $newNode) {
3024
+ foreach($node->childNodes as $newNode) {
3025
+ if ($newNode->nodeType != 1)
3026
+ continue;
3027
+ if ($selector && ! $this->is($selector, $newNode))
3028
+ continue;
3029
+ if ($this->elementsContainsNode($newNode, $stack))
3030
+ continue;
3031
+ $stack[] = $newNode;
3032
+ }
3033
+ }
3034
+ $this->elementsBackup = $this->elements;
3035
+ $this->elements = $stack;
3036
+ return $this->newInstance();
3037
+ }
3038
+ /**
3039
+ * Enter description here...
3040
+ *
3041
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3042
+ */
3043
+ public function ancestors($selector = null) {
3044
+ return $this->children( $selector );
3045
+ }
3046
+ /**
3047
+ * Enter description here...
3048
+ *
3049
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3050
+ */
3051
+ public function append( $content) {
3052
+ return $this->insert($content, __FUNCTION__);
3053
+ }
3054
+ /**
3055
+ * Enter description here...
3056
+ *
3057
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3058
+ */
3059
+ public function appendPHP( $content) {
3060
+ return $this->insert("<php><!-- {$content} --></php>", 'append');
3061
+ }
3062
+ /**
3063
+ * Enter description here...
3064
+ *
3065
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3066
+ */
3067
+ public function appendTo( $seletor) {
3068
+ return $this->insert($seletor, __FUNCTION__);
3069
+ }
3070
+ /**
3071
+ * Enter description here...
3072
+ *
3073
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3074
+ */
3075
+ public function prepend( $content) {
3076
+ return $this->insert($content, __FUNCTION__);
3077
+ }
3078
+ /**
3079
+ * Enter description here...
3080
+ *
3081
+ * @todo accept many arguments, which are joined, arrays maybe also
3082
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3083
+ */
3084
+ public function prependPHP( $content) {
3085
+ return $this->insert("<php><!-- {$content} --></php>", 'prepend');
3086
+ }
3087
+ /**
3088
+ * Enter description here...
3089
+ *
3090
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3091
+ */
3092
+ public function prependTo( $seletor) {
3093
+ return $this->insert($seletor, __FUNCTION__);
3094
+ }
3095
+ /**
3096
+ * Enter description here...
3097
+ *
3098
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3099
+ */
3100
+ public function before($content) {
3101
+ return $this->insert($content, __FUNCTION__);
3102
+ }
3103
+ /**
3104
+ * Enter description here...
3105
+ *
3106
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3107
+ */
3108
+ public function beforePHP( $content) {
3109
+ return $this->insert("<php><!-- {$content} --></php>", 'before');
3110
+ }
3111
+ /**
3112
+ * Enter description here...
3113
+ *
3114
+ * @param String|phpQuery
3115
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3116
+ */
3117
+ public function insertBefore( $seletor) {
3118
+ return $this->insert($seletor, __FUNCTION__);
3119
+ }
3120
+ /**
3121
+ * Enter description here...
3122
+ *
3123
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3124
+ */
3125
+ public function after( $content) {
3126
+ return $this->insert($content, __FUNCTION__);
3127
+ }
3128
+ /**
3129
+ * Enter description here...
3130
+ *
3131
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3132
+ */
3133
+ public function afterPHP( $content) {
3134
+ return $this->insert("<php><!-- {$content} --></php>", 'after');
3135
+ }
3136
+ /**
3137
+ * Enter description here...
3138
+ *
3139
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3140
+ */
3141
+ public function insertAfter( $seletor) {
3142
+ return $this->insert($seletor, __FUNCTION__);
3143
+ }
3144
+ /**
3145
+ * Internal insert method. Don't use it.
3146
+ *
3147
+ * @param unknown_type $target
3148
+ * @param unknown_type $type
3149
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3150
+ * @access private
3151
+ */
3152
+ public function insert($target, $type) {
3153
+ $this->debug("Inserting data with '{$type}'");
3154
+ $to = false;
3155
+ switch( $type) {
3156
+ case 'appendTo':
3157
+ case 'prependTo':
3158
+ case 'insertBefore':
3159
+ case 'insertAfter':
3160
+ $to = true;
3161
+ }
3162
+ switch(gettype($target)) {
3163
+ case 'string':
3164
+ $insertFrom = $insertTo = array();
3165
+ if ($to) {
3166
+ // INSERT TO
3167
+ $insertFrom = $this->elements;
3168
+ if (phpQuery::isMarkup($target)) {
3169
+ // $target is new markup, import it
3170
+ $insertTo = $this->documentWrapper->import($target);
3171
+ // insert into selected element
3172
+ } else {
3173
+ // $tagret is a selector
3174
+ $thisStack = $this->elements;
3175
+ $this->toRoot();
3176
+ $insertTo = $this->find($target)->elements;
3177
+ $this->elements = $thisStack;
3178
+ }
3179
+ } else {
3180
+ // INSERT FROM
3181
+ $insertTo = $this->elements;
3182
+ $insertFrom = $this->documentWrapper->import($target);
3183
+ }
3184
+ break;
3185
+ case 'object':
3186
+ $insertFrom = $insertTo = array();
3187
+ // phpQuery
3188
+ if ($target instanceof self) {
3189
+ if ($to) {
3190
+ $insertTo = $target->elements;
3191
+ if ($this->documentFragment && $this->stackIsRoot())
3192
+ // get all body children
3193
+ // $loop = $this->find('body > *')->elements;
3194
+ // TODO test it, test it hard...
3195
+ // $loop = $this->newInstance($this->root)->find('> *')->elements;
3196
+ $loop = $this->root->childNodes;
3197
+ else
3198
+ $loop = $this->elements;
3199
+ // import nodes if needed
3200
+ $insertFrom = $this->getDocumentID() == $target->getDocumentID()
3201
+ ? $loop
3202
+ : $target->documentWrapper->import($loop);
3203
+ } else {
3204
+ $insertTo = $this->elements;
3205
+ if ( $target->documentFragment && $target->stackIsRoot() )
3206
+ // get all body children
3207
+ // $loop = $target->find('body > *')->elements;
3208
+ $loop = $target->root->childNodes;
3209
+ else
3210
+ $loop = $target->elements;
3211
+ // import nodes if needed
3212
+ $insertFrom = $this->getDocumentID() == $target->getDocumentID()
3213
+ ? $loop
3214
+ : $this->documentWrapper->import($loop);
3215
+ }
3216
+ // DOMNODE
3217
+ } elseif ($target instanceof DOMNODE) {
3218
+ // import node if needed
3219
+ // if ( $target->ownerDocument != $this->DOM )
3220
+ // $target = $this->DOM->importNode($target, true);
3221
+ if ( $to) {
3222
+ $insertTo = array($target);
3223
+ if ($this->documentFragment && $this->stackIsRoot())
3224
+ // get all body children
3225
+ $loop = $this->root->childNodes;
3226
+ // $loop = $this->find('body > *')->elements;
3227
+ else
3228
+ $loop = $this->elements;
3229
+ foreach($loop as $fromNode)
3230
+ // import nodes if needed
3231
+ $insertFrom[] = ! $fromNode->ownerDocument->isSameNode($target->ownerDocument)
3232
+ ? $target->ownerDocument->importNode($fromNode, true)
3233
+ : $fromNode;
3234
+ } else {
3235
+ // import node if needed
3236
+ if (! $target->ownerDocument->isSameNode($this->document))
3237
+ $target = $this->document->importNode($target, true);
3238
+ $insertTo = $this->elements;
3239
+ $insertFrom[] = $target;
3240
+ }
3241
+ }
3242
+ break;
3243
+ }
3244
+ phpQuery::debug("From ".count($insertFrom)."; To ".count($insertTo)." nodes");
3245
+ foreach($insertTo as $insertNumber => $toNode) {
3246
+ // we need static relative elements in some cases
3247
+ switch( $type) {
3248
+ case 'prependTo':
3249
+ case 'prepend':
3250
+ $firstChild = $toNode->firstChild;
3251
+ break;
3252
+ case 'insertAfter':
3253
+ case 'after':
3254
+ $nextSibling = $toNode->nextSibling;
3255
+ break;
3256
+ }
3257
+ foreach($insertFrom as $fromNode) {
3258
+ // clone if inserted already before
3259
+ $insert = $insertNumber
3260
+ ? $fromNode->cloneNode(true)
3261
+ : $fromNode;
3262
+ switch($type) {
3263
+ case 'appendTo':
3264
+ case 'append':
3265
+ // $toNode->insertBefore(
3266
+ // $fromNode,
3267
+ // $toNode->lastChild->nextSibling
3268
+ // );
3269
+ $toNode->appendChild($insert);
3270
+ $eventTarget = $insert;
3271
+ break;
3272
+ case 'prependTo':
3273
+ case 'prepend':
3274
+ $toNode->insertBefore(
3275
+ $insert,
3276
+ $firstChild
3277
+ );
3278
+ break;
3279
+ case 'insertBefore':
3280
+ case 'before':
3281
+ if (! $toNode->parentNode)
3282
+ throw new Exception("No parentNode, can't do {$type}()");
3283
+ else
3284
+ $toNode->parentNode->insertBefore(
3285
+ $insert,
3286
+ $toNode
3287
+ );
3288
+ break;
3289
+ case 'insertAfter':
3290
+ case 'after':
3291
+ if (! $toNode->parentNode)
3292
+ throw new Exception("No parentNode, can't do {$type}()");
3293
+ else
3294
+ $toNode->parentNode->insertBefore(
3295
+ $insert,
3296
+ $nextSibling
3297
+ );
3298
+ break;
3299
+ }
3300
+ // Mutation event
3301
+ $event = new DOMEvent(array(
3302
+ 'target' => $insert,
3303
+ 'type' => 'DOMNodeInserted'
3304
+ ));
3305
+ phpQueryEvents::trigger($this->getDocumentID(),
3306
+ $event->type, array($event), $insert
3307
+ );
3308
+ }
3309
+ }
3310
+ return $this;
3311
+ }
3312
+ /**
3313
+ * Enter description here...
3314
+ *
3315
+ * @return Int
3316
+ */
3317
+ public function index($subject) {
3318
+ $index = -1;
3319
+ $subject = $subject instanceof phpQueryObject
3320
+ ? $subject->elements[0]
3321
+ : $subject;
3322
+ foreach($this->newInstance() as $k => $node) {
3323
+ if ($node->isSameNode($subject))
3324
+ $index = $k;
3325
+ }
3326
+ return $index;
3327
+ }
3328
+ /**
3329
+ * Enter description here...
3330
+ *
3331
+ * @param unknown_type $start
3332
+ * @param unknown_type $end
3333
+ *
3334
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3335
+ * @testme
3336
+ */
3337
+ public function slice($start, $end = null) {
3338
+ // $last = count($this->elements)-1;
3339
+ // $end = $end
3340
+ // ? min($end, $last)
3341
+ // : $last;
3342
+ // if ($start < 0)
3343
+ // $start = $last+$start;
3344
+ // if ($start > $last)
3345
+ // return array();
3346
+ if ($end > 0)
3347
+ $end = $end-$start;
3348
+ return $this->newInstance(
3349
+ array_slice($this->elements, $start, $end)
3350
+ );
3351
+ }
3352
+ /**
3353
+ * Enter description here...
3354
+ *
3355
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3356
+ */
3357
+ public function reverse() {
3358
+ $this->elementsBackup = $this->elements;
3359
+ $this->elements = array_reverse($this->elements);
3360
+ return $this->newInstance();
3361
+ }
3362
+ /**
3363
+ * Return joined text content.
3364
+ * @return String
3365
+ */
3366
+ public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) {
3367
+ if (isset($text))
3368
+ return $this->html(htmlspecialchars($text));
3369
+ $args = func_get_args();
3370
+ $args = array_slice($args, 1);
3371
+ $return = '';
3372
+ foreach($this->elements as $node) {
3373
+ $text = $node->textContent;
3374
+ if (count($this->elements) > 1 && $text)
3375
+ $text .= "\n";
3376
+ foreach($args as $callback) {
3377
+ $text = phpQuery::callbackRun($callback, array($text));
3378
+ }
3379
+ $return .= $text;
3380
+ }
3381
+ return $return;
3382
+ }
3383
+ /**
3384
+ * Enter description here...
3385
+ *
3386
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3387
+ */
3388
+ public function plugin($class, $file = null) {
3389
+ phpQuery::plugin($class, $file);
3390
+ return $this;
3391
+ }
3392
+ /**
3393
+ * Deprecated, use $pq->plugin() instead.
3394
+ *
3395
+ * @deprecated
3396
+ * @param $class
3397
+ * @param $file
3398
+ * @return unknown_type
3399
+ */
3400
+ public static function extend($class, $file = null) {
3401
+ return $this->plugin($class, $file);
3402
+ }
3403
+ /**
3404
+ *
3405
+ * @access private
3406
+ * @param $method
3407
+ * @param $args
3408
+ * @return unknown_type
3409
+ */
3410
+ public function __call($method, $args) {
3411
+ $aliasMethods = array('clone', 'empty');
3412
+ if (isset(phpQuery::$extendMethods[$method])) {
3413
+ array_unshift($args, $this);
3414
+ return phpQuery::callbackRun(
3415
+ phpQuery::$extendMethods[$method], $args
3416
+ );
3417
+ } else if (isset(phpQuery::$pluginsMethods[$method])) {
3418
+ array_unshift($args, $this);
3419
+ $class = phpQuery::$pluginsMethods[$method];
3420
+ $realClass = "phpQueryObjectPlugin_$class";
3421
+ $return = call_user_func_array(
3422
+ array($realClass, $method),
3423
+ $args
3424
+ );
3425
+ // XXX deprecate ?
3426
+ return is_null($return)
3427
+ ? $this
3428
+ : $return;
3429
+ } else if (in_array($method, $aliasMethods)) {
3430
+ return call_user_func_array(array($this, '_'.$method), $args);
3431
+ } else
3432
+ throw new Exception("Method '{$method}' doesnt exist");
3433
+ }
3434
+ /**
3435
+ * Safe rename of next().
3436
+ *
3437
+ * Use it ONLY when need to call next() on an iterated object (in same time).
3438
+ * Normaly there is no need to do such thing ;)
3439
+ *
3440
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3441
+ * @access private
3442
+ */
3443
+ public function _next($selector = null) {
3444
+ return $this->newInstance(
3445
+ $this->getElementSiblings('nextSibling', $selector, true)
3446
+ );
3447
+ }
3448
+ /**
3449
+ * Use prev() and next().
3450
+ *
3451
+ * @deprecated
3452
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3453
+ * @access private
3454
+ */
3455
+ public function _prev($selector = null) {
3456
+ return $this->prev($selector);
3457
+ }
3458
+ /**
3459
+ * Enter description here...
3460
+ *
3461
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3462
+ */
3463
+ public function prev($selector = null) {
3464
+ return $this->newInstance(
3465
+ $this->getElementSiblings('previousSibling', $selector, true)
3466
+ );
3467
+ }
3468
+ /**
3469
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3470
+ * @todo
3471
+ */
3472
+ public function prevAll($selector = null) {
3473
+ return $this->newInstance(
3474
+ $this->getElementSiblings('previousSibling', $selector)
3475
+ );
3476
+ }
3477
+ /**
3478
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3479
+ * @todo FIXME: returns source elements insted of next siblings
3480
+ */
3481
+ public function nextAll($selector = null) {
3482
+ return $this->newInstance(
3483
+ $this->getElementSiblings('nextSibling', $selector)
3484
+ );
3485
+ }
3486
+ /**
3487
+ * @access private
3488
+ */
3489
+ protected function getElementSiblings($direction, $selector = null, $limitToOne = false) {
3490
+ $stack = array();
3491
+ $count = 0;
3492
+ foreach($this->stack() as $node) {
3493
+ $test = $node;
3494
+ while( isset($test->{$direction}) && $test->{$direction}) {
3495
+ $test = $test->{$direction};
3496
+ if (! $test instanceof DOMELEMENT)
3497
+ continue;
3498
+ $stack[] = $test;
3499
+ if ($limitToOne)
3500
+ break;
3501
+ }
3502
+ }
3503
+ if ($selector) {
3504
+ $stackOld = $this->elements;
3505
+ $this->elements = $stack;
3506
+ $stack = $this->filter($selector, true)->stack();
3507
+ $this->elements = $stackOld;
3508
+ }
3509
+ return $stack;
3510
+ }
3511
+ /**
3512
+ * Enter description here...
3513
+ *
3514
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3515
+ */
3516
+ public function siblings($selector = null) {
3517
+ $stack = array();
3518
+ $siblings = array_merge(
3519
+ $this->getElementSiblings('previousSibling', $selector),
3520
+ $this->getElementSiblings('nextSibling', $selector)
3521
+ );
3522
+ foreach($siblings as $node) {
3523
+ if (! $this->elementsContainsNode($node, $stack))
3524
+ $stack[] = $node;
3525
+ }
3526
+ return $this->newInstance($stack);
3527
+ }
3528
+ /**
3529
+ * Enter description here...
3530
+ *
3531
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3532
+ */
3533
+ public function not($selector = null) {
3534
+ if (is_string($selector))
3535
+ phpQuery::debug(array('not', $selector));
3536
+ else
3537
+ phpQuery::debug('not');
3538
+ $stack = array();
3539
+ if ($selector instanceof self || $selector instanceof DOMNODE) {
3540
+ foreach($this->stack() as $node) {
3541
+ if ($selector instanceof self) {
3542
+ $matchFound = false;
3543
+ foreach($selector->stack() as $notNode) {
3544
+ if ($notNode->isSameNode($node))
3545
+ $matchFound = true;
3546
+ }
3547
+ if (! $matchFound)
3548
+ $stack[] = $node;
3549
+ } else if ($selector instanceof DOMNODE) {
3550
+ if (! $selector->isSameNode($node))
3551
+ $stack[] = $node;
3552
+ } else {
3553
+ if (! $this->is($selector))
3554
+ $stack[] = $node;
3555
+ }
3556
+ }
3557
+ } else {
3558
+ $orgStack = $this->stack();
3559
+ $matched = $this->filter($selector, true)->stack();
3560
+ // $matched = array();
3561
+ // // simulate OR in filter() instead of AND 5y
3562
+ // foreach($this->parseSelector($selector) as $s) {
3563
+ // $matched = array_merge($matched,
3564
+ // $this->filter(array($s))->stack()
3565
+ // );
3566
+ // }
3567
+ foreach($orgStack as $node)
3568
+ if (! $this->elementsContainsNode($node, $matched))
3569
+ $stack[] = $node;
3570
+ }
3571
+ return $this->newInstance($stack);
3572
+ }
3573
+ /**
3574
+ * Enter description here...
3575
+ *
3576
+ * @param string|phpQueryObject
3577
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3578
+ */
3579
+ public function add($selector = null) {
3580
+ if (! $selector)
3581
+ return $this;
3582
+ $stack = array();
3583
+ $this->elementsBackup = $this->elements;
3584
+ $found = phpQuery::pq($selector, $this->getDocumentID());
3585
+ $this->merge($found->elements);
3586
+ return $this->newInstance();
3587
+ }
3588
+ /**
3589
+ * @access private
3590
+ */
3591
+ protected function merge() {
3592
+ foreach(func_get_args() as $nodes)
3593
+ foreach($nodes as $newNode )
3594
+ if (! $this->elementsContainsNode($newNode) )
3595
+ $this->elements[] = $newNode;
3596
+ }
3597
+ /**
3598
+ * @access private
3599
+ * TODO refactor to stackContainsNode
3600
+ */
3601
+ protected function elementsContainsNode($nodeToCheck, $elementsStack = null) {
3602
+ $loop = ! is_null($elementsStack)
3603
+ ? $elementsStack
3604
+ : $this->elements;
3605
+ foreach($loop as $node) {
3606
+ if ( $node->isSameNode( $nodeToCheck ) )
3607
+ return true;
3608
+ }
3609
+ return false;
3610
+ }
3611
+ /**
3612
+ * Enter description here...
3613
+ *
3614
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3615
+ */
3616
+ public function parent($selector = null) {
3617
+ $stack = array();
3618
+ foreach($this->elements as $node )
3619
+ if ( $node->parentNode && ! $this->elementsContainsNode($node->parentNode, $stack) )
3620
+ $stack[] = $node->parentNode;
3621
+ $this->elementsBackup = $this->elements;
3622
+ $this->elements = $stack;
3623
+ if ( $selector )
3624
+ $this->filter($selector, true);
3625
+ return $this->newInstance();
3626
+ }
3627
+ /**
3628
+ * Enter description here...
3629
+ *
3630
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3631
+ */
3632
+ public function parents($selector = null) {
3633
+ $stack = array();
3634
+ if (! $this->elements )
3635
+ $this->debug('parents() - stack empty');
3636
+ foreach($this->elements as $node) {
3637
+ $test = $node;
3638
+ while( $test->parentNode) {
3639
+ $test = $test->parentNode;
3640
+ if ($this->isRoot($test))
3641
+ break;
3642
+ if (! $this->elementsContainsNode($test, $stack)) {
3643
+ $stack[] = $test;
3644
+ continue;
3645
+ }
3646
+ }
3647
+ }
3648
+ $this->elementsBackup = $this->elements;
3649
+ $this->elements = $stack;
3650
+ if ( $selector )
3651
+ $this->filter($selector, true);
3652
+ return $this->newInstance();
3653
+ }
3654
+ /**
3655
+ * Internal stack iterator.
3656
+ *
3657
+ * @access private
3658
+ */
3659
+ public function stack($nodeTypes = null) {
3660
+ if (!isset($nodeTypes))
3661
+ return $this->elements;
3662
+ if (!is_array($nodeTypes))
3663
+ $nodeTypes = array($nodeTypes);
3664
+ $return = array();
3665
+ foreach($this->elements as $node) {
3666
+ if (in_array($node->nodeType, $nodeTypes))
3667
+ $return[] = $node;
3668
+ }
3669
+ return $return;
3670
+ }
3671
+ // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes
3672
+ protected function attrEvents($attr, $oldAttr, $oldValue, $node) {
3673
+ // skip events for XML documents
3674
+ if (! $this->isXHTML() && ! $this->isHTML())
3675
+ return;
3676
+ $event = null;
3677
+ // identify
3678
+ $isInputValue = $node->tagName == 'input'
3679
+ && (
3680
+ in_array($node->getAttribute('type'),
3681
+ array('text', 'password', 'hidden'))
3682
+ || !$node->getAttribute('type')
3683
+ );
3684
+ $isRadio = $node->tagName == 'input'
3685
+ && $node->getAttribute('type') == 'radio';
3686
+ $isCheckbox = $node->tagName == 'input'
3687
+ && $node->getAttribute('type') == 'checkbox';
3688
+ $isOption = $node->tagName == 'option';
3689
+ if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) {
3690
+ $event = new DOMEvent(array(
3691
+ 'target' => $node,
3692
+ 'type' => 'change'
3693
+ ));
3694
+ } else if (($isRadio || $isCheckbox) && $attr == 'checked' && (
3695
+ // check
3696
+ (! $oldAttr && $node->hasAttribute($attr))
3697
+ // un-check
3698
+ || (! $node->hasAttribute($attr) && $oldAttr)
3699
+ )) {
3700
+ $event = new DOMEvent(array(
3701
+ 'target' => $node,
3702
+ 'type' => 'change'
3703
+ ));
3704
+ } else if ($isOption && $node->parentNode && $attr == 'selected' && (
3705
+ // select
3706
+ (! $oldAttr && $node->hasAttribute($attr))
3707
+ // un-select
3708
+ || (! $node->hasAttribute($attr) && $oldAttr)
3709
+ )) {
3710
+ $event = new DOMEvent(array(
3711
+ 'target' => $node->parentNode,
3712
+ 'type' => 'change'
3713
+ ));
3714
+ }
3715
+ if ($event) {
3716
+ phpQueryEvents::trigger($this->getDocumentID(),
3717
+ $event->type, array($event), $node
3718
+ );
3719
+ }
3720
+ }
3721
+ public function attr($attr = null, $value = null) {
3722
+ foreach($this->stack(1) as $node) {
3723
+ if (! is_null($value)) {
3724
+ $loop = $attr == '*'
3725
+ ? $this->getNodeAttrs($node)
3726
+ : array($attr);
3727
+ foreach($loop as $a) {
3728
+ $oldValue = $node->getAttribute($a);
3729
+ $oldAttr = $node->hasAttribute($a);
3730
+ // TODO raises an error when charset other than UTF-8
3731
+ // while document's charset is also not UTF-8
3732
+ @$node->setAttribute($a, $value);
3733
+ $this->attrEvents($a, $oldAttr, $oldValue, $node);
3734
+ }
3735
+ } else if ($attr == '*') {
3736
+ // jQuery difference
3737
+ $return = array();
3738
+ foreach($node->attributes as $n => $v)
3739
+ $return[$n] = $v->value;
3740
+ return $return;
3741
+ } else
3742
+ return $node->hasAttribute($attr)
3743
+ ? $node->getAttribute($attr)
3744
+ : null;
3745
+ }
3746
+ return is_null($value)
3747
+ ? '' : $this;
3748
+ }
3749
+ /**
3750
+ * @access private
3751
+ */
3752
+ protected function getNodeAttrs($node) {
3753
+ $return = array();
3754
+ foreach($node->attributes as $n => $o)
3755
+ $return[] = $n;
3756
+ return $return;
3757
+ }
3758
+ /**
3759
+ * Enter description here...
3760
+ *
3761
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3762
+ * @todo check CDATA ???
3763
+ */
3764
+ public function attrPHP($attr, $code) {
3765
+ if (! is_null($code)) {
3766
+ $value = '<'.'?php '.$code.' ?'.'>';
3767
+ // TODO tempolary solution
3768
+ // http://code.google.com/p/phpquery/issues/detail?id=17
3769
+ // if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII')
3770
+ // $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES');
3771
+ }
3772
+ foreach($this->stack(1) as $node) {
3773
+ if (! is_null($code)) {
3774
+ // $attrNode = $this->DOM->createAttribute($attr);
3775
+ $node->setAttribute($attr, $value);
3776
+ // $attrNode->value = $value;
3777
+ // $node->appendChild($attrNode);
3778
+ } else if ( $attr == '*') {
3779
+ // jQuery diff
3780
+ $return = array();
3781
+ foreach($node->attributes as $n => $v)
3782
+ $return[$n] = $v->value;
3783
+ return $return;
3784
+ } else
3785
+ return $node->getAttribute($attr);
3786
+ }
3787
+ return $this;
3788
+ }
3789
+ /**
3790
+ * Enter description here...
3791
+ *
3792
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3793
+ */
3794
+ public function removeAttr($attr) {
3795
+ foreach($this->stack(1) as $node) {
3796
+ $loop = $attr == '*'
3797
+ ? $this->getNodeAttrs($node)
3798
+ : array($attr);
3799
+ foreach($loop as $a) {
3800
+ $oldValue = $node->getAttribute($a);
3801
+ $node->removeAttribute($a);
3802
+ $this->attrEvents($a, $oldValue, null, $node);
3803
+ }
3804
+ }
3805
+ return $this;
3806
+ }
3807
+ /**
3808
+ * Return form element value.
3809
+ *
3810
+ * @return String Fields value.
3811
+ */
3812
+ public function val($val = null) {
3813
+ if (! isset($val)) {
3814
+ if ($this->eq(0)->is('select')) {
3815
+ $selected = $this->eq(0)->find('option[selected=selected]');
3816
+ if ($selected->is('[value]'))
3817
+ return $selected->attr('value');
3818
+ else
3819
+ return $selected->text();
3820
+ } else if ($this->eq(0)->is('textarea'))
3821
+ return $this->eq(0)->markup();
3822
+ else
3823
+ return $this->eq(0)->attr('value');
3824
+ } else {
3825
+ $_val = null;
3826
+ foreach($this->stack(1) as $node) {
3827
+ $node = pq($node, $this->getDocumentID());
3828
+ if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) {
3829
+ $isChecked = in_array($node->attr('value'), $val)
3830
+ || in_array($node->attr('name'), $val);
3831
+ if ($isChecked)
3832
+ $node->attr('checked', 'checked');
3833
+ else
3834
+ $node->removeAttr('checked');
3835
+ } else if ($node->get(0)->tagName == 'select') {
3836
+ if (! isset($_val)) {
3837
+ $_val = array();
3838
+ if (! is_array($val))
3839
+ $_val = array((string)$val);
3840
+ else
3841
+ foreach($val as $v)
3842
+ $_val[] = $v;
3843
+ }
3844
+ foreach($node['option']->stack(1) as $option) {
3845
+ $option = pq($option, $this->getDocumentID());
3846
+ $selected = false;
3847
+ // XXX: workaround for string comparsion, see issue #96
3848
+ // http://code.google.com/p/phpquery/issues/detail?id=96
3849
+ $selected = is_null($option->attr('value'))
3850
+ ? in_array($option->markup(), $_val)
3851
+ : in_array($option->attr('value'), $_val);
3852
+ // $optionValue = $option->attr('value');
3853
+ // $optionText = $option->text();
3854
+ // $optionTextLenght = mb_strlen($optionText);
3855
+ // foreach($_val as $v)
3856
+ // if ($optionValue == $v)
3857
+ // $selected = true;
3858
+ // else if ($optionText == $v && $optionTextLenght == mb_strlen($v))
3859
+ // $selected = true;
3860
+ if ($selected)
3861
+ $option->attr('selected', 'selected');
3862
+ else
3863
+ $option->removeAttr('selected');
3864
+ }
3865
+ } else if ($node->get(0)->tagName == 'textarea')
3866
+ $node->markup($val);
3867
+ else
3868
+ $node->attr('value', $val);
3869
+ }
3870
+ }
3871
+ return $this;
3872
+ }
3873
+ /**
3874
+ * Enter description here...
3875
+ *
3876
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3877
+ */
3878
+ public function andSelf() {
3879
+ if ( $this->previous )
3880
+ $this->elements = array_merge($this->elements, $this->previous->elements);
3881
+ return $this;
3882
+ }
3883
+ /**
3884
+ * Enter description here...
3885
+ *
3886
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3887
+ */
3888
+ public function addClass( $className) {
3889
+ if (! $className)
3890
+ return $this;
3891
+ foreach($this->stack(1) as $node) {
3892
+ if (! $this->is(".$className", $node))
3893
+ $node->setAttribute(
3894
+ 'class',
3895
+ trim($node->getAttribute('class').' '.$className)
3896
+ );
3897
+ }
3898
+ return $this;
3899
+ }
3900
+ /**
3901
+ * Enter description here...
3902
+ *
3903
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3904
+ */
3905
+ public function addClassPHP( $className) {
3906
+ foreach($this->stack(1) as $node) {
3907
+ $classes = $node->getAttribute('class');
3908
+ $newValue = $classes
3909
+ ? $classes.' <'.'?php '.$className.' ?'.'>'
3910
+ : '<'.'?php '.$className.' ?'.'>';
3911
+ $node->setAttribute('class', $newValue);
3912
+ }
3913
+ return $this;
3914
+ }
3915
+ /**
3916
+ * Enter description here...
3917
+ *
3918
+ * @param string $className
3919
+ * @return bool
3920
+ */
3921
+ public function hasClass($className) {
3922
+ foreach($this->stack(1) as $node) {
3923
+ if ( $this->is(".$className", $node))
3924
+ return true;
3925
+ }
3926
+ return false;
3927
+ }
3928
+ /**
3929
+ * Enter description here...
3930
+ *
3931
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3932
+ */
3933
+ public function removeClass($className) {
3934
+ foreach($this->stack(1) as $node) {
3935
+ $classes = explode( ' ', $node->getAttribute('class'));
3936
+ if ( in_array($className, $classes)) {
3937
+ $classes = array_diff($classes, array($className));
3938
+ if ( $classes )
3939
+ $node->setAttribute('class', implode(' ', $classes));
3940
+ else
3941
+ $node->removeAttribute('class');
3942
+ }
3943
+ }
3944
+ return $this;
3945
+ }
3946
+ /**
3947
+ * Enter description here...
3948
+ *
3949
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3950
+ */
3951
+ public function toggleClass($className) {
3952
+ foreach($this->stack(1) as $node) {
3953
+ if ( $this->is( $node, '.'.$className ))
3954
+ $this->removeClass($className);
3955
+ else
3956
+ $this->addClass($className);
3957
+ }
3958
+ return $this;
3959
+ }
3960
+ /**
3961
+ * Proper name without underscore (just ->empty()) also works.
3962
+ *
3963
+ * Removes all child nodes from the set of matched elements.
3964
+ *
3965
+ * Example:
3966
+ * pq("p")._empty()
3967
+ *
3968
+ * HTML:
3969
+ * <p>Hello, <span>Person</span> <a href="#">and person</a></p>
3970
+ *
3971
+ * Result:
3972
+ * [ <p></p> ]
3973
+ *
3974
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3975
+ * @access private
3976
+ */
3977
+ public function _empty() {
3978
+ foreach($this->stack(1) as $node) {
3979
+ // thx to 'dave at dgx dot cz'
3980
+ $node->nodeValue = '';
3981
+ }
3982
+ return $this;
3983
+ }
3984
+ /**
3985
+ * Enter description here...
3986
+ *
3987
+ * @param array|string $callback Expects $node as first param, $index as second
3988
+ * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope)
3989
+ * @param array $arg1 Will ba passed as third and futher args to callback.
3990
+ * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on...
3991
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3992
+ */
3993
+ public function each($callback, $param1 = null, $param2 = null, $param3 = null) {
3994
+ $paramStructure = null;
3995
+ if (func_num_args() > 1) {
3996
+ $paramStructure = func_get_args();
3997
+ $paramStructure = array_slice($paramStructure, 1);
3998
+ }
3999
+ foreach($this->elements as $v)
4000
+ phpQuery::callbackRun($callback, array($v), $paramStructure);
4001
+ return $this;
4002
+ }
4003
+ /**
4004
+ * Run callback on actual object.
4005
+ *
4006
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4007
+ */
4008
+ public function callback($callback, $param1 = null, $param2 = null, $param3 = null) {
4009
+ $params = func_get_args();
4010
+ $params[0] = $this;
4011
+ phpQuery::callbackRun($callback, $params);
4012
+ return $this;
4013
+ }
4014
+ /**
4015
+ * Enter description here...
4016
+ *
4017
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4018
+ * @todo add $scope and $args as in each() ???
4019
+ */
4020
+ public function map($callback, $param1 = null, $param2 = null, $param3 = null) {
4021
+ // $stack = array();
4022
+ //// foreach($this->newInstance() as $node) {
4023
+ // foreach($this->newInstance() as $node) {
4024
+ // $result = call_user_func($callback, $node);
4025
+ // if ($result)
4026
+ // $stack[] = $result;
4027
+ // }
4028
+ $params = func_get_args();
4029
+ array_unshift($params, $this->elements);
4030
+ return $this->newInstance(
4031
+ call_user_func_array(array('phpQuery', 'map'), $params)
4032
+ // phpQuery::map($this->elements, $callback)
4033
+ );
4034
+ }
4035
+ /**
4036
+ * Enter description here...
4037
+ *
4038
+ * @param <type> $key
4039
+ * @param <type> $value
4040
+ */
4041
+ public function data($key, $value = null) {
4042
+ if (! isset($value)) {
4043
+ // TODO? implement specific jQuery behavior od returning parent values
4044
+ // is child which we look up doesn't exist
4045
+ return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID());
4046
+ } else {
4047
+ foreach($this as $node)
4048
+ phpQuery::data($node, $key, $value, $this->getDocumentID());
4049
+ return $this;
4050
+ }
4051
+ }
4052
+ /**
4053
+ * Enter description here...
4054
+ *
4055
+ * @param <type> $key
4056
+ */
4057
+ public function removeData($key) {
4058
+ foreach($this as $node)
4059
+ phpQuery::removeData($node, $key, $this->getDocumentID());
4060
+ return $this;
4061
+ }
4062
+ // INTERFACE IMPLEMENTATIONS
4063
+
4064
+ // ITERATOR INTERFACE
4065
+ /**
4066
+ * @access private
4067
+ */
4068
+ public function rewind(){
4069
+ $this->debug('iterating foreach');
4070
+ // phpQuery::selectDocument($this->getDocumentID());
4071
+ $this->elementsBackup = $this->elements;
4072
+ $this->elementsInterator = $this->elements;
4073
+ $this->valid = isset( $this->elements[0] )
4074
+ ? 1 : 0;
4075
+ // $this->elements = $this->valid
4076
+ // ? array($this->elements[0])
4077
+ // : array();
4078
+ $this->current = 0;
4079
+ }
4080
+ /**
4081
+ * @access private
4082
+ */
4083
+ public function current(){
4084
+ return $this->elementsInterator[ $this->current ];
4085
+ }
4086
+ /**
4087
+ * @access private
4088
+ */
4089
+ public function key(){
4090
+ return $this->current;
4091
+ }
4092
+ /**
4093
+ * Double-function method.
4094
+ *
4095
+ * First: main iterator interface method.
4096
+ * Second: Returning next sibling, alias for _next().
4097
+ *
4098
+ * Proper functionality is choosed automagicaly.
4099
+ *
4100
+ * @see phpQueryObject::_next()
4101
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4102
+ */
4103
+ public function next($cssSelector = null){
4104
+ // if ($cssSelector || $this->valid)
4105
+ // return $this->_next($cssSelector);
4106
+ $this->valid = isset( $this->elementsInterator[ $this->current+1 ] )
4107
+ ? true
4108
+ : false;
4109
+ if (! $this->valid && $this->elementsInterator) {
4110
+ $this->elementsInterator = null;
4111
+ } else if ($this->valid) {
4112
+ $this->current++;
4113
+ } else {
4114
+ return $this->_next($cssSelector);
4115
+ }
4116
+ }
4117
+ /**
4118
+ * @access private
4119
+ */
4120
+ public function valid(){
4121
+ return $this->valid;
4122
+ }
4123
+ // ITERATOR INTERFACE END
4124
+ // ARRAYACCESS INTERFACE
4125
+ /**
4126
+ * @access private
4127
+ */
4128
+ public function offsetExists($offset) {
4129
+ return $this->find($offset)->size() > 0;
4130
+ }
4131
+ /**
4132
+ * @access private
4133
+ */
4134
+ public function offsetGet($offset) {
4135
+ return $this->find($offset);
4136
+ }
4137
+ /**
4138
+ * @access private
4139
+ */
4140
+ public function offsetSet($offset, $value) {
4141
+ // $this->find($offset)->replaceWith($value);
4142
+ $this->find($offset)->html($value);
4143
+ }
4144
+ /**
4145
+ * @access private
4146
+ */
4147
+ public function offsetUnset($offset) {
4148
+ // empty
4149
+ throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML.");
4150
+ }
4151
+ // ARRAYACCESS INTERFACE END
4152
+ /**
4153
+ * Returns node's XPath.
4154
+ *
4155
+ * @param unknown_type $oneNode
4156
+ * @return string
4157
+ * @TODO use native getNodePath is avaible
4158
+ * @access private
4159
+ */
4160
+ protected function getNodeXpath($oneNode = null, $namespace = null) {
4161
+ $return = array();
4162
+ $loop = $oneNode
4163
+ ? array($oneNode)
4164
+ : $this->elements;
4165
+ // if ($namespace)
4166
+ // $namespace .= ':';
4167
+ foreach($loop as $node) {
4168
+ if ($node instanceof DOMDOCUMENT) {
4169
+ $return[] = '';
4170
+ continue;
4171
+ }
4172
+ $xpath = array();
4173
+ while(! ($node instanceof DOMDOCUMENT)) {
4174
+ $i = 1;
4175
+ $sibling = $node;
4176
+ while($sibling->previousSibling) {
4177
+ $sibling = $sibling->previousSibling;
4178
+ $isElement = $sibling instanceof DOMELEMENT;
4179
+ if ($isElement && $sibling->tagName == $node->tagName)
4180
+ $i++;
4181
+ }
4182
+ $xpath[] = $this->isXML()
4183
+ ? "*[local-name()='{$node->tagName}'][{$i}]"
4184
+ : "{$node->tagName}[{$i}]";
4185
+ $node = $node->parentNode;
4186
+ }
4187
+ $xpath = join('/', array_reverse($xpath));
4188
+ $return[] = '/'.$xpath;
4189
+ }
4190
+ return $oneNode
4191
+ ? $return[0]
4192
+ : $return;
4193
+ }
4194
+ // HELPERS
4195
+ public function whois($oneNode = null) {
4196
+ $return = array();
4197
+ $loop = $oneNode
4198
+ ? array( $oneNode )
4199
+ : $this->elements;
4200
+ foreach($loop as $node) {
4201
+ if (isset($node->tagName)) {
4202
+ $tag = in_array($node->tagName, array('php', 'js'))
4203
+ ? strtoupper($node->tagName)
4204
+ : $node->tagName;
4205
+ $return[] = $tag
4206
+ .($node->getAttribute('id')
4207
+ ? '#'.$node->getAttribute('id'):'')
4208
+ .($node->getAttribute('class')
4209
+ ? '.'.join('.', split(' ', $node->getAttribute('class'))):'')
4210
+ .($node->getAttribute('name')
4211
+ ? '[name="'.$node->getAttribute('name').'"]':'')
4212
+ .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false
4213
+ ? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'')
4214
+ .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false
4215
+ ? '[value=PHP]':'')
4216
+ .($node->getAttribute('selected')
4217
+ ? '[selected]':'')
4218
+ .($node->getAttribute('checked')
4219
+ ? '[checked]':'')
4220
+ ;
4221
+ } else if ($node instanceof DOMTEXT) {
4222
+ if (trim($node->textContent))
4223
+ $return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15);
4224
+ } else {
4225
+
4226
+ }
4227
+ }
4228
+ return $oneNode && isset($return[0])
4229
+ ? $return[0]
4230
+ : $return;
4231
+ }
4232
+ /**
4233
+ * Dump htmlOuter and preserve chain. Usefull for debugging.
4234
+ *
4235
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4236
+ *
4237
+ */
4238
+ public function dump() {
4239
+ print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4240
+ $debug = phpQuery::$debug;
4241
+ phpQuery::$debug = false;
4242
+ // print __FILE__.':'.__LINE__."\n";
4243
+ var_dump($this->htmlOuter());
4244
+ return $this;
4245
+ }
4246
+ public function dumpWhois() {
4247
+ print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4248
+ $debug = phpQuery::$debug;
4249
+ phpQuery::$debug = false;
4250
+ // print __FILE__.':'.__LINE__."\n";
4251
+ var_dump('whois', $this->whois());
4252
+ phpQuery::$debug = $debug;
4253
+ return $this;
4254
+ }
4255
+ public function dumpLength() {
4256
+ print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4257
+ $debug = phpQuery::$debug;
4258
+ phpQuery::$debug = false;
4259
+ // print __FILE__.':'.__LINE__."\n";
4260
+ var_dump('length', $this->length());
4261
+ phpQuery::$debug = $debug;
4262
+ return $this;
4263
+ }
4264
+ public function dumpTree($html = true, $title = true) {
4265
+ $output = $title
4266
+ ? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : '';
4267
+ $debug = phpQuery::$debug;
4268
+ phpQuery::$debug = false;
4269
+ foreach($this->stack() as $node)
4270
+ $output .= $this->__dumpTree($node);
4271
+ phpQuery::$debug = $debug;
4272
+ print $html
4273
+ ? nl2br(str_replace(' ', '&nbsp;', $output))
4274
+ : $output;
4275
+ return $this;
4276
+ }
4277
+ private function __dumpTree($node, $intend = 0) {
4278
+ $whois = $this->whois($node);
4279
+ $return = '';
4280
+ if ($whois)
4281
+ $return .= str_repeat(' - ', $intend).$whois."\n";
4282
+ if (isset($node->childNodes))
4283
+ foreach($node->childNodes as $chNode)
4284
+ $return .= $this->__dumpTree($chNode, $intend+1);
4285
+ return $return;
4286
+ }
4287
+ /**
4288
+ * Dump htmlOuter and stop script execution. Usefull for debugging.
4289
+ *
4290
+ */
4291
+ public function dumpDie() {
4292
+ print __FILE__.':'.__LINE__;
4293
+ var_dump($this->htmlOuter());
4294
+ die();
4295
+ }
4296
+ }
4297
+
4298
+
4299
+ // -- Multibyte Compatibility functions ---------------------------------------
4300
+ // http://svn.iphonewebdev.com/lace/lib/mb_compat.php
4301
+
4302
+ /**
4303
+ * mb_internal_encoding()
4304
+ *
4305
+ * Included for mbstring pseudo-compatability.
4306
+ */
4307
+ if (!function_exists('mb_internal_encoding'))
4308
+ {
4309
+ function mb_internal_encoding($enc) {return true; }
4310
+ }
4311
+
4312
+ /**
4313
+ * mb_regex_encoding()
4314
+ *
4315
+ * Included for mbstring pseudo-compatability.
4316
+ */
4317
+ if (!function_exists('mb_regex_encoding'))
4318
+ {
4319
+ function mb_regex_encoding($enc) {return true; }
4320
+ }
4321
+
4322
+ /**
4323
+ * mb_strlen()
4324
+ *
4325
+ * Included for mbstring pseudo-compatability.
4326
+ */
4327
+ if (!function_exists('mb_strlen'))
4328
+ {
4329
+ function mb_strlen($str)
4330
+ {
4331
+ return strlen($str);
4332
+ }
4333
+ }
4334
+
4335
+ /**
4336
+ * mb_strpos()
4337
+ *
4338
+ * Included for mbstring pseudo-compatability.
4339
+ */
4340
+ if (!function_exists('mb_strpos'))
4341
+ {
4342
+ function mb_strpos($haystack, $needle, $offset=0)
4343
+ {
4344
+ return strpos($haystack, $needle, $offset);
4345
+ }
4346
+ }
4347
+ /**
4348
+ * mb_stripos()
4349
+ *
4350
+ * Included for mbstring pseudo-compatability.
4351
+ */
4352
+ if (!function_exists('mb_stripos'))
4353
+ {
4354
+ function mb_stripos($haystack, $needle, $offset=0)
4355
+ {
4356
+ return stripos($haystack, $needle, $offset);
4357
+ }
4358
+ }
4359
+
4360
+ /**
4361
+ * mb_substr()
4362
+ *
4363
+ * Included for mbstring pseudo-compatability.
4364
+ */
4365
+ if (!function_exists('mb_substr'))
4366
+ {
4367
+ function mb_substr($str, $start, $length=0)
4368
+ {
4369
+ return substr($str, $start, $length);
4370
+ }
4371
+ }
4372
+
4373
+ /**
4374
+ * mb_substr_count()
4375
+ *
4376
+ * Included for mbstring pseudo-compatability.
4377
+ */
4378
+ if (!function_exists('mb_substr_count'))
4379
+ {
4380
+ function mb_substr_count($haystack, $needle)
4381
+ {
4382
+ return substr_count($haystack, $needle);
4383
+ }
4384
+ }
4385
+
4386
+
4387
+ /**
4388
+ * Static namespace for phpQuery functions.
4389
+ *
4390
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
4391
+ * @package phpQuery
4392
+ */
4393
+ abstract class phpQuery {
4394
+ /**
4395
+ * XXX: Workaround for mbstring problems
4396
+ *
4397
+ * @var bool
4398
+ */
4399
+ public static $mbstringSupport = true;
4400
+ public static $debug = false;
4401
+ public static $documents = array();
4402
+ public static $defaultDocumentID = null;
4403
+ // public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"';
4404
+ /**
4405
+ * Applies only to HTML.
4406
+ *
4407
+ * @var unknown_type
4408
+ */
4409
+ public static $defaultDoctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
4410
+ "http://www.w3.org/TR/html4/loose.dtd">';
4411
+ public static $defaultCharset = 'UTF-8';
4412
+ /**
4413
+ * Static namespace for plugins.
4414
+ *
4415
+ * @var object
4416
+ */
4417
+ public static $plugins = array();
4418
+ /**
4419
+ * List of loaded plugins.
4420
+ *
4421
+ * @var unknown_type
4422
+ */
4423
+ public static $pluginsLoaded = array();
4424
+ public static $pluginsMethods = array();
4425
+ public static $pluginsStaticMethods = array();
4426
+ public static $extendMethods = array();
4427
+ /**
4428
+ * @TODO implement
4429
+ */
4430
+ public static $extendStaticMethods = array();
4431
+ /**
4432
+ * Hosts allowed for AJAX connections.
4433
+ * Dot '.' means $_SERVER['HTTP_HOST'] (if any).
4434
+ *
4435
+ * @var array
4436
+ */
4437
+ public static $ajaxAllowedHosts = array(
4438
+ '.'
4439
+ );
4440
+ /**
4441
+ * AJAX settings.
4442
+ *
4443
+ * @var array
4444
+ * XXX should it be static or not ?
4445
+ */
4446
+ public static $ajaxSettings = array(
4447
+ 'url' => '',//TODO
4448
+ 'global' => true,
4449
+ 'type' => "GET",
4450
+ 'timeout' => null,
4451
+ 'contentType' => "application/x-www-form-urlencoded",
4452
+ 'processData' => true,
4453
+ // 'async' => true,
4454
+ 'data' => null,
4455
+ 'username' => null,
4456
+ 'password' => null,
4457
+ 'accepts' => array(
4458
+ 'xml' => "application/xml, text/xml",
4459
+ 'html' => "text/html",
4460
+ 'script' => "text/javascript, application/javascript",
4461
+ 'json' => "application/json, text/javascript",
4462
+ 'text' => "text/plain",
4463
+ '_default' => "*/*"
4464
+ )
4465
+ );
4466
+ public static $lastModified = null;
4467
+ public static $active = 0;
4468
+ public static $dumpCount = 0;
4469
+ /**
4470
+ * Multi-purpose function.
4471
+ * Use pq() as shortcut.
4472
+ *
4473
+ * In below examples, $pq is any result of pq(); function.
4474
+ *
4475
+ * 1. Import markup into existing document (without any attaching):
4476
+ * - Import into selected document:
4477
+ * pq('<div/>') // DOESNT accept text nodes at beginning of input string !
4478
+ * - Import into document with ID from $pq->getDocumentID():
4479
+ * pq('<div/>', $pq->getDocumentID())
4480
+ * - Import into same document as DOMNode belongs to:
4481
+ * pq('<div/>', DOMNode)
4482
+ * - Import into document from phpQuery object:
4483
+ * pq('<div/>', $pq)
4484
+ *
4485
+ * 2. Run query:
4486
+ * - Run query on last selected document:
4487
+ * pq('div.myClass')
4488
+ * - Run query on document with ID from $pq->getDocumentID():
4489
+ * pq('div.myClass', $pq->getDocumentID())
4490
+ * - Run query on same document as DOMNode belongs to and use node(s)as root for query:
4491
+ * pq('div.myClass', DOMNode)
4492
+ * - Run query on document from phpQuery object
4493
+ * and use object's stack as root node(s) for query:
4494
+ * pq('div.myClass', $pq)
4495
+ *
4496
+ * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes
4497
+ * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root)
4498
+ *
4499
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false
4500
+ * phpQuery object or false in case of error.
4501
+ */
4502
+ public static function pq($arg1, $context = null) {
4503
+ if ($arg1 instanceof DOMNODE && ! isset($context)) {
4504
+ foreach(phpQuery::$documents as $documentWrapper) {
4505
+ $compare = $arg1 instanceof DOMDocument
4506
+ ? $arg1 : $arg1->ownerDocument;
4507
+ if ($documentWrapper->document->isSameNode($compare))
4508
+ $context = $documentWrapper->id;
4509
+ }
4510
+ }
4511
+ if (! $context) {
4512
+ $domId = self::$defaultDocumentID;
4513
+ if (! $domId)
4514
+ throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first.");
4515
+ // } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
4516
+ } else if (is_object($context) && $context instanceof phpQueryObject)
4517
+ $domId = $context->getDocumentID();
4518
+ else if ($context instanceof DOMDOCUMENT) {
4519
+ $domId = self::getDocumentID($context);
4520
+ if (! $domId) {
4521
+ //throw new Exception('Orphaned DOMDocument');
4522
+ $domId = self::newDocument($context)->getDocumentID();
4523
+ }
4524
+ } else if ($context instanceof DOMNODE) {
4525
+ $domId = self::getDocumentID($context);
4526
+ if (! $domId) {
4527
+ throw new Exception('Orphaned DOMNode');
4528
+ // $domId = self::newDocument($context->ownerDocument);
4529
+ }
4530
+ } else
4531
+ $domId = $context;
4532
+ if ($arg1 instanceof phpQueryObject) {
4533
+ // if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) {
4534
+ /**
4535
+ * Return $arg1 or import $arg1 stack if document differs:
4536
+ * pq(pq('<div/>'))
4537
+ */
4538
+ if ($arg1->getDocumentID() == $domId)
4539
+ return $arg1;
4540
+ $class = get_class($arg1);
4541
+ // support inheritance by passing old object to overloaded constructor
4542
+ $phpQuery = $class != 'phpQuery'
4543
+ ? new $class($arg1, $domId)
4544
+ : new phpQueryObject($domId);
4545
+ $phpQuery->elements = array();
4546
+ foreach($arg1->elements as $node)
4547
+ $phpQuery->elements[] = $phpQuery->document->importNode($node, true);
4548
+ return $phpQuery;
4549
+ } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) {
4550
+ /*
4551
+ * Wrap DOM nodes with phpQuery object, import into document when needed:
4552
+ * pq(array($domNode1, $domNode2))
4553
+ */
4554
+ $phpQuery = new phpQueryObject($domId);
4555
+ if (!($arg1 instanceof DOMNODELIST) && ! is_array($arg1))
4556
+ $arg1 = array($arg1);
4557
+ $phpQuery->elements = array();
4558
+ foreach($arg1 as $node) {
4559
+ $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT
4560
+ && ! $node->ownerDocument->isSameNode($phpQuery->document);
4561
+ $phpQuery->elements[] = $sameDocument
4562
+ ? $phpQuery->document->importNode($node, true)
4563
+ : $node;
4564
+ }
4565
+ return $phpQuery;
4566
+ } else if (self::isMarkup($arg1)) {
4567
+ /**
4568
+ * Import HTML:
4569
+ * pq('<div/>')
4570
+ */
4571
+ $phpQuery = new phpQueryObject($domId);
4572
+ return $phpQuery->newInstance(
4573
+ $phpQuery->documentWrapper->import($arg1)
4574
+ );
4575
+ } else {
4576
+ /**
4577
+ * Run CSS query:
4578
+ * pq('div.myClass')
4579
+ */
4580
+ $phpQuery = new phpQueryObject($domId);
4581
+ // if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
4582
+ if ($context && $context instanceof phpQueryObject)
4583
+ $phpQuery->elements = $context->elements;
4584
+ else if ($context && $context instanceof DOMNODELIST) {
4585
+ $phpQuery->elements = array();
4586
+ foreach($context as $node)
4587
+ $phpQuery->elements[] = $node;
4588
+ } else if ($context && $context instanceof DOMNODE)
4589
+ $phpQuery->elements = array($context);
4590
+ return $phpQuery->find($arg1);
4591
+ }
4592
+ }
4593
+ /**
4594
+ * Sets default document to $id. Document has to be loaded prior
4595
+ * to using this method.
4596
+ * $id can be retrived via getDocumentID() or getDocumentIDRef().
4597
+ *
4598
+ * @param unknown_type $id
4599
+ */
4600
+ public static function selectDocument($id) {
4601
+ $id = self::getDocumentID($id);
4602
+ self::debug("Selecting document '$id' as default one");
4603
+ self::$defaultDocumentID = self::getDocumentID($id);
4604
+ }
4605
+ /**
4606
+ * Returns document with id $id or last used as phpQueryObject.
4607
+ * $id can be retrived via getDocumentID() or getDocumentIDRef().
4608
+ * Chainable.
4609
+ *
4610
+ * @see phpQuery::selectDocument()
4611
+ * @param unknown_type $id
4612
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4613
+ */
4614
+ public static function getDocument($id = null) {
4615
+ if ($id)
4616
+ phpQuery::selectDocument($id);
4617
+ else
4618
+ $id = phpQuery::$defaultDocumentID;
4619
+ return new phpQueryObject($id);
4620
+ }
4621
+ /**
4622
+ * Creates new document from markup.
4623
+ * Chainable.
4624
+ *
4625
+ * @param unknown_type $markup
4626
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4627
+ */
4628
+ public static function newDocument($markup = null, $contentType = null) {
4629
+ if (! $markup)
4630
+ $markup = '';
4631
+ $documentID = phpQuery::createDocumentWrapper($markup, $contentType);
4632
+ return new phpQueryObject($documentID);
4633
+ }
4634
+ /**
4635
+ * Creates new document from markup.
4636
+ * Chainable.
4637
+ *
4638
+ * @param unknown_type $markup
4639
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4640
+ */
4641
+ public static function newDocumentHTML($markup = null, $charset = null) {
4642
+ $contentType = $charset
4643
+ ? ";charset=$charset"
4644
+ : '';
4645
+ return self::newDocument($markup, "text/html{$contentType}");
4646
+ }
4647
+ /**
4648
+ * Creates new document from markup.
4649
+ * Chainable.
4650
+ *
4651
+ * @param unknown_type $markup
4652
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4653
+ */
4654
+ public static function newDocumentXML($markup = null, $charset = null) {
4655
+ $contentType = $charset
4656
+ ? ";charset=$charset"
4657
+ : '';
4658
+ return self::newDocument($markup, "text/xml{$contentType}");
4659
+ }
4660
+ /**
4661
+ * Creates new document from markup.
4662
+ * Chainable.
4663
+ *
4664
+ * @param unknown_type $markup
4665
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4666
+ */
4667
+ public static function newDocumentXHTML($markup = null, $charset = null) {
4668
+ $contentType = $charset
4669
+ ? ";charset=$charset"
4670
+ : '';
4671
+ return self::newDocument($markup, "application/xhtml+xml{$contentType}");
4672
+ }
4673
+ /**
4674
+ * Creates new document from markup.
4675
+ * Chainable.
4676
+ *
4677
+ * @param unknown_type $markup
4678
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4679
+ */
4680
+ public static function newDocumentPHP($markup = null, $contentType = "text/html") {
4681
+ // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function)
4682
+ $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset);
4683
+ return self::newDocument($markup, $contentType);
4684
+ }
4685
+ public static function phpToMarkup($php, $charset = 'utf-8') {
4686
+ $regexes = array(
4687
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?>)([^\']*)\'@s',
4688
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?>)([^"]*)"@s',
4689
+ );
4690
+ foreach($regexes as $regex)
4691
+ while (preg_match($regex, $php, $matches)) {
4692
+ $php = preg_replace_callback(
4693
+ $regex,
4694
+ // create_function('$m, $charset = "'.$charset.'"',
4695
+ // 'return $m[1].$m[2]
4696
+ // .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
4697
+ // .$m[5].$m[2];'
4698
+ // ),
4699
+ array('phpQuery', '_phpToMarkupCallback'),
4700
+ $php
4701
+ );
4702
+ }
4703
+ $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s';
4704
+ //preg_match_all($regex, $php, $matches);
4705
+ //var_dump($matches);
4706
+ $php = preg_replace($regex, '\\1<php><!-- \\3 --></php>', $php);
4707
+ return $php;
4708
+ }
4709
+ public static function _phpToMarkupCallback($php, $charset = 'utf-8') {
4710
+ return $m[1].$m[2]
4711
+ .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
4712
+ .$m[5].$m[2];
4713
+ }
4714
+ public static function _markupToPHPCallback($m) {
4715
+ return "<"."?php ".htmlspecialchars_decode($m[1])." ?".">";
4716
+ }
4717
+ /**
4718
+ * Converts document markup containing PHP code generated by phpQuery::php()
4719
+ * into valid (executable) PHP code syntax.
4720
+ *
4721
+ * @param string|phpQueryObject $content
4722
+ * @return string PHP code.
4723
+ */
4724
+ public static function markupToPHP($content) {
4725
+ if ($content instanceof phpQueryObject)
4726
+ $content = $content->markupOuter();
4727
+ /* <php>...</php> to <?php...? > */
4728
+ $content = preg_replace_callback(
4729
+ '@<php>\s*<!--(.*?)-->\s*</php>@s',
4730
+ // create_function('$m',
4731
+ // 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";'
4732
+ // ),
4733
+ array('phpQuery', '_markupToPHPCallback'),
4734
+ $content
4735
+ );
4736
+ /* <node attr='< ?php ? >'> extra space added to save highlighters */
4737
+ $regexes = array(
4738
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^\']*)\'@s',
4739
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^"]*)"@s',
4740
+ );
4741
+ foreach($regexes as $regex)
4742
+ while (preg_match($regex, $content))
4743
+ $content = preg_replace_callback(
4744
+ $regex,
4745
+ create_function('$m',
4746
+ 'return $m[1].$m[2].$m[3]."<?php "
4747
+ .str_replace(
4748
+ array("%20", "%3E", "%09", "&#10;", "&#9;", "%7B", "%24", "%7D", "%22", "%5B", "%5D"),
4749
+ array(" ", ">", " ", "\n", " ", "{", "$", "}", \'"\', "[", "]"),
4750
+ htmlspecialchars_decode($m[4])
4751
+ )
4752
+ ." ?>".$m[5].$m[2];'
4753
+ ),
4754
+ $content
4755
+ );
4756
+ return $content;
4757
+ }
4758
+ /**
4759
+ * Creates new document from file $file.
4760
+ * Chainable.
4761
+ *
4762
+ * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources.
4763
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4764
+ */
4765
+ public static function newDocumentFile($file, $contentType = null) {
4766
+ $documentID = self::createDocumentWrapper(
4767
+ file_get_contents($file), $contentType
4768
+ );
4769
+ return new phpQueryObject($documentID);
4770
+ }
4771
+ /**
4772
+ * Creates new document from markup.
4773
+ * Chainable.
4774
+ *
4775
+ * @param unknown_type $markup
4776
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4777
+ */
4778
+ public static function newDocumentFileHTML($file, $charset = null) {
4779
+ $contentType = $charset
4780
+ ? ";charset=$charset"
4781
+ : '';
4782
+ return self::newDocumentFile($file, "text/html{$contentType}");
4783
+ }
4784
+ /**
4785
+ * Creates new document from markup.
4786
+ * Chainable.
4787
+ *
4788
+ * @param unknown_type $markup
4789
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4790
+ */
4791
+ public static function newDocumentFileXML($file, $charset = null) {
4792
+ $contentType = $charset
4793
+ ? ";charset=$charset"
4794
+ : '';
4795
+ return self::newDocumentFile($file, "text/xml{$contentType}");
4796
+ }
4797
+ /**
4798
+ * Creates new document from markup.
4799
+ * Chainable.
4800
+ *
4801
+ * @param unknown_type $markup
4802
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4803
+ */
4804
+ public static function newDocumentFileXHTML($file, $charset = null) {
4805
+ $contentType = $charset
4806
+ ? ";charset=$charset"
4807
+ : '';
4808
+ return self::newDocumentFile($file, "application/xhtml+xml{$contentType}");
4809
+ }
4810
+ /**
4811
+ * Creates new document from markup.
4812
+ * Chainable.
4813
+ *
4814
+ * @param unknown_type $markup
4815
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4816
+ */
4817
+ public static function newDocumentFilePHP($file, $contentType = null) {
4818
+ return self::newDocumentPHP(file_get_contents($file), $contentType);
4819
+ }
4820
+ /**
4821
+ * Reuses existing DOMDocument object.
4822
+ * Chainable.
4823
+ *
4824
+ * @param $document DOMDocument
4825
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4826
+ * @TODO support DOMDocument
4827
+ */
4828
+ public static function loadDocument($document) {
4829
+ // TODO
4830
+ die('TODO loadDocument');
4831
+ }
4832
+ /**
4833
+ * Enter description here...
4834
+ *
4835
+ * @param unknown_type $html
4836
+ * @param unknown_type $domId
4837
+ * @return unknown New DOM ID
4838
+ * @todo support PHP tags in input
4839
+ * @todo support passing DOMDocument object from self::loadDocument
4840
+ */
4841
+ protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) {
4842
+ if (function_exists('domxml_open_mem'))
4843
+ throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled.");
4844
+ // $id = $documentID
4845
+ // ? $documentID
4846
+ // : md5(microtime());
4847
+ $document = null;
4848
+ if ($html instanceof DOMDOCUMENT) {
4849
+ if (self::getDocumentID($html)) {
4850
+ // document already exists in phpQuery::$documents, make a copy
4851
+ $document = clone $html;
4852
+ } else {
4853
+ // new document, add it to phpQuery::$documents
4854
+ $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
4855
+ }
4856
+ } else {
4857
+ $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
4858
+ }
4859
+ // $wrapper->id = $id;
4860
+ // bind document
4861
+ phpQuery::$documents[$wrapper->id] = $wrapper;
4862
+ // remember last loaded document
4863
+ phpQuery::selectDocument($wrapper->id);
4864
+ return $wrapper->id;
4865
+ }
4866
+ /**
4867
+ * Extend class namespace.
4868
+ *
4869
+ * @param string|array $target
4870
+ * @param array $source
4871
+ * @TODO support string $source
4872
+ * @return unknown_type
4873
+ */
4874
+ public static function extend($target, $source) {
4875
+ switch($target) {
4876
+ case 'phpQueryObject':
4877
+ $targetRef = &self::$extendMethods;
4878
+ $targetRef2 = &self::$pluginsMethods;
4879
+ break;
4880
+ case 'phpQuery':
4881
+ $targetRef = &self::$extendStaticMethods;
4882
+ $targetRef2 = &self::$pluginsStaticMethods;
4883
+ break;
4884
+ default:
4885
+ throw new Exception("Unsupported \$target type");
4886
+ }
4887
+ if (is_string($source))
4888
+ $source = array($source => $source);
4889
+ foreach($source as $method => $callback) {
4890
+ if (isset($targetRef[$method])) {
4891
+ // throw new Exception
4892
+ self::debug("Duplicate method '{$method}', can\'t extend '{$target}'");
4893
+ continue;
4894
+ }
4895
+ if (isset($targetRef2[$method])) {
4896
+ // throw new Exception
4897
+ self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}',"
4898
+ ." can\'t extend '{$target}'");
4899
+ continue;
4900
+ }
4901
+ $targetRef[$method] = $callback;
4902
+ }
4903
+ return true;
4904
+ }
4905
+ /**
4906
+ * Extend phpQuery with $class from $file.
4907
+ *
4908
+ * @param string $class Extending class name. Real class name can be prepended phpQuery_.
4909
+ * @param string $file Filename to include. Defaults to "{$class}.php".
4910
+ */
4911
+ public static function plugin($class, $file = null) {
4912
+ // TODO $class checked agains phpQuery_$class
4913
+ // if (strpos($class, 'phpQuery') === 0)
4914
+ // $class = substr($class, 8);
4915
+ if (in_array($class, self::$pluginsLoaded))
4916
+ return true;
4917
+ if (! $file)
4918
+ $file = $class.'.php';
4919
+ $objectClassExists = class_exists('phpQueryObjectPlugin_'.$class);
4920
+ $staticClassExists = class_exists('phpQueryPlugin_'.$class);
4921
+ if (! $objectClassExists && ! $staticClassExists)
4922
+ require_once($file);
4923
+ self::$pluginsLoaded[] = $class;
4924
+ // static methods
4925
+ if (class_exists('phpQueryPlugin_'.$class)) {
4926
+ $realClass = 'phpQueryPlugin_'.$class;
4927
+ $vars = get_class_vars($realClass);
4928
+ $loop = isset($vars['phpQueryMethods'])
4929
+ && ! is_null($vars['phpQueryMethods'])
4930
+ ? $vars['phpQueryMethods']
4931
+ : get_class_methods($realClass);
4932
+ foreach($loop as $method) {
4933
+ if ($method == '__initialize')
4934
+ continue;
4935
+ if (! is_callable(array($realClass, $method)))
4936
+ continue;
4937
+ if (isset(self::$pluginsStaticMethods[$method])) {
4938
+ throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsStaticMethods[$method]."'");
4939
+ return;
4940
+ }
4941
+ self::$pluginsStaticMethods[$method] = $class;
4942
+ }
4943
+ if (method_exists($realClass, '__initialize'))
4944
+ call_user_func_array(array($realClass, '__initialize'), array());
4945
+ }
4946
+ // object methods
4947
+ if (class_exists('phpQueryObjectPlugin_'.$class)) {
4948
+ $realClass = 'phpQueryObjectPlugin_'.$class;
4949
+ $vars = get_class_vars($realClass);
4950
+ $loop = isset($vars['phpQueryMethods'])
4951
+ && ! is_null($vars['phpQueryMethods'])
4952
+ ? $vars['phpQueryMethods']
4953
+ : get_class_methods($realClass);
4954
+ foreach($loop as $method) {
4955
+ if (! is_callable(array($realClass, $method)))
4956
+ continue;
4957
+ if (isset(self::$pluginsMethods[$method])) {
4958
+ throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsMethods[$method]."'");
4959
+ continue;
4960
+ }
4961
+ self::$pluginsMethods[$method] = $class;
4962
+ }
4963
+ }
4964
+ return true;
4965
+ }
4966
+ /**
4967
+ * Unloades all or specified document from memory.
4968
+ *
4969
+ * @param mixed $documentID @see phpQuery::getDocumentID() for supported types.
4970
+ */
4971
+ public static function unloadDocuments($id = null) {
4972
+ if (isset($id)) {
4973
+ if ($id = self::getDocumentID($id))
4974
+ unset(phpQuery::$documents[$id]);
4975
+ } else {
4976
+ foreach(phpQuery::$documents as $k => $v) {
4977
+ unset(phpQuery::$documents[$k]);
4978
+ }
4979
+ }
4980
+ }
4981
+ /**
4982
+ * Parses phpQuery object or HTML result against PHP tags and makes them active.
4983
+ *
4984
+ * @param phpQuery|string $content
4985
+ * @deprecated
4986
+ * @return string
4987
+ */
4988
+ public static function unsafePHPTags($content) {
4989
+ return self::markupToPHP($content);
4990
+ }
4991
+ public static function DOMNodeListToArray($DOMNodeList) {
4992
+ $array = array();
4993
+ if (! $DOMNodeList)
4994
+ return $array;
4995
+ foreach($DOMNodeList as $node)
4996
+ $array[] = $node;
4997
+ return $array;
4998
+ }
4999
+ /**
5000
+ * Checks if $input is HTML string, which has to start with '<'.
5001
+ *
5002
+ * @deprecated
5003
+ * @param String $input
5004
+ * @return Bool
5005
+ * @todo still used ?
5006
+ */
5007
+ public static function isMarkup($input) {
5008
+ return ! is_array($input) && substr(trim($input), 0, 1) == '<';
5009
+ }
5010
+ public static function debug($text) {
5011
+ if (self::$debug)
5012
+ print var_dump($text);
5013
+ }
5014
+ /**
5015
+ * Make an AJAX request.
5016
+ *
5017
+ * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions
5018
+ * Additional options are:
5019
+ * 'document' - document for global events, @see phpQuery::getDocumentID()
5020
+ * 'referer' - implemented
5021
+ * 'requested_with' - TODO; not implemented (X-Requested-With)
5022
+ * @return Zend_Http_Client
5023
+ * @link http://docs.jquery.com/Ajax/jQuery.ajax
5024
+ *
5025
+ * @TODO $options['cache']
5026
+ * @TODO $options['processData']
5027
+ * @TODO $options['xhr']
5028
+ * @TODO $options['data'] as string
5029
+ * @TODO XHR interface
5030
+ */
5031
+ public static function ajax($options = array(), $xhr = null) {
5032
+ $options = array_merge(
5033
+ self::$ajaxSettings, $options
5034
+ );
5035
+ $documentID = isset($options['document'])
5036
+ ? self::getDocumentID($options['document'])
5037
+ : null;
5038
+ if ($xhr) {
5039
+ // reuse existing XHR object, but clean it up
5040
+ $client = $xhr;
5041
+ // $client->setParameterPost(null);
5042
+ // $client->setParameterGet(null);
5043
+ $client->setAuth(false);
5044
+ $client->setHeaders("If-Modified-Since", null);
5045
+ $client->setHeaders("Referer", null);
5046
+ $client->resetParameters();
5047
+ } else {
5048
+ // create new XHR object
5049
+ require_once('Zend/Http/Client.php');
5050
+ $client = new Zend_Http_Client();
5051
+ $client->setCookieJar();
5052
+ }
5053
+ if (isset($options['timeout']))
5054
+ $client->setConfig(array(
5055
+ 'timeout' => $options['timeout'],
5056
+ ));
5057
+ // 'maxredirects' => 0,
5058
+ foreach(self::$ajaxAllowedHosts as $k => $host)
5059
+ if ($host == '.' && isset($_SERVER['HTTP_HOST']))
5060
+ self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST'];
5061
+ $host = parse_url($options['url'], PHP_URL_HOST);
5062
+ if (! in_array($host, self::$ajaxAllowedHosts)) {
5063
+ throw new Exception("Request not permitted, host '$host' not present in "
5064
+ ."phpQuery::\$ajaxAllowedHosts");
5065
+ }
5066
+ // JSONP
5067
+ $jsre = "/=\\?(&|$)/";
5068
+ if (isset($options['dataType']) && $options['dataType'] == 'jsonp') {
5069
+ $jsonpCallbackParam = $options['jsonp']
5070
+ ? $options['jsonp'] : 'callback';
5071
+ if (strtolower($options['type']) == 'get') {
5072
+ if (! preg_match($jsre, $options['url'])) {
5073
+ $sep = strpos($options['url'], '?')
5074
+ ? '&' : '?';
5075
+ $options['url'] .= "$sep$jsonpCallbackParam=?";
5076
+ }
5077
+ } else if ($options['data']) {
5078
+ $jsonp = false;
5079
+ foreach($options['data'] as $n => $v) {
5080
+ if ($v == '?')
5081
+ $jsonp = true;
5082
+ }
5083
+ if (! $jsonp) {
5084
+ $options['data'][$jsonpCallbackParam] = '?';
5085
+ }
5086
+ }
5087
+ $options['dataType'] = 'json';
5088
+ }
5089
+ if (isset($options['dataType']) && $options['dataType'] == 'json') {
5090
+ $jsonpCallback = 'json_'.md5(microtime());
5091
+ $jsonpData = $jsonpUrl = false;
5092
+ if ($options['data']) {
5093
+ foreach($options['data'] as $n => $v) {
5094
+ if ($v == '?')
5095
+ $jsonpData = $n;
5096
+ }
5097
+ }
5098
+ if (preg_match($jsre, $options['url']))
5099
+ $jsonpUrl = true;
5100
+ if ($jsonpData !== false || $jsonpUrl) {
5101
+ // remember callback name for httpData()
5102
+ $options['_jsonp'] = $jsonpCallback;
5103
+ if ($jsonpData !== false)
5104
+ $options['data'][$jsonpData] = $jsonpCallback;
5105
+ if ($jsonpUrl)
5106
+ $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']);
5107
+ }
5108
+ }
5109
+ $client->setUri($options['url']);
5110
+ $client->setMethod(strtoupper($options['type']));
5111
+ if (isset($options['referer']) && $options['referer'])
5112
+ $client->setHeaders('Referer', $options['referer']);
5113
+ $client->setHeaders(array(
5114
+ // 'content-type' => $options['contentType'],
5115
+ 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko'
5116
+ .'/2008122010 Firefox/3.0.5',
5117
+ // TODO custom charset
5118
+ 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
5119
+ // 'Connection' => 'keep-alive',
5120
+ // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
5121
+ 'Accept-Language' => 'en-us,en;q=0.5',
5122
+ ));
5123
+ if ($options['username'])
5124
+ $client->setAuth($options['username'], $options['password']);
5125
+ if (isset($options['ifModified']) && $options['ifModified'])
5126
+ $client->setHeaders("If-Modified-Since",
5127
+ self::$lastModified
5128
+ ? self::$lastModified
5129
+ : "Thu, 01 Jan 1970 00:00:00 GMT"
5130
+ );
5131
+ $client->setHeaders("Accept",
5132
+ isset($options['dataType'])
5133
+ && isset(self::$ajaxSettings['accepts'][ $options['dataType'] ])
5134
+ ? self::$ajaxSettings['accepts'][ $options['dataType'] ].", */*"
5135
+ : self::$ajaxSettings['accepts']['_default']
5136
+ );
5137
+ // TODO $options['processData']
5138
+ if ($options['data'] instanceof phpQueryObject) {
5139
+ $serialized = $options['data']->serializeArray($options['data']);
5140
+ $options['data'] = array();
5141
+ foreach($serialized as $r)
5142
+ $options['data'][ $r['name'] ] = $r['value'];
5143
+ }
5144
+ if (strtolower($options['type']) == 'get') {
5145
+ $client->setParameterGet($options['data']);
5146
+ } else if (strtolower($options['type']) == 'post') {
5147
+ $client->setEncType($options['contentType']);
5148
+ $client->setParameterPost($options['data']);
5149
+ }
5150
+ if (self::$active == 0 && $options['global'])
5151
+ phpQueryEvents::trigger($documentID, 'ajaxStart');
5152
+ self::$active++;
5153
+ // beforeSend callback
5154
+ if (isset($options['beforeSend']) && $options['beforeSend'])
5155
+ phpQuery::callbackRun($options['beforeSend'], array($client));
5156
+ // ajaxSend event
5157
+ if ($options['global'])
5158
+ phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options));
5159
+ if (phpQuery::$debug) {
5160
+ self::debug("{$options['type']}: {$options['url']}\n");
5161
+ self::debug("Options: <pre>".var_export($options, true)."</pre>\n");
5162
+ // if ($client->getCookieJar())
5163
+ // self::debug("Cookies: <pre>".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."</pre>\n");
5164
+ }
5165
+ // request
5166
+ $response = $client->request();
5167
+ if (phpQuery::$debug) {
5168
+ self::debug('Status: '.$response->getStatus().' / '.$response->getMessage());
5169
+ self::debug($client->getLastRequest());
5170
+ self::debug($response->getHeaders());
5171
+ }
5172
+ if ($response->isSuccessful()) {
5173
+ // XXX tempolary
5174
+ self::$lastModified = $response->getHeader('Last-Modified');
5175
+ $data = self::httpData($response->getBody(), $options['dataType'], $options);
5176
+ if (isset($options['success']) && $options['success'])
5177
+ phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options));
5178
+ if ($options['global'])
5179
+ phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options));
5180
+ } else {
5181
+ if (isset($options['error']) && $options['error'])
5182
+ phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage()));
5183
+ if ($options['global'])
5184
+ phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/$response->getMessage(), $options));
5185
+ }
5186
+ if (isset($options['complete']) && $options['complete'])
5187
+ phpQuery::callbackRun($options['complete'], array($client, $response->getStatus()));
5188
+ if ($options['global'])
5189
+ phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options));
5190
+ if ($options['global'] && ! --self::$active)
5191
+ phpQueryEvents::trigger($documentID, 'ajaxStop');
5192
+ return $client;
5193
+ // if (is_null($domId))
5194
+ // $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false;
5195
+ // return new phpQueryAjaxResponse($response, $domId);
5196
+ }
5197
+ protected static function httpData($data, $type, $options) {
5198
+ if (isset($options['dataFilter']) && $options['dataFilter'])
5199
+ $data = self::callbackRun($options['dataFilter'], array($data, $type));
5200
+ if (is_string($data)) {
5201
+ if ($type == "json") {
5202
+ if (isset($options['_jsonp']) && $options['_jsonp']) {
5203
+ $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data);
5204
+ }
5205
+ $data = self::parseJSON($data);
5206
+ }
5207
+ }
5208
+ return $data;
5209
+ }
5210
+ /**
5211
+ * Enter description here...
5212
+ *
5213
+ * @param array|phpQuery $data
5214
+ *
5215
+ */
5216
+ public static function param($data) {
5217
+ return http_build_query($data, null, '&');
5218
+ }
5219
+ public static function get($url, $data = null, $callback = null, $type = null) {
5220
+ if (!is_array($data)) {
5221
+ $callback = $data;
5222
+ $data = null;
5223
+ }
5224
+ // TODO some array_values on this shit
5225
+ return phpQuery::ajax(array(
5226
+ 'type' => 'GET',
5227
+ 'url' => $url,
5228
+ 'data' => $data,
5229
+ 'success' => $callback,
5230
+ 'dataType' => $type,
5231
+ ));
5232
+ }
5233
+ public static function post($url, $data = null, $callback = null, $type = null) {
5234
+ if (!is_array($data)) {
5235
+ $callback = $data;
5236
+ $data = null;
5237
+ }
5238
+ return phpQuery::ajax(array(
5239
+ 'type' => 'POST',
5240
+ 'url' => $url,
5241
+ 'data' => $data,
5242
+ 'success' => $callback,
5243
+ 'dataType' => $type,
5244
+ ));
5245
+ }
5246
+ public static function getJSON($url, $data = null, $callback = null) {
5247
+ if (!is_array($data)) {
5248
+ $callback = $data;
5249
+ $data = null;
5250
+ }
5251
+ // TODO some array_values on this shit
5252
+ return phpQuery::ajax(array(
5253
+ 'type' => 'GET',
5254
+ 'url' => $url,
5255
+ 'data' => $data,
5256
+ 'success' => $callback,
5257
+ 'dataType' => 'json',
5258
+ ));
5259
+ }
5260
+ public static function ajaxSetup($options) {
5261
+ self::$ajaxSettings = array_merge(
5262
+ self::$ajaxSettings,
5263
+ $options
5264
+ );
5265
+ }
5266
+ public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) {
5267
+ $loop = is_array($host1)
5268
+ ? $host1
5269
+ : func_get_args();
5270
+ foreach($loop as $host) {
5271
+ if ($host && ! in_array($host, phpQuery::$ajaxAllowedHosts)) {
5272
+ phpQuery::$ajaxAllowedHosts[] = $host;
5273
+ }
5274
+ }
5275
+ }
5276
+ public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) {
5277
+ $loop = is_array($url1)
5278
+ ? $url1
5279
+ : func_get_args();
5280
+ foreach($loop as $url)
5281
+ phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST));
5282
+ }
5283
+ /**
5284
+ * Returns JSON representation of $data.
5285
+ *
5286
+ * @static
5287
+ * @param mixed $data
5288
+ * @return string
5289
+ */
5290
+ public static function toJSON($data) {
5291
+ if (function_exists('json_encode'))
5292
+ return json_encode($data);
5293
+ require_once('Zend/Json/Encoder.php');
5294
+ return Zend_Json_Encoder::encode($data);
5295
+ }
5296
+ /**
5297
+ * Parses JSON into proper PHP type.
5298
+ *
5299
+ * @static
5300
+ * @param string $json
5301
+ * @return mixed
5302
+ */
5303
+ public static function parseJSON($json) {
5304
+ if (function_exists('json_decode')) {
5305
+ $return = json_decode(trim($json), true);
5306
+ // json_decode and UTF8 issues
5307
+ if (isset($return))
5308
+ return $return;
5309
+ }
5310
+ require_once('Zend/Json/Decoder.php');
5311
+ return Zend_Json_Decoder::decode($json);
5312
+ }
5313
+ /**
5314
+ * Returns source's document ID.
5315
+ *
5316
+ * @param $source DOMNode|phpQueryObject
5317
+ * @return string
5318
+ */
5319
+ public static function getDocumentID($source) {
5320
+ if ($source instanceof DOMDOCUMENT) {
5321
+ foreach(phpQuery::$documents as $id => $document) {
5322
+ if ($source->isSameNode($document->document))
5323
+ return $id;
5324
+ }
5325
+ } else if ($source instanceof DOMNODE) {
5326
+ foreach(phpQuery::$documents as $id => $document) {
5327
+ if ($source->ownerDocument->isSameNode($document->document))
5328
+ return $id;
5329
+ }
5330
+ } else if ($source instanceof phpQueryObject)
5331
+ return $source->getDocumentID();
5332
+ else if (is_string($source) && isset(phpQuery::$documents[$source]))
5333
+ return $source;
5334
+ }
5335
+ /**
5336
+ * Get DOMDocument object related to $source.
5337
+ * Returns null if such document doesn't exist.
5338
+ *
5339
+ * @param $source DOMNode|phpQueryObject|string
5340
+ * @return string
5341
+ */
5342
+ public static function getDOMDocument($source) {
5343
+ if ($source instanceof DOMDOCUMENT)
5344
+ return $source;
5345
+ $source = self::getDocumentID($source);
5346
+ return $source
5347
+ ? self::$documents[$id]['document']
5348
+ : null;
5349
+ }
5350
+
5351
+ // UTILITIES
5352
+ // http://docs.jquery.com/Utilities
5353
+
5354
+ /**
5355
+ *
5356
+ * @return unknown_type
5357
+ * @link http://docs.jquery.com/Utilities/jQuery.makeArray
5358
+ */
5359
+ public static function makeArray($obj) {
5360
+ $array = array();
5361
+ if (is_object($object) && $object instanceof DOMNODELIST) {
5362
+ foreach($object as $value)
5363
+ $array[] = $value;
5364
+ } else if (is_object($object) && ! ($object instanceof Iterator)) {
5365
+ foreach(get_object_vars($object) as $name => $value)
5366
+ $array[0][$name] = $value;
5367
+ } else {
5368
+ foreach($object as $name => $value)
5369
+ $array[0][$name] = $value;
5370
+ }
5371
+ return $array;
5372
+ }
5373
+ public static function inArray($value, $array) {
5374
+ return in_array($value, $array);
5375
+ }
5376
+ /**
5377
+ *
5378
+ * @param $object
5379
+ * @param $callback
5380
+ * @return unknown_type
5381
+ * @link http://docs.jquery.com/Utilities/jQuery.each
5382
+ */
5383
+ public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) {
5384
+ $paramStructure = null;
5385
+ if (func_num_args() > 2) {
5386
+ $paramStructure = func_get_args();
5387
+ $paramStructure = array_slice($paramStructure, 2);
5388
+ }
5389
+ if (is_object($object) && ! ($object instanceof Iterator)) {
5390
+ foreach(get_object_vars($object) as $name => $value)
5391
+ phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
5392
+ } else {
5393
+ foreach($object as $name => $value)
5394
+ phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
5395
+ }
5396
+ }
5397
+ /**
5398
+ *
5399
+ * @link http://docs.jquery.com/Utilities/jQuery.map
5400
+ */
5401
+ public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) {
5402
+ $result = array();
5403
+ $paramStructure = null;
5404
+ if (func_num_args() > 2) {
5405
+ $paramStructure = func_get_args();
5406
+ $paramStructure = array_slice($paramStructure, 2);
5407
+ }
5408
+ foreach($array as $v) {
5409
+ $vv = phpQuery::callbackRun($callback, array($v), $paramStructure);
5410
+ // $callbackArgs = $args;
5411
+ // foreach($args as $i => $arg) {
5412
+ // $callbackArgs[$i] = $arg instanceof CallbackParam
5413
+ // ? $v
5414
+ // : $arg;
5415
+ // }
5416
+ // $vv = call_user_func_array($callback, $callbackArgs);
5417
+ if (is_array($vv)) {
5418
+ foreach($vv as $vvv)
5419
+ $result[] = $vvv;
5420
+ } else if ($vv !== null) {
5421
+ $result[] = $vv;
5422
+ }
5423
+ }
5424
+ return $result;
5425
+ }
5426
+ /**
5427
+ *
5428
+ * @param $callback Callback
5429
+ * @param $params
5430
+ * @param $paramStructure
5431
+ * @return unknown_type
5432
+ */
5433
+ public static function callbackRun($callback, $params = array(), $paramStructure = null) {
5434
+ if (! $callback)
5435
+ return;
5436
+ if ($callback instanceof CallbackParameterToReference) {
5437
+ // TODO support ParamStructure to select which $param push to reference
5438
+ if (isset($params[0]))
5439
+ $callback->callback = $params[0];
5440
+ return true;
5441
+ }
5442
+ if ($callback instanceof Callback) {
5443
+ $paramStructure = $callback->params;
5444
+ $callback = $callback->callback;
5445
+ }
5446
+ if (! $paramStructure)
5447
+ return call_user_func_array($callback, $params);
5448
+ $p = 0;
5449
+ foreach($paramStructure as $i => $v) {
5450
+ $paramStructure[$i] = $v instanceof CallbackParam
5451
+ ? $params[$p++]
5452
+ : $v;
5453
+ }
5454
+ return call_user_func_array($callback, $paramStructure);
5455
+ }
5456
+ /**
5457
+ * Merge 2 phpQuery objects.
5458
+ * @param array $one
5459
+ * @param array $two
5460
+ * @protected
5461
+ * @todo node lists, phpQueryObject
5462
+ */
5463
+ public static function merge($one, $two) {
5464
+ $elements = $one->elements;
5465
+ foreach($two->elements as $node) {
5466
+ $exists = false;
5467
+ foreach($elements as $node2) {
5468
+ if ($node2->isSameNode($node))
5469
+ $exists = true;
5470
+ }
5471
+ if (! $exists)
5472
+ $elements[] = $node;
5473
+ }
5474
+ return $elements;
5475
+ // $one = $one->newInstance();
5476
+ // $one->elements = $elements;
5477
+ // return $one;
5478
+ }
5479
+ /**
5480
+ *
5481
+ * @param $array
5482
+ * @param $callback
5483
+ * @param $invert
5484
+ * @return unknown_type
5485
+ * @link http://docs.jquery.com/Utilities/jQuery.grep
5486
+ */
5487
+ public static function grep($array, $callback, $invert = false) {
5488
+ $result = array();
5489
+ foreach($array as $k => $v) {
5490
+ $r = call_user_func_array($callback, array($v, $k));
5491
+ if ($r === !(bool)$invert)
5492
+ $result[] = $v;
5493
+ }
5494
+ return $result;
5495
+ }
5496
+ public static function unique($array) {
5497
+ return array_unique($array);
5498
+ }
5499
+ /**
5500
+ *
5501
+ * @param $function
5502
+ * @return unknown_type
5503
+ * @TODO there are problems with non-static methods, second parameter pass it
5504
+ * but doesnt verify is method is really callable
5505
+ */
5506
+ public static function isFunction($function) {
5507
+ return is_callable($function);
5508
+ }
5509
+ public static function trim($str) {
5510
+ return trim($str);
5511
+ }
5512
+ /* PLUGINS NAMESPACE */
5513
+ /**
5514
+ *
5515
+ * @param $url
5516
+ * @param $callback
5517
+ * @param $param1
5518
+ * @param $param2
5519
+ * @param $param3
5520
+ * @return phpQueryObject
5521
+ */
5522
+ public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) {
5523
+ if (self::plugin('WebBrowser')) {
5524
+ $params = func_get_args();
5525
+ return self::callbackRun(array(self::$plugins, 'browserGet'), $params);
5526
+ } else {
5527
+ self::debug('WebBrowser plugin not available...');
5528
+ }
5529
+ }
5530
+ /**
5531
+ *
5532
+ * @param $url
5533
+ * @param $data
5534
+ * @param $callback
5535
+ * @param $param1
5536
+ * @param $param2
5537
+ * @param $param3
5538
+ * @return phpQueryObject
5539
+ */
5540
+ public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) {
5541
+ if (self::plugin('WebBrowser')) {
5542
+ $params = func_get_args();
5543
+ return self::callbackRun(array(self::$plugins, 'browserPost'), $params);
5544
+ } else {
5545
+ self::debug('WebBrowser plugin not available...');
5546
+ }
5547
+ }
5548
+ /**
5549
+ *
5550
+ * @param $ajaxSettings
5551
+ * @param $callback
5552
+ * @param $param1
5553
+ * @param $param2
5554
+ * @param $param3
5555
+ * @return phpQueryObject
5556
+ */
5557
+ public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) {
5558
+ if (self::plugin('WebBrowser')) {
5559
+ $params = func_get_args();
5560
+ return self::callbackRun(array(self::$plugins, 'browser'), $params);
5561
+ } else {
5562
+ self::debug('WebBrowser plugin not available...');
5563
+ }
5564
+ }
5565
+ /**
5566
+ *
5567
+ * @param $code
5568
+ * @return string
5569
+ */
5570
+ public static function php($code) {
5571
+ return self::code('php', $code);
5572
+ }
5573
+ /**
5574
+ *
5575
+ * @param $type
5576
+ * @param $code
5577
+ * @return string
5578
+ */
5579
+ public static function code($type, $code) {
5580
+ return "<$type><!-- ".trim($code)." --></$type>";
5581
+ }
5582
+
5583
+ public static function __callStatic($method, $params) {
5584
+ return call_user_func_array(
5585
+ array(phpQuery::$plugins, $method),
5586
+ $params
5587
+ );
5588
+ }
5589
+ protected static function dataSetupNode($node, $documentID) {
5590
+ // search are return if alredy exists
5591
+ foreach(phpQuery::$documents[$documentID]->dataNodes as $dataNode) {
5592
+ if ($node->isSameNode($dataNode))
5593
+ return $dataNode;
5594
+ }
5595
+ // if doesn't, add it
5596
+ phpQuery::$documents[$documentID]->dataNodes[] = $node;
5597
+ return $node;
5598
+ }
5599
+ protected static function dataRemoveNode($node, $documentID) {
5600
+ // search are return if alredy exists
5601
+ foreach(phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) {
5602
+ if ($node->isSameNode($dataNode)) {
5603
+ unset(self::$documents[$documentID]->dataNodes[$k]);
5604
+ unset(self::$documents[$documentID]->data[ $dataNode->dataID ]);
5605
+ }
5606
+ }
5607
+ }
5608
+ public static function data($node, $name, $data, $documentID = null) {
5609
+ if (! $documentID)
5610
+ // TODO check if this works
5611
+ $documentID = self::getDocumentID($node);
5612
+ $document = phpQuery::$documents[$documentID];
5613
+ $node = self::dataSetupNode($node, $documentID);
5614
+ if (! isset($node->dataID))
5615
+ $node->dataID = ++phpQuery::$documents[$documentID]->uuid;
5616
+ $id = $node->dataID;
5617
+ if (! isset($document->data[$id]))
5618
+ $document->data[$id] = array();
5619
+ if (! is_null($data))
5620
+ $document->data[$id][$name] = $data;
5621
+ if ($name) {
5622
+ if (isset($document->data[$id][$name]))
5623
+ return $document->data[$id][$name];
5624
+ } else
5625
+ return $id;
5626
+ }
5627
+ public static function removeData($node, $name, $documentID) {
5628
+ if (! $documentID)
5629
+ // TODO check if this works
5630
+ $documentID = self::getDocumentID($node);
5631
+ $document = phpQuery::$documents[$documentID];
5632
+ $node = self::dataSetupNode($node, $documentID);
5633
+ $id = $node->dataID;
5634
+ if ($name) {
5635
+ if (isset($document->data[$id][$name]))
5636
+ unset($document->data[$id][$name]);
5637
+ $name = null;
5638
+ foreach($document->data[$id] as $name)
5639
+ break;
5640
+ if (! $name)
5641
+ self::removeData($node, $name, $documentID);
5642
+ } else {
5643
+ self::dataRemoveNode($node, $documentID);
5644
+ }
5645
+ }
5646
+ }
5647
+ /**
5648
+ * Plugins static namespace class.
5649
+ *
5650
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
5651
+ * @package phpQuery
5652
+ * @todo move plugin methods here (as statics)
5653
+ */
5654
+ class phpQueryPlugins {
5655
+ public function __call($method, $args) {
5656
+ if (isset(phpQuery::$extendStaticMethods[$method])) {
5657
+ $return = call_user_func_array(
5658
+ phpQuery::$extendStaticMethods[$method],
5659
+ $args
5660
+ );
5661
+ } else if (isset(phpQuery::$pluginsStaticMethods[$method])) {
5662
+ $class = phpQuery::$pluginsStaticMethods[$method];
5663
+ $realClass = "phpQueryPlugin_$class";
5664
+ $return = call_user_func_array(
5665
+ array($realClass, $method),
5666
+ $args
5667
+ );
5668
+ return isset($return)
5669
+ ? $return
5670
+ : $this;
5671
+ } else
5672
+ throw new Exception("Method '{$method}' doesnt exist");
5673
+ }
5674
+ }
5675
+ /**
5676
+ * Shortcut to phpQuery::pq($arg1, $context)
5677
+ * Chainable.
5678
+ *
5679
+ * @see phpQuery::pq()
5680
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
5681
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
5682
+ * @package phpQuery
5683
+ */
5684
+ function pq($arg1, $context = null) {
5685
+ $args = func_get_args();
5686
+ return call_user_func_array(
5687
+ array('phpQuery', 'pq'),
5688
+ $args
5689
+ );
5690
+ }
5691
+ // add plugins dir and Zend framework to include path
5692
+ set_include_path(
5693
+ get_include_path()
5694
+ .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/'
5695
+ .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/plugins/'
5696
+ );
5697
+ // why ? no __call nor __get for statics in php...
5698
+ // XXX __callStatic will be available in PHP 5.3
5699
+ phpQuery::$plugins = new phpQueryPlugins();
5700
+ // include bootstrap file (personal library config)
5701
+ if (file_exists(dirname(__FILE__).'/phpQuery/bootstrap.php'))
5702
  require_once dirname(__FILE__).'/phpQuery/bootstrap.php';
includes/wp-plugin-dev-classes/class-wp-meta-box-page.php CHANGED
@@ -1,664 +1,664 @@
1
- <?php defined( 'ABSPATH' ) OR die( 'No direct access.' );
2
- if ( ! class_exists( 'WP_Meta_Box_Page_01' ) ):
3
- /**
4
- * WP_Meta_Box_Page_01 Class
5
- *
6
- * Creating a dashboard-like admin page with meta boxes
7
- *
8
- * Requires WordPress 3.0+ and PHP 5.2+
9
- *
10
- * Custom Actions:
11
- * - load action params: (1) this object
12
- * - meta_box_load
13
- * - meta_box_load-[page_slug]
14
- *
15
- * Custom Filters:
16
- * - page_title filter params: (1) default content, (2) this object
17
- * - meta_box_page_title
18
- * - meta_box_page_title-[page_slug]
19
- *
20
- * - page_content filter params: (1) default content, (2) this object
21
- * - meta_box_page_content
22
- * - meta_box_page_content-[page_slug]
23
- *
24
- * - screen_settings filter params: (1) default content, (2) current screen object, (3) this object
25
- * - meta_box_screen_settings
26
- * - meta_box_screen_settings-[page_slug]
27
- *
28
- * - contextual_help filter params: (1) default content, (2) current screen id, (3) current screen object, (4) this object
29
- * - meta_box_contextual_help
30
- * - meta_box_contextual_help-[page_slug]
31
- *
32
- * @version 0.1
33
- * @author Victor Villaverde Laan
34
- * @link http://www.freelancephp.net/
35
- * @license Dual licensed under the MIT and GPL licenses
36
- */
37
- class WP_Meta_Box_Page_01 {
38
-
39
- /**
40
- * Default settings
41
- * @var array
42
- */
43
- private static $default_settings = array(
44
- // Page title
45
- 'page_title' => NULL, // Default will be set equal to $menu_title
46
-
47
- // Menu title
48
- 'menu_title' => NULL, // Default will be set equal to $page_title
49
-
50
- // Page slug
51
- 'page_slug' => NULL, // Default will be set to: sanitize_title_with_dashes( $menu_title )
52
-
53
- // Default number of columns
54
- 'default_columns' => 2,
55
-
56
- // Column widths
57
- 'column_widths' => array(
58
- 1 => array( 99 ),
59
- 2 => array( 49, 49 ),
60
- 3 => array( 32.33, 32.33, 32.33 ),
61
- 4 => array( 24, 24, 24, 24 ),
62
- ),
63
-
64
- // Add page method
65
- 'add_page_method' => 'add_options_page', // OR: add_menu_page, add_object_page, add_submenu_page
66
-
67
- // Extra params for the add_page_method
68
-
69
- // Capability
70
- 'capability' => 'manage_options', // Optional for all methods
71
-
72
- // Parent slug
73
- 'parent_slug' => NULL, // Nescessary when using "add_submenu_page"
74
-
75
- // Url to the icon to be used for the menu ( around 20 x 20 pixels )
76
- 'icon_url' => NULL, // Only for "add_menu_page" or "add_object_page"
77
-
78
- // The position in the menu order this menu should appear
79
- 'position' => NULL, // Only for "add_menu_page", default on bottom of the menu
80
- );
81
-
82
- /**
83
- * Settings
84
- * @var array
85
- */
86
- private $settings = array();
87
-
88
- /**
89
- * Meta boxes
90
- * @var array
91
- */
92
- private $meta_boxes = array();
93
-
94
- /**
95
- * Pagehook ( will be set when page is created )
96
- * @var string
97
- */
98
- protected $pagehook = NULL;
99
-
100
-
101
- /**
102
- * Initialize
103
- * @param array $settings Optional
104
- */
105
- public function init( $settings = NULL, $load_callback = NULL ) {
106
- // get settings of child class
107
- $child_settings = $this->settings;
108
-
109
- // set settings in 3 steps...
110
- // (1) first set the default options
111
- $this->set_setting( self::$default_settings );
112
-
113
- // (2) set child settings
114
- if ( ! empty( $child_settings ) )
115
- $this->set_setting( $child_settings );
116
-
117
- // (3) set param settings
118
- if ( $settings !== NULL )
119
- $this->set_setting( $settings );
120
-
121
- // actions
122
- add_action( 'admin_menu', array( $this, 'call_admin_menu' ) );
123
-
124
- // add load action
125
- if ( $load_callback !== NULL )
126
- add_action( 'meta_box_load-'. $this->get_setting( 'page_slug' ), $load_callback );
127
- }
128
-
129
- /**
130
- * Set default setting value
131
- * @param mixed $key Also possible to give an array of key/value pairs
132
- * @param mixed $value Optional
133
- * @static
134
- */
135
- public static function set_default_setting( $key, $value = NULL ) {
136
- if ( is_array( $key ) ) {
137
- foreach ( $key AS $k => $v )
138
- self::set_default_setting( $k, $v );
139
- } else {
140
- self::$default_settings[ $key ] = $value;
141
- }
142
- }
143
-
144
- /**
145
- * Set setting value
146
- * @param mixed $key Also possible to give an array of key/value pairs
147
- * @param mixed $value Optional
148
- * @return $this For chaining
149
- */
150
- public function set_setting( $key, $value = NULL ) {
151
- if ( is_array( $key ) ) {
152
- foreach ( $key AS $k => $v )
153
- $this->set_setting( $k, $v );
154
- } else {
155
- $this->settings[ $key ] = $value;
156
- }
157
-
158
- // auto-set related prop values
159
- if ( $value !== NULL ) {
160
- if ( $key == 'menu_title' AND $this->get_setting( 'page_title' ) === NULL )
161
- $this->set_setting( 'page_title', $value );
162
-
163
- if ( $key == 'page_title' AND $this->get_setting( 'menu_title' ) === NULL )
164
- $this->set_setting( 'menu_title', $value );
165
-
166
- if ( ( $key == 'menu_title' OR $key == 'page_title' ) AND $this->get_setting( 'page_slug' ) === NULL ) {
167
- $new_val = $value;
168
- $new_val = sanitize_title_with_dashes( $new_val );
169
- $new_val = strtolower( $new_val );
170
-
171
- // check for valid page_slug
172
- if ( ! preg_match( '/^[a-z_-]+$/', $new_val ) ) {
173
- $new_val = str_replace(
174
- array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 ),
175
- array( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' ),
176
- $new_val
177
- );
178
-
179
- $new_val = ereg_replace( '[^a-z_-]', '', $new_val );
180
- }
181
-
182
- $this->set_setting( 'page_slug', $new_val );
183
- }
184
- }
185
-
186
- return $this;
187
- }
188
-
189
- /**
190
- * Get setting value
191
- * @param string $key Optional, when NULL will return array of all options
192
- * @param mixed $default Optional, return default when key cannot be found OR is NULL
193
- * @return mixed
194
- */
195
- public function get_setting( $key = NULL, $default = NULL ) {
196
- if ( $key === NULL )
197
- return $this->settings;
198
-
199
- if ( key_exists( $key, $this->settings ) )
200
- return ( $this->settings[ $key ] === NULL ) ? $default : $this->settings[ $key ];
201
-
202
- return $default;
203
- }
204
-
205
- /**
206
- * Helper for adding a meta box
207
- * @param string $title Title of the meta box
208
- * @param string $callback Callback for the meta box content
209
- * @param mixed $context Optional, add meta box to this column (normal = 1, side = 2, column3 = 3, column4 = 4)
210
- * @param string $priority Optional, the priority within the context where the boxes should show ( 'high', 'core', 'default' or 'low' )
211
- * @return $this For chaining
212
- */
213
- public function add_meta_box( $title, $callback, $context = 'normal', $id = NULL, $priority = 'default' ) {
214
- $this->meta_boxes[] = array(
215
- 'id' => $id,
216
- 'title' => $title,
217
- 'callback' => $callback,
218
- 'context' => $context,
219
- 'priority' => $priority,
220
- );
221
-
222
- return $this;
223
- }
224
-
225
- /**
226
- * Add callback to "meta_box_load-[page]" action, only applied for this page/object
227
- * @param mixed $callback Callback function
228
- * @return $this
229
- */
230
- public function add_load_action( $callback ) {
231
- add_filter( 'meta_box_load-' . $this->get_setting( 'page_slug' ), $callback );
232
- return $this;
233
- }
234
-
235
- /**
236
- * Add callback to "meta_box_load" action, applied to all instances of this class
237
- * @param mixed $callback Callback function
238
- * @static
239
- */
240
- public static function add_global_load_action( $callback ) {
241
- add_filter( 'meta_box_load', $callback );
242
- }
243
-
244
- /**
245
- * Add callback to "meta_box_screen_settings-[page]" filter, only applied for this page/object
246
- * @param mixed $callback Callback function
247
- * @return $this
248
- */
249
- public function add_screen_settings_filter( $callback ) {
250
- add_filter( 'meta_box_screen_settings-' . $this->get_setting( 'page_slug' ), $callback );
251
- return $this;
252
- }
253
-
254
- /**
255
- * Add callback to "meta_box_contextual_help-[page]" filter, only applied for this page/object
256
- * @param mixed $callback Callback function
257
- * @return $this
258
- */
259
- public function add_contextual_help_filter( $callback ) {
260
- add_filter( 'meta_box_contextual_help-' . $this->get_setting( 'page_slug' ), $callback );
261
- return $this;
262
- }
263
-
264
- /**
265
- * Add callback to "meta_box_page_title-[page]" filter, only applied for this page/object
266
- * @param mixed $callback Callback function
267
- * @return $this
268
- */
269
- public function add_title_filter( $callback ) {
270
- add_filter( 'meta_box_page_title-' . $this->get_setting( 'page_slug' ), $callback );
271
- return $this;
272
- }
273
-
274
- /**
275
- * Add callback to "meta_box_page_content" filter, applied to all instances of this class
276
- * @param mixed $callback Callback function
277
- * @static
278
- */
279
- public static function add_global_title_filter( $callback ) {
280
- add_filter( 'meta_box_page_title', $callback );
281
- }
282
-
283
- /**
284
- * Add callback to "meta_box_page_content-[page]" filter, only applied for this page/object
285
- * @param mixed $callback Callback function
286
- * @return $this
287
- */
288
- public function add_content_filter( $callback ) {
289
- add_filter( 'meta_box_page_title-' . $this->get_setting( 'page_slug' ), $callback );
290
- return $this;
291
- }
292
-
293
- /**
294
- * Add callback to "meta_box_page_content" filter, applied to all instances of this class
295
- * @param mixed $callback Callback function
296
- * @static
297
- */
298
- public static function add_global_content_filter( $callback ) {
299
- add_filter( 'meta_box_page_title', $callback );
300
- }
301
-
302
- /**
303
- * Admin menu callback
304
- */
305
- public function call_admin_menu() {
306
- // add page
307
- switch ( $this->get_setting( 'add_page_method' ) ) {
308
- case 'add_menu_page':
309
- $this->pagehook = add_menu_page(
310
- $this->get_setting( 'page_title' ),
311
- $this->get_setting( 'menu_title' ),
312
- $this->get_setting( 'capability' ),
313
- $this->get_setting( 'page_slug' ),
314
- array( $this, 'call_page_content' ),
315
- $this->get_setting( 'icon_url' ),
316
- $this->get_setting( 'position' )
317
- );
318
- break;
319
-
320
- case 'add_object_page':
321
- $this->pagehook = add_object_page(
322
- $this->get_setting( 'page_title' ),
323
- $this->get_setting( 'menu_title' ),
324
- $this->get_setting( 'capability' ),
325
- $this->get_setting( 'page_slug' ),
326
- array( $this, 'call_page_content' ),
327
- $this->get_setting( 'icon_url' )
328
- );
329
- break;
330
-
331
- case 'add_submenu_page':
332
- $this->pagehook = add_submenu_page(
333
- $this->get_setting( 'parent_slug' ),
334
- $this->get_setting( 'page_title' ),
335
- $this->get_setting( 'menu_title' ),
336
- $this->get_setting( 'capability' ),
337
- $this->get_setting( 'page_slug' ),
338
- array( $this, 'call_page_content' )
339
- );
340
- break;
341
-
342
- case 'add_options_page':
343
- default:
344
- $this->pagehook = add_options_page(
345
- $this->get_setting( 'page_title' ),
346
- $this->get_setting( 'menu_title' ),
347
- $this->get_setting( 'capability' ),
348
- $this->get_setting( 'page_slug' ),
349
- array( $this, 'call_page_content' )
350
- );
351
- break;
352
-
353
- }
354
-
355
- // execute action
356
- do_action( 'meta_box_load', $this );
357
-
358
- // load page
359
- add_action( 'load-' . $this->pagehook, array( $this, 'call_load_page' ) );
360
- }
361
-
362
- /**
363
- * Admin head callback
364
- */
365
- public function call_admin_head() {
366
- ?>
367
- <style type="text/css">
368
- .postbox-container { padding-right:1%; float:left ; /* for WP < 3.1 */ }
369
- .postbox-container .meta-box-sortables { min-height:200px; }
370
- .postbox-container .postbox { min-width:0; }
371
- .postbox-container .postbox .inside { margin:10px 0 ; padding:0 10px; } /* for WP < 3.2 */
372
- </style>
373
- <?php
374
- }
375
-
376
- /**
377
- * Load page callback
378
- */
379
- public function call_load_page() {
380
- // execute action
381
- do_action( 'meta_box_load-' . $this->get_setting( 'page_slug' ), $this );
382
-
383
- // add script for meta boxes
384
- wp_enqueue_script( 'postbox' );
385
-
386
- // add to admin head
387
- add_action( 'admin_head', array( $this, 'call_admin_head' ) );
388
-
389
- // add screen settings filter
390
- add_filter( 'screen_settings', array( $this, 'call_screen_settings') );
391
-
392
- // add help text
393
- add_filter( 'contextual_help', array( $this, 'call_contextual_help' ) );
394
-
395
- // columns
396
- if ( function_exists( 'add_screen_option' ) ) {
397
- $count = count( $this->get_setting( 'column_widths' ) );
398
-
399
- add_screen_option( 'layout_columns', array(
400
- 'max' => $count,
401
- 'default' => min( $count, $this->get_setting( 'default_columns' ) )
402
- ));
403
- }
404
-
405
- // add meta boxes
406
- $nr = 0;
407
- foreach ( $this->meta_boxes AS $box ) {
408
- $title = $box[ 'title' ];
409
- $id = ( isset( $box[ 'id' ] ) ) ? $box[ 'id' ] : sanitize_title_with_dashes( $title .'-'. ++$nr, 'meta-box-' . $nr );
410
- $callback = ( is_string( $box[ 'callback' ] ) && method_exists( $this, $box[ 'callback' ] ) ) ? array( $this, $box[ 'callback' ] ) : $box[ 'callback' ];
411
- $context = $box[ 'context' ];
412
- $priority = $box[ 'priority' ];
413
-
414
- // set context
415
- if ( $context == 2 OR strtolower( $context ) == 'side' ) {
416
- $context = 'side';
417
- } elseif ( $context == 3 OR strtolower( $context ) == 'column3' ) {
418
- $context = 'column3';
419
- } elseif ( $context == 4 OR strtolower( $context ) == 'column4' ) {
420
- $context = 'column4';
421
- } else { // default
422
- $context = 'normal';
423
- }
424
-
425
- // add meta box
426
- add_meta_box( $id, $title, $callback, $this->pagehook, $context, $priority ); // $callback_args, doesn't seem to work
427
- }
428
- }
429
-
430
- /**
431
- * Screen settings (callback)
432
- * @param string $content
433
- * @return string
434
- */
435
- public function call_screen_settings( $content ) {
436
- if ( self::get_current_screen()->id == convert_to_screen( $this->pagehook )->id ) {
437
- // apply filters for this meta box page
438
- $content = apply_filters( 'meta_box_screen_settings-' . $this->get_setting( 'page_slug' ), $content, $this->get_current_screen(), $this );
439
- }
440
-
441
- return $content;
442
- }
443
-
444
- /**
445
- * Contextual help (callback)
446
- * @param string $content
447
- * @return string
448
- */
449
- public function call_contextual_help( $content ) {
450
- $current_screen = $this->get_current_screen();
451
-
452
- if ( $current_screen->id == convert_to_screen( $this->pagehook )->id ) {
453
- // apply filters for this meta box page
454
- $content = apply_filters( 'meta_box_contextual_help-' . $this->get_setting( 'page_slug' ), $content, $current_screen->id, $current_screen, $this );
455
- }
456
-
457
- return $content;
458
- }
459
-
460
- /**
461
- * Display admin page content (callback)
462
- */
463
- public function call_page_content() {
464
- echo '<div class="wrap">';
465
-
466
- // page title
467
- $meta_boxes_page_title = apply_filters( 'meta_box_page_title', $this->get_page_title(), $this );
468
- echo apply_filters( 'meta_box_page_title-' . $this->get_setting( 'page_slug' ), $meta_boxes_page_title, $this );
469
-
470
- // page content
471
- $meta_boxes_content = apply_filters( 'meta_box_page_content', $this->get_page_content(), $this );
472
- echo apply_filters( 'meta_box_page_content-' . $this->get_setting( 'page_slug' ), $meta_boxes_content, $this );
473
-
474
- echo '</div>';
475
- }
476
-
477
- /**
478
- * Get page title
479
- * Can be changed by using adding filters, see add_title_filter()
480
- * @return string
481
- */
482
- private function get_page_title() {
483
- return '<h2>' . get_admin_page_title() . '</h2>';
484
- }
485
-
486
- /**
487
- * Get meta boxes content. Can be changed by adding filters, see add_content_filter()
488
- * @return string
489
- */
490
- private function get_page_content() {
491
- return self::get_ob_callback( array( $this, 'show_meta_boxes' ) );
492
- }
493
-
494
- /**
495
- * Display meta boxes content
496
- */
497
- private function show_meta_boxes() {
498
- $opt_column_widths = $this->get_setting( 'column_widths' );
499
- $hide2 = $hide3 = $hide4 = '';
500
- switch ( self::get_screen_layout_columns( $this->get_setting( 'default_columns' ) ) ) {
501
- case 4:
502
- $column_widths = $opt_column_widths[ 4 ];
503
- break;
504
- case 3:
505
- $column_widths = $opt_column_widths[ 3 ];
506
- $hide4 = 'display:none;';
507
- break;
508
- case 2:
509
- $column_widths = $opt_column_widths[ 2 ];
510
- $hide3 = $hide4 = 'display:none;';
511
- break;
512
- default:
513
- $column_widths = $opt_column_widths[ 1 ];
514
- $hide2 = $hide3 = $hide4 = 'display:none;';
515
- }
516
-
517
- $column_widths = array_pad( $column_widths, 4, 0 );
518
- ?>
519
- <div id='<?php echo $this->pagehook ?>-widgets' class='metabox-holder'>
520
- <div class='postbox-container' style='width:<?php echo $column_widths[0] ?>%'>
521
- <?php do_meta_boxes( $this->pagehook, 'normal', '' ); ?>
522
- </div>
523
-
524
- <div class='postbox-container' style='<?php echo $hide2 ?>width:<?php echo $column_widths[1] ?>%'>
525
- <?php do_meta_boxes( $this->pagehook, 'side', '' ); ?>
526
- </div>
527
-
528
- <div class='postbox-container' style='<?php echo $hide3 ?>width:<?php echo $column_widths[2] ?>%'>
529
- <?php do_meta_boxes( $this->pagehook, 'column3', '' ); ?>
530
- </div>
531
-
532
- <div class='postbox-container' style='<?php echo $hide4 ?>width:<?php echo $column_widths[3] ?>%'>
533
- <?php do_meta_boxes( $this->pagehook, 'column4', '' ); ?>
534
- </div>
535
- </div>
536
-
537
- <form style="display:none" method="get" action="">
538
- <?php wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); ?>
539
- <?php wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); ?>
540
- </form>
541
-
542
- <script type="text/javascript">
543
- //<![CDATA[
544
- jQuery( document ).ready( function( $ ){
545
- var columnWidths = <?php echo json_encode( $opt_column_widths ) ?>,
546
- $boxes = $( '.postbox-container' ),
547
- setColumnWidths = function () {
548
- var c = $( 'input[name="screen_columns"]:checked' ).val();
549
-
550
- // first hide all boxes
551
- $boxes.hide();
552
-
553
- // set width and show boxes
554
- for ( var x = 0; x < columnWidths[ c ].length; x++ ) {
555
- $boxes.eq( x )
556
- .css( 'width', columnWidths[ c ][ x ]+ '%' )
557
- .show();
558
- }
559
- };
560
-
561
- // radio screen columns
562
- $( 'input[name="screen_columns"]' )
563
- .click(function(){
564
- if ( $( 'input[name="screen_columns"]:checked' ).val() == $( this ).val() ) {
565
- setTimeout(function(){
566
- setColumnWidths();
567
- }, 1 );
568
- }
569
- })
570
- .change(function( e ){
571
- setColumnWidths();
572
-
573
- // prevent
574
- e.stopImmediatePropagation();
575
- });
576
-
577
- // trigger change event of selected column
578
- $( 'input[name="screen_columns"]:checked' ).change();
579
-
580
- <?php if ( self::wp_version( '3.2', '<' ) ): ?>
581
- // for WP < 3.2
582
-
583
- // close postboxes that should be closed
584
- $( '.if-js-closed' ).removeClass( 'if-js-closed' ).addClass( 'closed' );
585
-
586
- // Loading saved screen settings
587
- postboxes.add_postbox_toggles( '<?php echo $this->pagehook ?>' );
588
- <?php endif; ?>
589
- });
590
- //]]>
591
- </script>
592
- <?php
593
- }
594
-
595
- /**
596
- * Static helpers
597
- */
598
-
599
- /**
600
- * Get content displayed by given callback
601
- * @param mixed $callback
602
- * @return string
603
- * @static
604
- */
605
- public static function get_ob_callback( $callback ) {
606
- // start output buffer
607
- ob_start();
608
-
609
- // call callback
610
- call_user_func( $callback );
611
-
612
- // get the view content
613
- $content = ob_get_contents();
614
-
615
- // clean output buffer
616
- ob_end_clean();
617
-
618
- return $content;
619
- }
620
-
621
- /**
622
- * Get current version of WP or compare versions
623
- * @global string $wp_version
624
- * @return mixed
625
- * @static
626
- */
627
- public static function wp_version( $compare_version = NULL, $operator = NULL ) {
628
- global $wp_version;
629
- $cur_wp_version = preg_replace( '/-.*$/', '', $wp_version );
630
-
631
- if ( $compare_version === NULL )
632
- return $cur_wp_version;
633
-
634
- // check comparison
635
- return version_compare( $cur_wp_version, $compare_version, $operator );
636
- }
637
-
638
- /**
639
- * Return global WP value of $screen_layout_columns
640
- * @global integer $screen_layout_columns
641
- * @return integer
642
- * @static
643
- */
644
- public static function get_screen_layout_columns( $default = NULL ) {
645
- global $screen_layout_columns;
646
- return ( empty( $screen_layout_columns ) ) ? $default : $screen_layout_columns;
647
- }
648
-
649
- /**
650
- * Return global WP value of $current_screen
651
- * @global string $current_screen
652
- * @return string
653
- * @static
654
- */
655
- public static function get_current_screen( $default = NULL ) {
656
- global $current_screen;
657
- return $current_screen;
658
- }
659
-
660
- } // End WP_Meta_Box_Page_01 Class
661
-
662
- endif;
663
-
664
  /* ommit PHP closing tag, to prevent unwanted whitespace at the end of the parts generated by the included files */
1
+ <?php defined( 'ABSPATH' ) OR die( 'No direct access.' );
2
+ if ( ! class_exists( 'WP_Meta_Box_Page_01' ) ):
3
+ /**
4
+ * WP_Meta_Box_Page_01 Class
5
+ *
6
+ * Creating a dashboard-like admin page with meta boxes
7
+ *
8
+ * Requires WordPress 3.0+ and PHP 5.2+
9
+ *
10
+ * Custom Actions:
11
+ * - load action params: (1) this object
12
+ * - meta_box_load
13
+ * - meta_box_load-[page_slug]
14
+ *
15
+ * Custom Filters:
16
+ * - page_title filter params: (1) default content, (2) this object
17
+ * - meta_box_page_title
18
+ * - meta_box_page_title-[page_slug]
19
+ *
20
+ * - page_content filter params: (1) default content, (2) this object
21
+ * - meta_box_page_content
22
+ * - meta_box_page_content-[page_slug]
23
+ *
24
+ * - screen_settings filter params: (1) default content, (2) current screen object, (3) this object
25
+ * - meta_box_screen_settings
26
+ * - meta_box_screen_settings-[page_slug]
27
+ *
28
+ * - contextual_help filter params: (1) default content, (2) current screen id, (3) current screen object, (4) this object
29
+ * - meta_box_contextual_help
30
+ * - meta_box_contextual_help-[page_slug]
31
+ *
32
+ * @version 0.1
33
+ * @author Victor Villaverde Laan
34
+ * @link http://www.freelancephp.net/
35
+ * @license Dual licensed under the MIT and GPL licenses
36
+ */
37
+ class WP_Meta_Box_Page_01 {
38
+
39
+ /**
40
+ * Default settings
41
+ * @var array
42
+ */
43
+ private static $default_settings = array(
44
+ // Page title
45
+ 'page_title' => NULL, // Default will be set equal to $menu_title
46
+
47
+ // Menu title
48
+ 'menu_title' => NULL, // Default will be set equal to $page_title
49
+
50
+ // Page slug
51
+ 'page_slug' => NULL, // Default will be set to: sanitize_title_with_dashes( $menu_title )
52
+
53
+ // Default number of columns
54
+ 'default_columns' => 2,
55
+
56
+ // Column widths
57
+ 'column_widths' => array(
58
+ 1 => array( 99 ),
59
+ 2 => array( 49, 49 ),
60
+ 3 => array( 32.33, 32.33, 32.33 ),
61
+ 4 => array( 24, 24, 24, 24 ),
62
+ ),
63
+
64
+ // Add page method
65
+ 'add_page_method' => 'add_options_page', // OR: add_menu_page, add_object_page, add_submenu_page
66
+
67
+ // Extra params for the add_page_method
68
+
69
+ // Capability
70
+ 'capability' => 'manage_options', // Optional for all methods
71
+
72
+ // Parent slug
73
+ 'parent_slug' => NULL, // Nescessary when using "add_submenu_page"
74
+
75
+ // Url to the icon to be used for the menu ( around 20 x 20 pixels )
76
+ 'icon_url' => NULL, // Only for "add_menu_page" or "add_object_page"
77
+
78
+ // The position in the menu order this menu should appear
79
+ 'position' => NULL, // Only for "add_menu_page", default on bottom of the menu
80
+ );
81
+
82
+ /**
83
+ * Settings
84
+ * @var array
85
+ */
86
+ private $settings = array();
87
+
88
+ /**
89
+ * Meta boxes
90
+ * @var array
91
+ */
92
+ private $meta_boxes = array();
93
+
94
+ /**
95
+ * Pagehook ( will be set when page is created )
96
+ * @var string
97
+ */
98
+ protected $pagehook = NULL;
99
+
100
+
101
+ /**
102
+ * Initialize
103
+ * @param array $settings Optional
104
+ */
105
+ public function init( $settings = NULL, $load_callback = NULL ) {
106
+ // get settings of child class
107
+ $child_settings = $this->settings;
108
+
109
+ // set settings in 3 steps...
110
+ // (1) first set the default options
111
+ $this->set_setting( self::$default_settings );
112
+
113
+ // (2) set child settings
114
+ if ( ! empty( $child_settings ) )
115
+ $this->set_setting( $child_settings );
116
+
117
+ // (3) set param settings
118
+ if ( $settings !== NULL )
119
+ $this->set_setting( $settings );
120
+
121
+ // actions
122
+ add_action( 'admin_menu', array( $this, 'call_admin_menu' ) );
123
+
124
+ // add load action
125
+ if ( $load_callback !== NULL )
126
+ add_action( 'meta_box_load-'. $this->get_setting( 'page_slug' ), $load_callback );
127
+ }
128
+
129
+ /**
130
+ * Set default setting value
131
+ * @param mixed $key Also possible to give an array of key/value pairs
132
+ * @param mixed $value Optional
133
+ * @static
134
+ */
135
+ public static function set_default_setting( $key, $value = NULL ) {
136
+ if ( is_array( $key ) ) {
137
+ foreach ( $key AS $k => $v )
138
+ self::set_default_setting( $k, $v );
139
+ } else {
140
+ self::$default_settings[ $key ] = $value;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Set setting value
146
+ * @param mixed $key Also possible to give an array of key/value pairs
147
+ * @param mixed $value Optional
148
+ * @return $this For chaining
149
+ */
150
+ public function set_setting( $key, $value = NULL ) {
151
+ if ( is_array( $key ) ) {
152
+ foreach ( $key AS $k => $v )
153
+ $this->set_setting( $k, $v );
154
+ } else {
155
+ $this->settings[ $key ] = $value;
156
+ }
157
+
158
+ // auto-set related prop values
159
+ if ( $value !== NULL ) {
160
+ if ( $key == 'menu_title' AND $this->get_setting( 'page_title' ) === NULL )
161
+ $this->set_setting( 'page_title', $value );
162
+
163
+ if ( $key == 'page_title' AND $this->get_setting( 'menu_title' ) === NULL )
164
+ $this->set_setting( 'menu_title', $value );
165
+
166
+ if ( ( $key == 'menu_title' OR $key == 'page_title' ) AND $this->get_setting( 'page_slug' ) === NULL ) {
167
+ $new_val = $value;
168
+ $new_val = sanitize_title_with_dashes( $new_val );
169
+ $new_val = strtolower( $new_val );
170
+
171
+ // check for valid page_slug
172
+ if ( ! preg_match( '/^[a-z_-]+$/', $new_val ) ) {
173
+ $new_val = str_replace(
174
+ array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 ),
175
+ array( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' ),
176
+ $new_val
177
+ );
178
+
179
+ $new_val = ereg_replace( '[^a-z_-]', '', $new_val );
180
+ }
181
+
182
+ $this->set_setting( 'page_slug', $new_val );
183
+ }
184
+ }
185
+
186
+ return $this;
187
+ }
188
+
189
+ /**
190
+ * Get setting value
191
+ * @param string $key Optional, when NULL will return array of all options
192
+ * @param mixed $default Optional, return default when key cannot be found OR is NULL
193
+ * @return mixed
194
+ */
195
+ public function get_setting( $key = NULL, $default = NULL ) {
196
+ if ( $key === NULL )
197
+ return $this->settings;
198
+
199
+ if ( key_exists( $key, $this->settings ) )
200
+ return ( $this->settings[ $key ] === NULL ) ? $default : $this->settings[ $key ];
201
+
202
+ return $default;
203
+ }
204
+
205
+ /**
206
+ * Helper for adding a meta box
207
+ * @param string $title Title of the meta box
208
+ * @param string $callback Callback for the meta box content
209
+ * @param mixed $context Optional, add meta box to this column (normal = 1, side = 2, column3 = 3, column4 = 4)
210
+ * @param string $priority Optional, the priority within the context where the boxes should show ( 'high', 'core', 'default' or 'low' )
211
+ * @return $this For chaining
212
+ */
213
+ public function add_meta_box( $title, $callback, $context = 'normal', $id = NULL, $priority = 'default' ) {
214
+ $this->meta_boxes[] = array(
215
+ 'id' => $id,
216
+ 'title' => $title,
217
+ 'callback' => $callback,
218
+ 'context' => $context,
219
+ 'priority' => $priority,
220
+ );
221
+
222
+ return $this;
223
+ }
224
+
225
+ /**
226
+ * Add callback to "meta_box_load-[page]" action, only applied for this page/object
227
+ * @param mixed $callback Callback function
228
+ * @return $this
229
+ */
230
+ public function add_load_action( $callback ) {
231
+ add_filter( 'meta_box_load-' . $this->get_setting( 'page_slug' ), $callback );
232
+ return $this;
233
+ }
234
+
235
+ /**
236
+ * Add callback to "meta_box_load" action, applied to all instances of this class
237
+ * @param mixed $callback Callback function
238
+ * @static
239
+ */
240
+ public static function add_global_load_action( $callback ) {
241
+ add_filter( 'meta_box_load', $callback );
242
+ }
243
+
244
+ /**
245
+ * Add callback to "meta_box_screen_settings-[page]" filter, only applied for this page/object
246
+ * @param mixed $callback Callback function
247
+ * @return $this
248
+ */
249
+ public function add_screen_settings_filter( $callback ) {
250
+ add_filter( 'meta_box_screen_settings-' . $this->get_setting( 'page_slug' ), $callback );
251
+ return $this;
252
+ }
253
+
254
+ /**
255
+ * Add callback to "meta_box_contextual_help-[page]" filter, only applied for this page/object
256
+ * @param mixed $callback Callback function
257
+ * @return $this
258
+ */
259
+ public function add_contextual_help_filter( $callback ) {
260
+ add_filter( 'meta_box_contextual_help-' . $this->get_setting( 'page_slug' ), $callback );
261
+ return $this;
262
+ }
263
+
264
+ /**
265
+ * Add callback to "meta_box_page_title-[page]" filter, only applied for this page/object
266
+ * @param mixed $callback Callback function
267
+ * @return $this
268
+ */
269
+ public function add_title_filter( $callback ) {
270
+ add_filter( 'meta_box_page_title-' . $this->get_setting( 'page_slug' ), $callback );
271
+ return $this;
272
+ }
273
+
274
+ /**
275
+ * Add callback to "meta_box_page_content" filter, applied to all instances of this class
276
+ * @param mixed $callback Callback function
277
+ * @static
278
+ */
279
+ public static function add_global_title_filter( $callback ) {
280
+ add_filter( 'meta_box_page_title', $callback );
281
+ }
282
+
283
+ /**
284
+ * Add callback to "meta_box_page_content-[page]" filter, only applied for this page/object
285
+ * @param mixed $callback Callback function
286
+ * @return $this
287
+ */
288
+ public function add_content_filter( $callback ) {
289
+ add_filter( 'meta_box_page_title-' . $this->get_setting( 'page_slug' ), $callback );
290
+ return $this;
291
+ }
292
+
293
+ /**
294
+ * Add callback to "meta_box_page_content" filter, applied to all instances of this class
295
+ * @param mixed $callback Callback function
296
+ * @static
297
+ */
298
+ public static function add_global_content_filter( $callback ) {
299
+ add_filter( 'meta_box_page_title', $callback );
300
+ }
301
+
302
+ /**
303
+ * Admin menu callback
304
+ */
305
+ public function call_admin_menu() {
306
+ // add page
307
+ switch ( $this->get_setting( 'add_page_method' ) ) {
308
+ case 'add_menu_page':
309
+ $this->pagehook = add_menu_page(
310
+ $this->get_setting( 'page_title' ),
311
+ $this->get_setting( 'menu_title' ),
312
+ $this->get_setting( 'capability' ),
313
+ $this->get_setting( 'page_slug' ),
314
+ array( $this, 'call_page_content' ),
315
+ $this->get_setting( 'icon_url' ),
316
+ $this->get_setting( 'position' )
317
+ );
318
+ break;
319
+
320
+ case 'add_object_page':
321
+ $this->pagehook = add_object_page(
322
+ $this->get_setting( 'page_title' ),
323
+ $this->get_setting( 'menu_title' ),
324
+ $this->get_setting( 'capability' ),
325
+ $this->get_setting( 'page_slug' ),
326
+ array( $this, 'call_page_content' ),
327
+ $this->get_setting( 'icon_url' )
328
+ );
329
+ break;
330
+
331
+ case 'add_submenu_page':
332
+ $this->pagehook = add_submenu_page(
333
+ $this->get_setting( 'parent_slug' ),
334
+ $this->get_setting( 'page_title' ),
335
+ $this->get_setting( 'menu_title' ),
336
+ $this->get_setting( 'capability' ),
337
+ $this->get_setting( 'page_slug' ),
338
+ array( $this, 'call_page_content' )
339
+ );
340
+ break;
341
+
342
+ case 'add_options_page':
343
+ default:
344
+ $this->pagehook = add_options_page(
345
+ $this->get_setting( 'page_title' ),
346
+ $this->get_setting( 'menu_title' ),
347
+ $this->get_setting( 'capability' ),
348
+ $this->get_setting( 'page_slug' ),
349
+ array( $this, 'call_page_content' )
350
+ );
351
+ break;
352
+
353
+ }
354
+
355
+ // execute action
356
+ do_action( 'meta_box_load', $this );
357
+
358
+ // load page
359
+ add_action( 'load-' . $this->pagehook, array( $this, 'call_load_page' ) );
360
+ }
361
+
362
+ /**
363
+ * Admin head callback
364
+ */
365
+ public function call_admin_head() {
366
+ ?>
367
+ <style type="text/css">
368
+ .postbox-container { padding-right:1%; float:left ; /* for WP < 3.1 */ }
369
+ .postbox-container .meta-box-sortables { min-height:200px; }
370
+ .postbox-container .postbox { min-width:0; }
371
+ .postbox-container .postbox .inside { margin:10px 0 ; padding:0 10px; } /* for WP < 3.2 */
372
+ </style>
373
+ <?php
374
+ }
375
+
376
+ /**
377
+ * Load page callback
378
+ */
379
+ public function call_load_page() {
380
+ // execute action
381
+ do_action( 'meta_box_load-' . $this->get_setting( 'page_slug' ), $this );
382
+
383
+ // add script for meta boxes
384
+ wp_enqueue_script( 'postbox' );
385
+
386
+ // add to admin head
387
+ add_action( 'admin_head', array( $this, 'call_admin_head' ) );
388
+
389
+ // add screen settings filter
390
+ add_filter( 'screen_settings', array( $this, 'call_screen_settings') );
391
+
392
+ // add help text
393
+ add_filter( 'contextual_help', array( $this, 'call_contextual_help' ) );
394
+
395
+ // columns
396
+ if ( function_exists( 'add_screen_option' ) ) {
397
+ $count = count( $this->get_setting( 'column_widths' ) );
398
+
399
+ add_screen_option( 'layout_columns', array(
400
+ 'max' => $count,
401
+ 'default' => min( $count, $this->get_setting( 'default_columns' ) )
402
+ ));
403
+ }
404
+
405
+ // add meta boxes
406
+ $nr = 0;
407
+ foreach ( $this->meta_boxes AS $box ) {
408
+ $title = $box[ 'title' ];
409
+ $id = ( isset( $box[ 'id' ] ) ) ? $box[ 'id' ] : sanitize_title_with_dashes( $title .'-'. ++$nr, 'meta-box-' . $nr );
410
+ $callback = ( is_string( $box[ 'callback' ] ) && method_exists( $this, $box[ 'callback' ] ) ) ? array( $this, $box[ 'callback' ] ) : $box[ 'callback' ];
411
+ $context = $box[ 'context' ];
412
+ $priority = $box[ 'priority' ];
413
+
414
+ // set context
415
+ if ( $context == 2 OR strtolower( $context ) == 'side' ) {
416
+ $context = 'side';
417
+ } elseif ( $context == 3 OR strtolower( $context ) == 'column3' ) {
418
+ $context = 'column3';
419
+ } elseif ( $context == 4 OR strtolower( $context ) == 'column4' ) {
420
+ $context = 'column4';
421
+ } else { // default
422
+ $context = 'normal';
423
+ }
424
+
425
+ // add meta box
426
+ add_meta_box( $id, $title, $callback, $this->pagehook, $context, $priority ); // $callback_args, doesn't seem to work
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Screen settings (callback)
432
+ * @param string $content
433
+ * @return string
434
+ */
435
+ public function call_screen_settings( $content ) {
436
+ if ( self::get_current_screen()->id == convert_to_screen( $this->pagehook )->id ) {
437
+ // apply filters for this meta box page
438
+ $content = apply_filters( 'meta_box_screen_settings-' . $this->get_setting( 'page_slug' ), $content, $this->get_current_screen(), $this );
439
+ }
440
+
441
+ return $content;
442
+ }
443
+
444
+ /**
445
+ * Contextual help (callback)
446
+ * @param string $content
447
+ * @return string
448
+ */
449
+ public function call_contextual_help( $content ) {
450
+ $current_screen = $this->get_current_screen();
451
+
452
+ if ( $current_screen->id == convert_to_screen( $this->pagehook )->id ) {
453
+ // apply filters for this meta box page
454
+ $content = apply_filters( 'meta_box_contextual_help-' . $this->get_setting( 'page_slug' ), $content, $current_screen->id, $current_screen, $this );
455
+ }
456
+
457
+ return $content;
458
+ }
459
+
460
+ /**
461
+ * Display admin page content (callback)
462
+ */
463
+ public function call_page_content() {
464
+ echo '<div class="wrap">';
465
+
466
+ // page title
467
+ $meta_boxes_page_title = apply_filters( 'meta_box_page_title', $this->get_page_title(), $this );
468
+ echo apply_filters( 'meta_box_page_title-' . $this->get_setting( 'page_slug' ), $meta_boxes_page_title, $this );
469
+
470
+ // page content
471
+ $meta_boxes_content = apply_filters( 'meta_box_page_content', $this->get_page_content(), $this );
472
+ echo apply_filters( 'meta_box_page_content-' . $this->get_setting( 'page_slug' ), $meta_boxes_content, $this );
473
+
474
+ echo '</div>';
475
+ }
476
+
477
+ /**
478
+ * Get page title
479
+ * Can be changed by using adding filters, see add_title_filter()
480
+ * @return string
481
+ */
482
+ private function get_page_title() {
483
+ return '<h2>' . get_admin_page_title() . '</h2>';
484
+ }
485
+
486
+ /**
487
+ * Get meta boxes content. Can be changed by adding filters, see add_content_filter()
488
+ * @return string
489
+ */
490
+ private function get_page_content() {
491
+ return self::get_ob_callback( array( $this, 'show_meta_boxes' ) );
492
+ }
493
+
494
+ /**
495
+ * Display meta boxes content
496
+ */
497
+ private function show_meta_boxes() {
498
+ $opt_column_widths = $this->get_setting( 'column_widths' );
499
+ $hide2 = $hide3 = $hide4 = '';
500
+ switch ( self::get_screen_layout_columns( $this->get_setting( 'default_columns' ) ) ) {
501
+ case 4:
502
+ $column_widths = $opt_column_widths[ 4 ];
503
+ break;
504
+ case 3:
505
+ $column_widths = $opt_column_widths[ 3 ];
506
+ $hide4 = 'display:none;';
507
+ break;
508
+ case 2:
509
+ $column_widths = $opt_column_widths[ 2 ];
510
+ $hide3 = $hide4 = 'display:none;';
511
+ break;
512
+ default:
513
+ $column_widths = $opt_column_widths[ 1 ];
514
+ $hide2 = $hide3 = $hide4 = 'display:none;';
515
+ }
516
+
517
+ $column_widths = array_pad( $column_widths, 4, 0 );
518
+ ?>
519
+ <div id='<?php echo $this->pagehook ?>-widgets' class='metabox-holder'>
520
+ <div class='postbox-container' style='width:<?php echo $column_widths[0] ?>%'>
521
+ <?php do_meta_boxes( $this->pagehook, 'normal', '' ); ?>
522
+ </div>
523
+
524
+ <div class='postbox-container' style='<?php echo $hide2 ?>width:<?php echo $column_widths[1] ?>%'>
525
+ <?php do_meta_boxes( $this->pagehook, 'side', '' ); ?>
526
+ </div>
527
+
528
+ <div class='postbox-container' style='<?php echo $hide3 ?>width:<?php echo $column_widths[2] ?>%'>
529
+ <?php do_meta_boxes( $this->pagehook, 'column3', '' ); ?>
530
+ </div>
531
+
532
+ <div class='postbox-container' style='<?php echo $hide4 ?>width:<?php echo $column_widths[3] ?>%'>
533
+ <?php do_meta_boxes( $this->pagehook, 'column4', '' ); ?>
534
+ </div>
535
+ </div>
536
+
537
+ <form style="display:none" method="get" action="">
538
+ <?php wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); ?>
539
+ <?php wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); ?>
540
+ </form>
541
+
542
+ <script type="text/javascript">
543
+ //<![CDATA[
544
+ jQuery( document ).ready( function( $ ){
545
+ var columnWidths = <?php echo json_encode( $opt_column_widths ) ?>,
546
+ $boxes = $( '.postbox-container' ),
547
+ setColumnWidths = function () {
548
+ var c = $( 'input[name="screen_columns"]:checked' ).val();
549
+
550
+ // first hide all boxes
551
+ $boxes.hide();
552
+
553
+ // set width and show boxes
554
+ for ( var x = 0; x < columnWidths[ c ].length; x++ ) {
555
+ $boxes.eq( x )
556
+ .css( 'width', columnWidths[ c ][ x ]+ '%' )
557
+ .show();
558
+ }
559
+ };
560
+
561
+ // radio screen columns
562
+ $( 'input[name="screen_columns"]' )
563
+ .click(function(){
564
+ if ( $( 'input[name="screen_columns"]:checked' ).val() == $( this ).val() ) {
565
+ setTimeout(function(){
566
+ setColumnWidths();
567
+ }, 1 );
568
+ }
569
+ })
570
+ .change(function( e ){
571
+ setColumnWidths();
572
+
573
+ // prevent
574
+ e.stopImmediatePropagation();
575
+ });
576
+
577
+ // trigger change event of selected column
578
+ $( 'input[name="screen_columns"]:checked' ).change();
579
+
580
+ <?php if ( self::wp_version( '3.2', '<' ) ): ?>
581
+ // for WP < 3.2
582
+
583
+ // close postboxes that should be closed
584
+ $( '.if-js-closed' ).removeClass( 'if-js-closed' ).addClass( 'closed' );
585
+
586
+ // Loading saved screen settings
587
+ postboxes.add_postbox_toggles( '<?php echo $this->pagehook ?>' );
588
+ <?php endif; ?>
589
+ });
590
+ //]]>
591
+ </script>
592
+ <?php
593
+ }
594
+
595
+ /**
596
+ * Static helpers
597
+ */
598
+
599
+ /**
600
+ * Get content displayed by given callback
601
+ * @param mixed $callback
602
+ * @return string
603
+ * @static
604
+ */
605
+ public static function get_ob_callback( $callback ) {
606
+ // start output buffer
607
+ ob_start();
608
+
609
+ // call callback
610
+ call_user_func( $callback );
611
+
612
+ // get the view content
613
+ $content = ob_get_contents();
614
+
615
+ // clean output buffer
616
+ ob_end_clean();
617
+
618
+ return $content;
619
+ }
620
+
621
+ /**
622
+ * Get current version of WP or compare versions
623
+ * @global string $wp_version
624
+ * @return mixed
625
+ * @static
626
+ */
627
+ public static function wp_version( $compare_version = NULL, $operator = NULL ) {
628
+ global $wp_version;
629
+ $cur_wp_version = preg_replace( '/-.*$/', '', $wp_version );
630
+
631
+ if ( $compare_version === NULL )
632
+ return $cur_wp_version;
633
+
634
+ // check comparison
635
+ return version_compare( $cur_wp_version, $compare_version, $operator );
636
+ }
637
+
638
+ /**
639
+ * Return global WP value of $screen_layout_columns
640
+ * @global integer $screen_layout_columns
641
+ * @return integer
642
+ * @static
643
+ */
644
+ public static function get_screen_layout_columns( $default = NULL ) {
645
+ global $screen_layout_columns;
646
+ return ( empty( $screen_layout_columns ) ) ? $default : $screen_layout_columns;
647
+ }
648
+
649
+ /**
650
+ * Return global WP value of $current_screen
651
+ * @global string $current_screen
652
+ * @return string
653
+ * @static
654
+ */
655
+ public static function get_current_screen( $default = NULL ) {
656
+ global $current_screen;
657
+ return $current_screen;
658
+ }
659
+
660
+ } // End WP_Meta_Box_Page_01 Class
661
+
662
+ endif;
663
+
664
  /* ommit PHP closing tag, to prevent unwanted whitespace at the end of the parts generated by the included files */
includes/wp-plugin-dev-classes/class-wp-option-forms.php CHANGED
@@ -1,360 +1,360 @@
1
- <?php defined( 'ABSPATH' ) OR die( 'No direct access.' );
2
- if ( ! class_exists( 'WP_Option_Forms_01' ) ):
3
- /**
4
- * WP_Option_Forms_01 Class
5
- *
6
- * Simple class for creating option forms of a the same option_group.
7
- * Also with Ajax save support.
8
- *
9
- * Requires WordPress 3.0+ and PHP 5.2+
10
- *
11
- * @version 0.1
12
- * @author Victor Villaverde Laan
13
- * @link http://www.freelancephp.net/
14
- * @license Dual licensed under the MIT and GPL licenses
15
- */
16
- class WP_Option_Forms_01 {
17
-
18
- /**
19
- * Name used as prefix for saving option names
20
- * @var string
21
- */
22
- protected $name = NULL;
23
-
24
- /**
25
- * Option names and values
26
- * @var string
27
- */
28
- protected $options = array();
29
-
30
- /**
31
- * Current option name
32
- * @var string
33
- */
34
- protected $current_option = NULL;
35
-
36
-
37
- /**
38
- * Constructor
39
- * @param array $name
40
- * @param array $options Optional
41
- */
42
- public function __construct( $name, $options = array() ) {
43
- $this->name = sanitize_title_with_dashes( $name );
44
-
45
- // set option names
46
- foreach ( $options AS $option_name => $values ) {
47
- $this->add_option( $option_name, $values );
48
- }
49
-
50
- // actions
51
- add_action( 'wp_ajax_wpof_update_options', array( $this, 'call_wp_ajax' ) );
52
- add_action( 'admin_menu', array( $this, 'call_admin_menu' ) );
53
- }
54
-
55
- /**
56
- * Admin menu callback
57
- */
58
- public function call_admin_menu() {
59
- // Register settings
60
- foreach ( $this->options AS $option_name => $values ) {
61
- register_setting( $option_name, $option_name );
62
- }
63
-
64
- // script
65
- wp_enqueue_script( 'option-forms', plugins_url( 'js/wp-option-forms.js', WP_EXTERNAL_LINKS_FILE ), array( 'jquery' ), '1.0' );
66
- }
67
-
68
- /**
69
- * Ajax call for saving option values
70
- */
71
- public function call_wp_ajax() {
72
- check_ajax_referer( 'wpof_update_options', 'wpof-nonce' );
73
-
74
- $option_name = $_POST[ 'ajax_option_name' ];
75
- $value = NULL;
76
-
77
- if ( isset( $_POST[ $option_name ] ) )
78
- $value = $_POST[ $option_name ];
79
-
80
- if ( ! is_array( $value ) )
81
- $value = trim( $value );
82
-
83
- $value = stripslashes_deep( $value );
84
-
85
- update_option( $option_name, $value );
86
-
87
- die( '1' );
88
- }
89
-
90
- /**
91
- * Add option (or reset option when already exists)
92
- * @param string $option_name
93
- * @param array $default_values Optional
94
- * @return this
95
- */
96
- public function add_option( $option_name, $default_values = array() ) {
97
- // set values
98
- $saved_values = get_option( $this->name .'-'. $option_name );
99
-
100
- if ( empty( $saved_values ) ) {
101
- foreach ( $default_values AS $key => $value )
102
- $values[ $key ] = $value;
103
- } else {
104
- foreach ( $default_values AS $key => $value )
105
- $values[ $key ] = '';
106
-
107
- foreach ( $saved_values AS $key => $value )
108
- $values[ $key ] = $value;
109
- }
110
-
111
- // option and values
112
- $this->options[ $this->name .'-'. $option_name ] = $values;
113
- return $this;
114
- }
115
-
116
- /**
117
- * Set current option to use
118
- * @param string $option_name
119
- * @return this
120
- */
121
- public function set_current_option( $option_name ) {
122
- $this->current_option = $this->name .'-'. $option_name;
123
- return $this;
124
- }
125
-
126
- /**
127
- * Get opening form with all nescessary WP fields
128
- * @param boolean $ajaxSave Optional
129
- * @param array $attrs Optional
130
- * @return string
131
- */
132
- public function open_form( $ajaxSave = TRUE, $attrs = array() ) {
133
- // set class for ajax or non-ajax form
134
- $attrs[ 'class' ] = ( ( $ajaxSave ) ? 'ajax-form' : 'no-ajax-form' )
135
- . ( ( key_exists( 'class', $attrs ) ) ? ' '. $attrs[ 'class' ] : '' );
136
-
137
- // show start form
138
- $html = '';
139
- $html .= '<form method="post" action="options.php" '. $this->attrs( $attrs ) .'>';
140
-
141
- if ( $ajaxSave ) {
142
- $html .= wp_nonce_field( 'wpof_update_options', 'wpof-nonce', FALSE, FALSE );
143
- $html .= '<input type="hidden" name="action" value="wpof_update_options" />';
144
- $html .= '<input type="hidden" name="ajax_option_name" value="'. $this->current_option .'" />';
145
-
146
- // instead of using settings_fields();
147
- $html .= '<input type="hidden" name="option_page" value="' . esc_attr( $this->current_option ) . '" />';
148
- $html .= wp_nonce_field( $this->current_option . '-options', '_wpnonce', TRUE, FALSE );
149
- } else {
150
- // instead of using settings_fields();
151
- $html .= '<input type="hidden" name="option_page" value="' . esc_attr( $this->current_option ) . '" />';
152
- $html .= '<input type="hidden" name="action" value="update" />';
153
- $html .= wp_nonce_field( $this->current_option . '-options', '_wpnonce', TRUE, FALSE );
154
- }
155
-
156
- return $html;
157
- }
158
-
159
- /**
160
- * Get script for saving screen option
161
- * @param string $option_name
162
- * @param string $key
163
- * @return string
164
- */
165
- public function open_screen_option( $option_name, $key ) {
166
- $this->set_current_option( $option_name );
167
-
168
- $html = '';
169
- $html .= '<script type="text/javascript">' . "\n";
170
- $html .= '//<![CDATA[' . "\n";
171
- $html .= 'jQuery( document ).ready( function( $ ){' . "\n";
172
- $html .= "\t" . '// save screen option' . "\n";
173
- $html .= "\t" . '$( "#screen-meta #'. $key .'" )' . "\n";
174
- $html .= "\t\t" . '.change(function(){' . "\n";
175
- $html .= "\t\t\t" . 'var self = this;' . "\n";
176
- $html .= "\t\t\t" . '$.post( ajaxurl, {' . "\n";
177
- $html .= "\t\t\t\t" . 'action: "wpof_update_options",' . "\n";
178
- $html .= "\t\t\t\t" . '"wpof-nonce": "'. wp_create_nonce( 'wpof_update_options' ) .'",' . "\n";
179
- $html .= "\t\t\t\t" . 'ajax_option_name: "'. $this->current_option .'",' . "\n";
180
- $html .= "\t\t\t\t" . '"'. $this->field_name( $key ) .'": $( this ).val()' . "\n";
181
- $html .= "\t\t\t" . '}, function () {' . "\n";
182
- $html .= "\t\t\t\t" . '$( self ).trigger( "ajax_updated" );' . "\n";
183
- $html .= "\t\t\t" . '});' . "\n";
184
- $html .= "\t\t" . '});' . "\n";
185
- $html .= '});' . "\n";
186
- $html .= '//]]>' . "\n";
187
- $html .= '</script>' . "\n";
188
-
189
- return $html;
190
- }
191
-
192
- /**
193
- * Get closing form
194
- * @return string
195
- */
196
- public function close_form() {
197
- return '</form>';
198
- }
199
-
200
- /**
201
- * Text field
202
- * @param string $key
203
- * @param array $attrs Optional
204
- * @return string
205
- */
206
- public function text( $key, $attrs = array() ) {
207
- if ( ! key_exists( 'class', $attrs ) )
208
- $attrs[ 'class' ] = 'regular-text';
209
-
210
- return '<input type="text" '. $this->attrs( $attrs, $key, $this->value( $key ) ) .' />';
211
- }
212
-
213
- /**
214
- * Text field
215
- * @param string $key
216
- * @param array $attrs Optional
217
- * @return string
218
- */
219
- public function textarea( $key, $attrs = array() ) {
220
- if ( ! key_exists( 'class', $attrs ) )
221
- $attrs[ 'class' ] = 'large-text';
222
-
223
- return '<textarea '. $this->attrs( $attrs, $key ) .'>'. $this->value( $key ) .'</textarea>';
224
- }
225
-
226
- /**
227
- * Radio field
228
- * @param string $key
229
- * @param mixed $value
230
- * @param array $attrs Optional
231
- * @return string
232
- */
233
- public function radio( $key, $value, $attrs = array() ) {
234
- $checked = ( $value == $this->value( $key ) ) ? ' checked="checked"' : '';
235
- return '<input type="radio" '. $this->attrs( $attrs, $key, $value )
236
- . $checked . ' />';
237
- }
238
-
239
- /**
240
- * Checkbox field
241
- * @param string $key
242
- * @param mixed $value
243
- * @param array $attrs Optional
244
- * @return string
245
- */
246
- public function checkbox( $key, $value, $attrs = array() ) {
247
- $checked = ( $value == $this->value( $key ) ) ? ' checked="checked"' : '';
248
- return '<input type="checkbox" '. $this->attrs( $attrs, $key, $value )
249
- . $checked . ' />';
250
- }
251
-
252
- /**
253
- * Select field
254
- * @param string $key
255
- * @param array $options Optional
256
- * @param array $attrs Optional
257
- * @return string
258
- */
259
- public function select( $key, $options = array(), $attrs = array() ) {
260
- $html = '<select '. $this->attrs( $attrs, $key ) .'>';
261
-
262
- foreach ( $options AS $value => $label ) {
263
- $selected = ( $value == $this->value( $key ) ) ? ' selected="selected"' : '';
264
- $html .= '<option value="'. $value .'"'. $selected .'>'. $label .'</option>';
265
- }
266
-
267
- $html .= '</select>';
268
- return $html;
269
- }
270
-
271
- /**
272
- * Submit button
273
- * @param array $attrs Optional
274
- * @return string
275
- */
276
- public function submit( $attrs = array() ) {
277
- // set class attr
278
- $attrs[ 'class' ] = 'button-primary'. ( ( key_exists( 'class', $attrs ) ) ? ' '. $attrs[ 'class' ] : '' );
279
-
280
- // show submit
281
- $html = '';
282
- $html .= '<p class="button-controls" style="text-align:right;">';
283
- $html .= '<img alt="" title="" class="ajax-feedback" src="'. get_bloginfo( 'url' ) .'/wp-admin/images/wpspin_light.gif" style="visibility: hidden;" />';
284
- $html .= '<input type="submit" '. $this->attrs( $attrs, '', __( 'Save Changes' ) ) .' />';
285
- $html .= '</p>';
286
- return $html;
287
- }
288
-
289
- /**
290
- * Get field name of given key
291
- * @param string $key
292
- * @return string
293
- */
294
- public function field_name( $key ) {
295
- return $this->current_option . '[' . $key . ']';
296
- }
297
-
298
- /**
299
- * Get value of given option key
300
- * @param string $key
301
- * @param mixed $default_value Optional
302
- * @param boolean $option_name Optional, search in given option_name instead of the current option
303
- * @return mixed
304
- */
305
- public function value( $key, $default_value = NULL, $option_name = NULL ) {
306
- if ( $option_name === NULL ) {
307
- $option = $this->current_option;
308
- } else {
309
- $option = $this->name . '-' . $option_name;
310
- }
311
-
312
- if (!isset($this->options[ $option ])) {
313
- return $default_value;
314
- }
315
-
316
- $values = $this->options[ $option ];
317
-
318
- return ( is_array( $values ) AND key_exists( $key, $values ) AND $values[ $key ] !== NULL ) ? $values[ $key ] : $default_value;
319
- }
320
-
321
- /**
322
- * Delete and unregister option
323
- */
324
- public function delete_options() {
325
- foreach ( $this->options AS $option_name => $values ) {
326
- delete_option( $option_name );
327
- }
328
- }
329
-
330
- /**
331
- * Get string of given attributes
332
- * @param array $attrs
333
- * @param string $key Optional
334
- * @param mixed $value Optional
335
- * @return string
336
- */
337
- protected function attrs( $attrs, $key = NULL, $value = NULL ) {
338
- $str = '';
339
-
340
- // set name, id, value attr
341
- if ( $key !== NULL ) {
342
- $str .= 'name="' . $this->field_name( $key ) .'" ';
343
- if ( ! key_exists( 'id', $attrs ) )
344
- $str .= 'id="' . $key .'" ';
345
- }
346
-
347
- if ( $value !== NULL )
348
- $str .= 'value="' . $value .'" ';
349
-
350
- foreach ( $attrs AS $attr => $value )
351
- $str .= $attr .'="'. $value .'" ';
352
-
353
- return $str;
354
- }
355
-
356
- } // End WP_Option_Forms_01
357
-
358
- endif;
359
-
360
  /* ommit PHP closing tag, to prevent unwanted whitespace at the end of the parts generated by the included files */
1
+ <?php defined( 'ABSPATH' ) OR die( 'No direct access.' );
2
+ if ( ! class_exists( 'WP_Option_Forms_01' ) ):
3
+ /**
4
+ * WP_Option_Forms_01 Class
5
+ *
6
+ * Simple class for creating option forms of a the same option_group.
7
+ * Also with Ajax save support.
8
+ *
9
+ * Requires WordPress 3.0+ and PHP 5.2+
10
+ *
11
+ * @version 0.1
12
+ * @author Victor Villaverde Laan
13
+ * @link http://www.freelancephp.net/
14
+ * @license Dual licensed under the MIT and GPL licenses
15
+ */
16
+ class WP_Option_Forms_01 {
17
+
18
+ /**
19
+ * Name used as prefix for saving option names
20
+ * @var string
21
+ */
22
+ protected $name = NULL;
23
+
24
+ /**
25
+ * Option names and values
26
+ * @var string
27
+ */
28
+ protected $options = array();
29
+
30
+ /**
31
+ * Current option name
32
+ * @var string
33
+ */
34
+ protected $current_option = NULL;
35
+
36
+
37
+ /**
38
+ * Constructor
39
+ * @param array $name
40
+ * @param array $options Optional
41
+ */
42
+ public function __construct( $name, $options = array() ) {
43
+ $this->name = sanitize_title_with_dashes( $name );
44
+
45
+ // set option names
46
+ foreach ( $options AS $option_name => $values ) {
47
+ $this->add_option( $option_name, $values );
48
+ }
49
+
50
+ // actions
51
+ add_action( 'wp_ajax_wpof_update_options', array( $this, 'call_wp_ajax' ) );
52
+ add_action( 'admin_menu', array( $this, 'call_admin_menu' ) );
53
+ }
54
+
55
+ /**
56
+ * Admin menu callback
57
+ */
58
+ public function call_admin_menu() {
59
+ // Register settings
60
+ foreach ( $this->options AS $option_name => $values ) {
61
+ register_setting( $option_name, $option_name );
62
+ }
63
+
64
+ // script
65
+ // wp_enqueue_script( 'option-forms', plugins_url( 'js/wp-option-forms.js', WP_EXTERNAL_LINKS_FILE ), array( 'jquery' ), '1.0' );
66
+ }
67
+
68
+ /**
69
+ * Ajax call for saving option values
70
+ */
71
+ public function call_wp_ajax() {
72
+ check_ajax_referer( 'wpof_update_options', 'wpof-nonce' );
73
+
74
+ $option_name = $_POST[ 'ajax_option_name' ];
75
+ $value = NULL;
76
+
77
+ if ( isset( $_POST[ $option_name ] ) )
78
+ $value = $_POST[ $option_name ];
79
+
80
+ if ( ! is_array( $value ) )
81
+ $value = trim( $value );
82
+
83
+ $value = stripslashes_deep( $value );
84
+
85
+ update_option( $option_name, $value );
86
+
87
+ die( '1' );
88
+ }
89
+
90
+ /**
91
+ * Add option (or reset option when already exists)
92
+ * @param string $option_name
93
+ * @param array $default_values Optional
94
+ * @return this
95
+ */
96
+ public function add_option( $option_name, $default_values = array() ) {
97
+ // set values
98
+ $saved_values = get_option( $this->name .'-'. $option_name );
99
+
100
+ if ( empty( $saved_values ) ) {
101
+ foreach ( $default_values AS $key => $value )
102
+ $values[ $key ] = $value;
103
+ } else {
104
+ foreach ( $default_values AS $key => $value )
105
+ $values[ $key ] = '';
106
+
107
+ foreach ( $saved_values AS $key => $value )
108
+ $values[ $key ] = $value;
109
+ }
110
+
111
+ // option and values
112
+ $this->options[ $this->name .'-'. $option_name ] = $values;
113
+ return $this;
114
+ }
115
+
116
+ /**
117
+ * Set current option to use
118
+ * @param string $option_name
119
+ * @return this
120
+ */
121
+ public function set_current_option( $option_name ) {
122
+ $this->current_option = $this->name .'-'. $option_name;
123
+ return $this;
124
+ }
125
+
126
+ /**
127
+ * Get opening form with all nescessary WP fields
128
+ * @param boolean $ajaxSave Optional
129
+ * @param array $attrs Optional
130
+ * @return string
131
+ */
132
+ public function open_form( $ajaxSave = TRUE, $attrs = array() ) {
133
+ // set class for ajax or non-ajax form
134
+ $attrs[ 'class' ] = ( ( $ajaxSave ) ? 'ajax-form' : 'no-ajax-form' )
135
+ . ( ( key_exists( 'class', $attrs ) ) ? ' '. $attrs[ 'class' ] : '' );
136
+
137
+ // show start form
138
+ $html = '';
139
+ $html .= '<form method="post" action="options.php" '. $this->attrs( $attrs ) .'>';
140
+
141
+ if ( $ajaxSave ) {
142
+ $html .= wp_nonce_field( 'wpof_update_options', 'wpof-nonce', FALSE, FALSE );
143
+ $html .= '<input type="hidden" name="action" value="wpof_update_options" />';
144
+ $html .= '<input type="hidden" name="ajax_option_name" value="'. $this->current_option .'" />';
145
+
146
+ // instead of using settings_fields();
147
+ $html .= '<input type="hidden" name="option_page" value="' . esc_attr( $this->current_option ) . '" />';
148
+ $html .= wp_nonce_field( $this->current_option . '-options', '_wpnonce', TRUE, FALSE );
149
+ } else {
150
+ // instead of using settings_fields();
151
+ $html .= '<input type="hidden" name="option_page" value="' . esc_attr( $this->current_option ) . '" />';
152
+ $html .= '<input type="hidden" name="action" value="update" />';
153
+ $html .= wp_nonce_field( $this->current_option . '-options', '_wpnonce', TRUE, FALSE );
154
+ }
155
+
156
+ return $html;
157
+ }
158
+
159
+ /**
160
+ * Get script for saving screen option
161
+ * @param string $option_name
162
+ * @param string $key
163
+ * @return string
164
+ */
165
+ public function open_screen_option( $option_name, $key ) {
166
+ $this->set_current_option( $option_name );
167
+
168
+ $html = '';
169
+ $html .= '<script type="text/javascript">' . "\n";
170
+ $html .= '//<![CDATA[' . "\n";
171
+ $html .= 'jQuery( document ).ready( function( $ ){' . "\n";
172
+ $html .= "\t" . '// save screen option' . "\n";
173
+ $html .= "\t" . '$( "#screen-meta #'. $key .'" )' . "\n";
174
+ $html .= "\t\t" . '.change(function(){' . "\n";
175
+ $html .= "\t\t\t" . 'var self = this;' . "\n";
176
+ $html .= "\t\t\t" . '$.post( ajaxurl, {' . "\n";
177
+ $html .= "\t\t\t\t" . 'action: "wpof_update_options",' . "\n";
178
+ $html .= "\t\t\t\t" . '"wpof-nonce": "'. wp_create_nonce( 'wpof_update_options' ) .'",' . "\n";
179
+ $html .= "\t\t\t\t" . 'ajax_option_name: "'. $this->current_option .'",' . "\n";
180
+ $html .= "\t\t\t\t" . '"'. $this->field_name( $key ) .'": $( this ).val()' . "\n";
181
+ $html .= "\t\t\t" . '}, function () {' . "\n";
182
+ $html .= "\t\t\t\t" . '$( self ).trigger( "ajax_updated" );' . "\n";
183
+ $html .= "\t\t\t" . '});' . "\n";
184
+ $html .= "\t\t" . '});' . "\n";
185
+ $html .= '});' . "\n";
186
+ $html .= '//]]>' . "\n";
187
+ $html .= '</script>' . "\n";
188
+
189
+ return $html;
190
+ }
191
+
192
+ /**
193
+ * Get closing form
194
+ * @return string
195
+ */
196
+ public function close_form() {
197
+ return '</form>';
198
+ }
199
+
200
+ /**
201
+ * Text field
202
+ * @param string $key
203
+ * @param array $attrs Optional
204
+ * @return string
205
+ */
206
+ public function text( $key, $attrs = array() ) {
207
+ if ( ! key_exists( 'class', $attrs ) )
208
+ $attrs[ 'class' ] = 'regular-text';
209
+
210
+ return '<input type="text" '. $this->attrs( $attrs, $key, $this->value( $key ) ) .' />';
211
+ }
212
+
213
+ /**
214
+ * Text field
215
+ * @param string $key
216
+ * @param array $attrs Optional
217
+ * @return string
218
+ */
219
+ public function textarea( $key, $attrs = array() ) {
220
+ if ( ! key_exists( 'class', $attrs ) )
221
+ $attrs[ 'class' ] = 'large-text';
222
+
223
+ return '<textarea '. $this->attrs( $attrs, $key ) .'>'. $this->value( $key ) .'</textarea>';
224
+ }
225
+
226
+ /**
227
+ * Radio field
228
+ * @param string $key
229
+ * @param mixed $value
230
+ * @param array $attrs Optional
231
+ * @return string
232
+ */
233
+ public function radio( $key, $value, $attrs = array() ) {
234
+ $checked = ( $value == $this->value( $key ) ) ? ' checked="checked"' : '';
235
+ return '<input type="radio" '. $this->attrs( $attrs, $key, $value )
236
+ . $checked . ' />';
237
+ }
238
+
239
+ /**
240
+ * Checkbox field
241
+ * @param string $key
242
+ * @param mixed $value
243
+ * @param array $attrs Optional
244
+ * @return string
245
+ */
246
+ public function checkbox( $key, $value, $attrs = array() ) {
247
+ $checked = ( $value == $this->value( $key ) ) ? ' checked="checked"' : '';
248
+ return '<input type="checkbox" '. $this->attrs( $attrs, $key, $value )
249
+ . $checked . ' />';
250
+ }
251
+
252
+ /**
253
+ * Select field
254
+ * @param string $key
255
+ * @param array $options Optional
256
+ * @param array $attrs Optional
257
+ * @return string
258
+ */
259
+ public function select( $key, $options = array(), $attrs = array() ) {
260
+ $html = '<select '. $this->attrs( $attrs, $key ) .'>';
261
+
262
+ foreach ( $options AS $value => $label ) {
263
+ $selected = ( $value == $this->value( $key ) ) ? ' selected="selected"' : '';
264
+ $html .= '<option value="'. $value .'"'. $selected .'>'. $label .'</option>';
265
+ }
266
+
267
+ $html .= '</select>';
268
+ return $html;
269
+ }
270
+
271
+ /**
272
+ * Submit button
273
+ * @param array $attrs Optional
274
+ * @return string
275
+ */
276
+ public function submit( $attrs = array() ) {
277
+ // set class attr
278
+ $attrs[ 'class' ] = 'button-primary'. ( ( key_exists( 'class', $attrs ) ) ? ' '. $attrs[ 'class' ] : '' );
279
+
280
+ // show submit
281
+ $html = '';
282
+ $html .= '<p class="button-controls" style="text-align:right;">';
283
+ $html .= '<img alt="" title="" class="ajax-feedback" src="'. get_bloginfo( 'url' ) .'/wp-admin/images/wpspin_light.gif" style="visibility: hidden;" />';
284
+ $html .= '<input type="submit" '. $this->attrs( $attrs, '', __( 'Save Changes' ) ) .' />';
285
+ $html .= '</p>';
286
+ return $html;
287
+ }
288
+
289
+ /**
290
+ * Get field name of given key
291
+ * @param string $key
292
+ * @return string
293
+ */
294
+ public function field_name( $key ) {
295
+ return $this->current_option . '[' . $key . ']';
296
+ }
297
+
298
+ /**
299
+ * Get value of given option key
300
+ * @param string $key
301
+ * @param mixed $default_value Optional
302
+ * @param boolean $option_name Optional, search in given option_name instead of the current option
303
+ * @return mixed
304
+ */
305
+ public function value( $key, $default_value = NULL, $option_name = NULL ) {
306
+ if ( $option_name === NULL ) {
307
+ $option = $this->current_option;
308
+ } else {
309
+ $option = $this->name . '-' . $option_name;
310
+ }
311
+
312
+ if (!isset($this->options[ $option ])) {
313
+ return $default_value;
314
+ }
315
+
316
+ $values = $this->options[ $option ];
317
+
318
+ return ( is_array( $values ) AND key_exists( $key, $values ) AND $values[ $key ] !== NULL ) ? $values[ $key ] : $default_value;
319
+ }
320
+
321
+ /**
322
+ * Delete and unregister option
323
+ */
324
+ public function delete_options() {
325
+ foreach ( $this->options AS $option_name => $values ) {
326
+ delete_option( $option_name );
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Get string of given attributes
332
+ * @param array $attrs
333
+ * @param string $key Optional
334
+ * @param mixed $value Optional
335
+ * @return string
336
+ */
337
+ protected function attrs( $attrs, $key = NULL, $value = NULL ) {
338
+ $str = '';
339
+
340
+ // set name, id, value attr
341
+ if ( $key !== NULL ) {
342
+ $str .= 'name="' . $this->field_name( $key ) .'" ';
343
+ if ( ! key_exists( 'id', $attrs ) )
344
+ $str .= 'id="' . $key .'" ';
345
+ }
346
+
347
+ if ( $value !== NULL )
348
+ $str .= 'value="' . $value .'" ';
349
+
350
+ foreach ( $attrs AS $attr => $value )
351
+ $str .= $attr .'="'. $value .'" ';
352
+
353
+ return $str;
354
+ }
355
+
356
+ } // End WP_Option_Forms_01
357
+
358
+ endif;
359
+
360
  /* ommit PHP closing tag, to prevent unwanted whitespace at the end of the parts generated by the included files */
js/admin-wp-external-links.js CHANGED
@@ -1,172 +1,2 @@
1
- /* WP External Links Plugin - Admin */
2
- jQuery(function ($) {
3
-
4
- /* Tipsy Plugin */
5
- (function () {
6
- $.fn.tipsy = function(options) {
7
- options = $.extend({}, $.fn.tipsy.defaults, options);
8
-
9
- return this.each(function() {
10
- var opts = $.fn.tipsy.elementOptions(this, options);
11
-
12
- $(this).hover(function() {
13
- $.data(this, 'cancel.tipsy', true);
14
-
15
- var tip = $.data(this, 'active.tipsy');
16
- if (!tip) {
17
- tip = $('<div class="tipsy"><div class="tipsy-inner"/></div>');
18
- tip.css({position: 'absolute', zIndex: 100000});
19
- $.data(this, 'active.tipsy', tip);
20
- }
21
-
22
- if ($(this).attr('title') || typeof($(this).attr('original-title')) != 'string') {
23
- $(this).attr('original-title', $(this).attr('title') || '').removeAttr('title');
24
- }
25
-
26
- var title;
27
- if (typeof opts.title == 'string') {
28
- title = $(this).attr(opts.title == 'title' ? 'original-title' : opts.title);
29
- } else if (typeof opts.title == 'function') {
30
- title = opts.title.call(this);
31
- }
32
-
33
- tip.find('.tipsy-inner')[opts.html ? 'html' : 'text'](title || opts.fallback);
34
-
35
- var pos = $.extend({}, $(this).offset(), {width: this.offsetWidth, height: this.offsetHeight});
36
- tip.get(0).className = 'tipsy'; // reset classname in case of dynamic gravity
37
- tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body);
38
- var actualWidth = tip[0].offsetWidth, actualHeight = tip[0].offsetHeight;
39
- var gravity = (typeof opts.gravity == 'function') ? opts.gravity.call(this) : opts.gravity;
40
-
41
- switch (gravity.charAt(0)) {
42
- case 'n':
43
- tip.css({top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}).addClass('tipsy-north');
44
- break;
45
- case 's':
46
- tip.css({top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}).addClass('tipsy-south');
47
- break;
48
- case 'e':
49
- tip.css({top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}).addClass('tipsy-east');
50
- break;
51
- case 'w':
52
- tip.css({top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}).addClass('tipsy-west');
53
- break;
54
- }
55
-
56
- if (opts.fade) {
57
- tip.css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: 0.9});
58
- } else {
59
- tip.css({visibility: 'visible'});
60
- }
61
-
62
- }, function() {
63
- $.data(this, 'cancel.tipsy', false);
64
- var self = this;
65
- setTimeout(function() {
66
- if ($.data(this, 'cancel.tipsy')) return;
67
- var tip = $.data(self, 'active.tipsy');
68
- if (opts.fade) {
69
- tip.stop().fadeOut(function() { $(this).remove(); });
70
- } else {
71
- tip.remove();
72
- }
73
- }, 100);
74
-
75
- });
76
- });
77
- };
78
-
79
- // Overwrite this method to provide options on a per-element basis.
80
- // For example, you could store the gravity in a 'tipsy-gravity' attribute:
81
- // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
82
- // (remember - do not modify 'options' in place!)
83
- $.fn.tipsy.elementOptions = function(ele, options) {
84
- return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
85
- };
86
-
87
- $.fn.tipsy.defaults = {
88
- fade: false,
89
- fallback: '',
90
- gravity: 'w',
91
- html: false,
92
- title: 'title'
93
- };
94
-
95
- $.fn.tipsy.autoNS = function() {
96
- return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
97
- };
98
-
99
- $.fn.tipsy.autoWE = function() {
100
- return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
101
- };
102
-
103
- })(); // End Tipsy Plugin
104
-
105
-
106
- $('#setting-error-settings_updated').click(function () {
107
- $(this).hide();
108
- });
109
-
110
- // option filter page
111
- $( 'input#filter_page' )
112
- .change(function(){
113
- var $i = $( 'input#filter_posts, input#filter_comments, input#filter_widgets' );
114
-
115
- if ( $( this ).attr( 'checked' ) ) {
116
- $i.attr( 'disabled', true )
117
- .attr( 'checked', true );
118
- } else {
119
- $i.attr( 'disabled', false )
120
- }
121
- })
122
- .change();
123
-
124
- // option use js
125
- $( 'input#use_js' )
126
- .change(function(){
127
- var $i = $( 'input#load_in_footer' );
128
-
129
- if ( $( this ).attr( 'checked' ) ) {
130
- $i.attr( 'disabled', false );
131
- } else {
132
- $i.attr( 'disabled', true )
133
- .attr( 'checked', false );
134
- }
135
- })
136
- .change();
137
-
138
- // option filter_excl_sel
139
- $( 'input#phpquery' )
140
- .change(function(){
141
- if ( $( this ).attr( 'checked' ) ) {
142
- $( '.filter_excl_sel' ).fadeIn();
143
- } else {
144
- $( '.filter_excl_sel' ).fadeOut();
145
- }
146
- })
147
- .change();
148
-
149
- // refresh page when updated menu position
150
- $('#menu_position').parents( 'form.ajax-form' ).on( 'ajax_saved_options', function(result){
151
- var s = $( this ).val() || '';
152
- window.location.href = s + ( s.indexOf( '?' ) > -1 ? '&' : '?' ) + 'page=wp_external_links&settings-updated=true';
153
- });
154
-
155
- // set tooltips
156
- $( '.tooltip-help' ).css( 'margin', '0 5px' ).tipsy({ fade:true, live:true, gravity:'w', fallback: 'No help text.' });
157
-
158
- // remove class to fix button background
159
- $('*[type="submit"]').removeClass('submit');
160
-
161
- // slide postbox
162
- $( '.postbox' ).find( '.handlediv, .hndle' ).click(function(){
163
- var $inside = $( this ).parent().find( '.inside' );
164
-
165
- if ( $inside.css( 'display' ) == 'block' ) {
166
- $inside.css({ display:'none' });
167
- } else {
168
- $inside.css({ display:'block' });
169
- }
170
- });
171
-
172
- });
1
+ /* WP External Links - Admin */
2
+ jQuery(function(e){"use strict";(function(){e.fn.tipsy=function(t){t=e.extend({},e.fn.tipsy.defaults,t);return this.each(function(){var n=e.fn.tipsy.elementOptions(this,t);e(this).hover(function(){e.data(this,"cancel.tipsy",true);var t=e.data(this,"active.tipsy");if(!t){t=e('<div class="tipsy"><div class="tipsy-inner"/></div>');t.css({position:"absolute",zIndex:1e5});e.data(this,"active.tipsy",t)}if(e(this).attr("title")||typeof e(this).attr("original-title")!=="string"){e(this).attr("original-title",e(this).attr("title")||"").removeAttr("title")}var r;if(typeof n.title==="string"){r=e(this).attr(n.title==="title"?"original-title":n.title)}else if(typeof n.title==="function"){r=n.title.call(this)}t.find(".tipsy-inner")[n.html?"html":"text"](r||n.fallback);var i=e.extend({},e(this).offset(),{width:this.offsetWidth,height:this.offsetHeight});t.get(0).className="tipsy";t.remove().css({top:0,left:0,visibility:"hidden",display:"block"}).appendTo(document.body);var s=t[0].offsetWidth,o=t[0].offsetHeight;var u=typeof n.gravity=="function"?n.gravity.call(this):n.gravity;switch(u.charAt(0)){case"n":t.css({top:i.top+i.height,left:i.left+i.width/2-s/2}).addClass("tipsy-north");break;case"s":t.css({top:i.top-o,left:i.left+i.width/2-s/2}).addClass("tipsy-south");break;case"e":t.css({top:i.top+i.height/2-o/2,left:i.left-s}).addClass("tipsy-east");break;case"w":t.css({top:i.top+i.height/2-o/2,left:i.left+i.width}).addClass("tipsy-west");break}if(n.fade){t.css({opacity:0,display:"block",visibility:"visible"}).animate({opacity:.9})}else{t.css({visibility:"visible"})}},function(){e.data(this,"cancel.tipsy",false);var t=this;setTimeout(function(){if(e.data(this,"cancel.tipsy"))return;var r=e.data(t,"active.tipsy");if(n.fade){r.stop().fadeOut(function(){e(this).remove()})}else{r.remove()}},100)})})};e.fn.tipsy.elementOptions=function(t,n){return e.metadata?e.extend({},n,e(t).metadata()):n};e.fn.tipsy.defaults={fade:false,fallback:"",gravity:"w",html:false,title:"title"};e.fn.tipsy.autoNS=function(){return e(this).offset().top>e(document).scrollTop()+e(window).height()/2?"s":"n"};e.fn.tipsy.autoWE=function(){return e(this).offset().left>e(document).scrollLeft()+e(window).width()/2?"e":"w"}})();e("#setting-error-settings_updated").click(function(){e(this).hide()});e("input#filter_page").change(function(){var t=e("input#filter_posts, input#filter_comments, input#filter_widgets");if(e(this).attr("checked")){t.attr("disabled",true).attr("checked",true)}else{t.attr("disabled",false)}}).change();e("input#use_js").change(function(){var t=e("input#load_in_footer");if(e(this).attr("checked")){t.attr("disabled",false)}else{t.attr("disabled",true).attr("checked",false)}}).change();e("input#phpquery").change(function(){if(e(this).attr("checked")){e(".filter_excl_sel").fadeIn()}else{e(".filter_excl_sel").fadeOut()}}).change();e("#menu_position").parents("form.ajax-form").on("ajax_saved_options",function(){var t=e(this).val()||"";window.location.href=t+(t.indexOf("?")>-1?"&":"?")+"page=wp_external_links&settings-updated=true"});e(".tooltip-help").css("margin","0 5px").tipsy({fade:true,live:true,gravity:"w",fallback:"No help text."});e('*[type="submit"]').removeClass("submit");e(".postbox").find(".handlediv, .hndle").click(function(){var t=e(this).parent().find(".inside");if(t.css("display")==="block"){t.css({display:"none"})}else{t.css({display:"block"})}})});jQuery(function(e){"use strict";var t=function(t){var n=e(t),r=n.parents("form"),i=r.serializeArray();n.attr("disabled",true);r.find(".ajax-feedback").css("visibility","visible");e.post(ajaxurl,i,function(t){var i=e("<strong>").insertBefore(n);if(t==="1"){i.html("Saved")}else{r.find('[name="action"]').val("update");r.submit()}i.css({margin:"0 5px"}).delay(1e3).fadeOut(function(){e(this).remove()});n.attr("disabled",false);r.find(".ajax-feedback").css("visibility","hidden");r.trigger("ajax_saved_options",[t])})};e('form.ajax-form input[type="submit"]').click(function(e){t(this);e.preventDefault()})})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/src/admin-wp-external-links.js ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* WP External Links Plugin - Admin */
2
+ /*global jQuery, window*/
3
+ jQuery(function ($) {
4
+ 'use strict';
5
+
6
+ /* Tipsy Plugin */
7
+ (function () {
8
+ $.fn.tipsy = function (options) {
9
+ options = $.extend({}, $.fn.tipsy.defaults, options);
10
+
11
+ return this.each(function () {
12
+ var opts = $.fn.tipsy.elementOptions(this, options);
13
+
14
+ $(this).hover(function () {
15
+ $.data(this, 'cancel.tipsy', true);
16
+
17
+ var tip = $.data(this, 'active.tipsy');
18
+ if (!tip) {
19
+ tip = $('<div class="tipsy"><div class="tipsy-inner"/></div>');
20
+ tip.css({position: 'absolute', zIndex: 100000});
21
+ $.data(this, 'active.tipsy', tip);
22
+ }
23
+
24
+ if ($(this).attr('title') || typeof $(this).attr('original-title') !== 'string') {
25
+ $(this).attr('original-title', $(this).attr('title') || '').removeAttr('title');
26
+ }
27
+
28
+ var title;
29
+ if (typeof opts.title === 'string') {
30
+ title = $(this).attr(opts.title === 'title' ? 'original-title' : opts.title);
31
+ } else if (typeof opts.title === 'function') {
32
+ title = opts.title.call(this);
33
+ }
34
+
35
+ tip.find('.tipsy-inner')[opts.html ? 'html' : 'text'](title || opts.fallback);
36
+
37
+ var pos = $.extend({}, $(this).offset(), {width: this.offsetWidth, height: this.offsetHeight});
38
+ tip.get(0).className = 'tipsy'; // reset classname in case of dynamic gravity
39
+ tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body);
40
+ var actualWidth = tip[0].offsetWidth, actualHeight = tip[0].offsetHeight;
41
+ var gravity = (typeof opts.gravity == 'function') ? opts.gravity.call(this) : opts.gravity;
42
+
43
+ switch (gravity.charAt(0)) {
44
+ case 'n':
45
+ tip.css({top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}).addClass('tipsy-north');
46
+ break;
47
+ case 's':
48
+ tip.css({top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}).addClass('tipsy-south');
49
+ break;
50
+ case 'e':
51
+ tip.css({top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}).addClass('tipsy-east');
52
+ break;
53
+ case 'w':
54
+ tip.css({top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}).addClass('tipsy-west');
55
+ break;
56
+ }
57
+
58
+ if (opts.fade) {
59
+ tip.css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: 0.9});
60
+ } else {
61
+ tip.css({visibility: 'visible'});
62
+ }
63
+
64
+ }, function () {
65
+ $.data(this, 'cancel.tipsy', false);
66
+ var self = this;
67
+ setTimeout(function () {
68
+ if ($.data(this, 'cancel.tipsy')) return;
69
+ var tip = $.data(self, 'active.tipsy');
70
+ if (opts.fade) {
71
+ tip.stop().fadeOut(function () { $(this).remove(); });
72
+ } else {
73
+ tip.remove();
74
+ }
75
+ }, 100);
76
+ });
77
+ });
78
+ };
79
+
80
+ // Overwrite this method to provide options on a per-element basis.
81
+ // For example, you could store the gravity in a 'tipsy-gravity' attribute:
82
+ // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
83
+ // (remember - do not modify 'options' in place!)
84
+ $.fn.tipsy.elementOptions = function (ele, options) {
85
+ return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
86
+ };
87
+
88
+ $.fn.tipsy.defaults = {
89
+ fade: false,
90
+ fallback: '',
91
+ gravity: 'w',
92
+ html: false,
93
+ title: 'title'
94
+ };
95
+
96
+ $.fn.tipsy.autoNS = function () {
97
+ return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
98
+ };
99
+
100
+ $.fn.tipsy.autoWE = function () {
101
+ return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
102
+ };
103
+
104
+ })(); // End Tipsy Plugin
105
+
106
+
107
+ $('#setting-error-settings_updated').click(function () {
108
+ $(this).hide();
109
+ });
110
+
111
+ // option filter page
112
+ $('input#filter_page')
113
+ .change(function () {
114
+ var $i = $('input#filter_posts, input#filter_comments, input#filter_widgets');
115
+
116
+ if ($(this).attr('checked')) {
117
+ $i.attr('disabled', true)
118
+ .attr('checked', true);
119
+ } else {
120
+ $i.attr('disabled', false);
121
+ }
122
+ })
123
+ .change();
124
+
125
+ // option use js
126
+ $('input#use_js')
127
+ .change(function () {
128
+ var $i = $('input#load_in_footer');
129
+
130
+ if ($(this).attr('checked')) {
131
+ $i.attr('disabled', false);
132
+ } else {
133
+ $i.attr('disabled', true)
134
+ .attr('checked', false);
135
+ }
136
+ })
137
+ .change();
138
+
139
+ // option filter_excl_sel
140
+ $('input#phpquery')
141
+ .change(function () {
142
+ if ($(this).attr('checked')) {
143
+ $('.filter_excl_sel').fadeIn();
144
+ } else {
145
+ $('.filter_excl_sel').fadeOut();
146
+ }
147
+ })
148
+ .change();
149
+
150
+ // refresh page when updated menu position
151
+ $('#menu_position').parents('form.ajax-form').on('ajax_saved_options', function () {
152
+ var s = $(this).val() || '';
153
+ window.location.href = s + (s.indexOf('?') > -1 ? '&' : '?') + 'page=wp_external_links&settings-updated=true';
154
+ });
155
+
156
+ // set tooltips
157
+ $('.tooltip-help').css('margin', '0 5px').tipsy({ fade: true, live: true, gravity: 'w', fallback: 'No help text.' });
158
+
159
+ // remove class to fix button background
160
+ $('*[type="submit"]').removeClass('submit');
161
+
162
+ // slide postbox
163
+ $('.postbox').find('.handlediv, .hndle').click(function () {
164
+ var $inside = $(this).parent().find('.inside');
165
+
166
+ if ($inside.css('display') === 'block') {
167
+ $inside.css({ display: 'none' });
168
+ } else {
169
+ $inside.css({ display: 'block' });
170
+ }
171
+ });
172
+
173
+ });
js/src/wp-external-links.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* WP External Links Plugin */
2
+ /*global jQuery, window, wpExtLinks*/
3
+ (function () {
4
+ 'use strict';
5
+
6
+ var $ = jQuery === undefined ? null : jQuery;
7
+
8
+ // add event handler
9
+ function addEvt(el, evt, fn) {
10
+ if (el.attachEvent) {
11
+ // IE method
12
+ el.attachEvent('on' + evt, fn);
13
+ } else if (el.addEventListener) {
14
+ // Standard JS method
15
+ el.addEventListener(evt, fn, false);
16
+ }
17
+ }
18
+
19
+ // open external link
20
+ function openExtLink(a, opts, e) {
21
+ var options = opts || wpExtLinks,
22
+ href = a.href ? a.href.toLowerCase() : '',
23
+ rel = a.rel ? a.rel.toLowerCase() : '',
24
+ n;
25
+
26
+ if (a.href && (options.excludeClass.length === 0 || a.className.indexOf(options.excludeClass))
27
+ && (rel.indexOf('external') > -1
28
+ || ((href.indexOf(options.baseUrl) === -1) &&
29
+ (href.substr(0, 7) === 'http://'
30
+ || href.substr(0, 8) === 'https://'
31
+ || href.substr(0, 6) === 'ftp://'
32
+ || href.substr(0, 2) === '//')))) {
33
+ // open link in a new window
34
+ n = window.open(a.href, options.target);
35
+ n.focus();
36
+
37
+ // prevent default event action
38
+ if (e) {
39
+ if (e.preventDefault) {
40
+ e.preventDefault();
41
+ } else {
42
+ e.returnValue = false;
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ if ($ && false) {
49
+ // jQuery DOMready method
50
+ $(function () {
51
+ $('a').live('click', function (e) {
52
+ openExtLink(this, null, e);
53
+ });
54
+ });
55
+ } else {
56
+ // use onload when jQuery not available
57
+ addEvt(window, 'load', function () {
58
+ var links = window.document.getElementsByTagName('a'),
59
+ eventClick = function (e) {
60
+ openExtLink(e.target, null, e);
61
+ },
62
+ a,
63
+ i;
64
+
65
+ // check each <a> element
66
+ for (i = 0; i < links.length; i += 1) {
67
+ a = links[i];
68
+
69
+ // click event for opening in a new window
70
+ addEvt(a, 'click', eventClick);
71
+ }
72
+ });
73
+ }
74
+
75
+ }());
js/src/wp-option-forms.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* WP Options Form */
2
+ /*global jQuery, ajaxurl*/
3
+ jQuery(function ($) {
4
+ 'use strict';
5
+
6
+ // save function
7
+ var saveAjaxForm = function (target) {
8
+ var $this = $(target),
9
+ $form = $this.parents('form'),
10
+ // get ajax post values
11
+ vals = $form.serializeArray();
12
+
13
+ // disable button
14
+ $this.attr('disabled', true);
15
+
16
+ // show ajax loader
17
+ $form.find('.ajax-feedback').css('visibility', 'visible');
18
+
19
+ // save option values
20
+ $.post(ajaxurl, vals, function (result) {
21
+ var $msg = $('<strong>').insertBefore($this);
22
+
23
+ if (result === '1') {
24
+ $msg.html('Saved');
25
+ } else {
26
+ // save options, non-ajax fallback
27
+ $form.find('[name="action"]').val('update');
28
+ // normal submit
29
+ $form.submit();
30
+ }
31
+
32
+ $msg.css({ margin: '0 5px' })
33
+ .delay(1000)
34
+ .fadeOut(function () {
35
+ $(this).remove();
36
+ });
37
+
38
+ // enable button
39
+ $this.attr('disabled', false);
40
+
41
+ // hide ajax loader
42
+ $form.find('.ajax-feedback').css('visibility', 'hidden');
43
+
44
+ // trigger ajax_saved_options
45
+ $form.trigger('ajax_saved_options', [result]);
46
+ });
47
+ };
48
+
49
+ // add ajax post
50
+ $('form.ajax-form input[type="submit"]').click(function (e) {
51
+ saveAjaxForm(this);
52
+ e.preventDefault();
53
+ });
54
+
55
+ });
js/wp-external-links.js CHANGED
@@ -1,72 +1,2 @@
1
- /* WP External Links Plugin */
2
- (function(){
3
-
4
- var $ = typeof jQuery == 'undefined' ? null : jQuery;
5
-
6
- // add event handler
7
- function addEvt ( el, evt, fn ) {
8
- if ( el.attachEvent ) {
9
- // IE method
10
- el.attachEvent( 'on'+ evt, fn );
11
- } else if ( el.addEventListener ) {
12
- // Standard JS method
13
- el.addEventListener( evt, fn, false );
14
- }
15
- };
16
-
17
- // open external link
18
- function openExtLink( a, opts, e ) {
19
- var options = opts ? opts : wpExtLinks,
20
- href = a.href ? a.href.toLowerCase() : '',
21
- rel = a.rel ? a.rel.toLowerCase() : '',
22
- n;
23
-
24
- if ( a.href && ( options.excludeClass.length == 0 || a.className.indexOf( options.excludeClass ) )
25
- && ( rel.indexOf( 'external' ) > -1
26
- || ( ( href.indexOf( options.baseUrl ) === -1 ) &&
27
- ( href.substr( 0, 7 ) == 'http://'
28
- || href.substr( 0, 8 ) == 'https://'
29
- || href.substr( 0, 6 ) == 'ftp://' ) ) ) ) {
30
- // open link in a new window
31
- n = window.open( a.href, options.target );
32
- n.focus();
33
-
34
- // prevent default event action
35
- if ( e ) {
36
- if ( e.preventDefault ) {
37
- e.preventDefault();
38
- } else {
39
- e.returnValue = false;
40
- }
41
- }
42
- }
43
- };
44
-
45
- if ( $ ) {
46
- // jQuery DOMready method
47
- $(function(){
48
- $( 'a' ).live( 'click', function( e ){
49
- openExtLink( this, null, e );
50
- });
51
- });
52
- } else {
53
- // use onload when jQuery not available
54
- addEvt( window, 'load', function () {
55
- var links = window.document.getElementsByTagName( 'a' ),
56
- a;
57
-
58
- // check each <a> element
59
- for ( var i = 0; i < links.length; i++ ) {
60
- a = links[ i ];
61
-
62
- // click event for opening in a new window
63
- addEvt( a, 'click', function( a ){
64
- return function ( e ) {
65
- openExtLink( a, null, e );
66
- }
67
- }( a ));
68
- }
69
- });
70
- }
71
-
72
- })();
1
+ /* WP External Links */
2
+ (function(){"use strict";function t(e,t,n){if(e.attachEvent){e.attachEvent("on"+t,n)}else if(e.addEventListener){e.addEventListener(t,n,false)}}function n(e,t,n){var r=t||wpExtLinks,i=e.href?e.href.toLowerCase():"",s=e.rel?e.rel.toLowerCase():"",o;if(e.href&&(r.excludeClass.length===0||e.className.indexOf(r.excludeClass))&&(s.indexOf("external")>-1||i.indexOf(r.baseUrl)===-1&&(i.substr(0,7)==="http://"||i.substr(0,8)==="https://"||i.substr(0,6)==="ftp://"||i.substr(0,2)==="//"))){o=window.open(e.href,r.target);o.focus();if(n){if(n.preventDefault){n.preventDefault()}else{n.returnValue=false}}}}var e=jQuery===undefined?null:jQuery;if(e){e(function(){e("a").live("click",function(e){n(this,null,e)})})}else{t(window,"load",function(){var e=window.document.getElementsByTagName("a"),r=function(e){n(e.target,null,e)},i,s;for(s=0;s<e.length;s+=1){i=e[s];t(i,"click",r)}})}})()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/wp-option-forms.js DELETED
@@ -1,55 +0,0 @@
1
- /* WP Options Form */
2
- /*global jQuery, ajaxurl*/
3
- jQuery(function ($) {
4
- 'use strict';
5
-
6
- // save function
7
- var saveAjaxForm = function (target) {
8
- var $this = $(target),
9
- $form = $this.parents('form'),
10
- // get ajax post values
11
- vals = $form.serializeArray();
12
-
13
- // disable button
14
- $this.attr('disabled', true);
15
-
16
- // show ajax loader
17
- $form.find('.ajax-feedback').css('visibility', 'visible');
18
-
19
- // save option values
20
- $.post(ajaxurl, vals, function (result) {
21
- var $msg = $('<strong>').insertBefore($this);
22
-
23
- if (result === '1') {
24
- $msg.html('Saved');
25
- } else {
26
- // save options, non-ajax fallback
27
- $form.find('[name="action"]').val('update');
28
- // normal submit
29
- $form.submit();
30
- }
31
-
32
- $msg.css({ margin: '0 5px' })
33
- .delay(1000)
34
- .fadeOut(function(){
35
- $(this).remove();
36
- });
37
-
38
- // enable button
39
- $this.attr('disabled', false);
40
-
41
- // hide ajax loader
42
- $form.find('.ajax-feedback').css('visibility', 'hidden');
43
-
44
- // trigger ajax_saved_options
45
- $form.trigger('ajax_saved_options', [result]);
46
- });
47
- };
48
-
49
- // add ajax post
50
- $('form.ajax-form input[type="submit"]').click(function(e) {
51
- saveAjaxForm(this);
52
- e.preventDefault();
53
- });
54
-
55
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -1,200 +1,212 @@
1
- === WP External Links (nofollow new window seo) ===
2
- Contributors: freelancephp
3
- Tags: links, external, icon, target, _blank, _new, _none, rel, nofollow, new window, new tab, javascript, xhtml, seo
4
- Requires at least: 3.4.0
5
- Tested up to: 3.8.2
6
- Stable tag: 1.51
7
-
8
- Open external links in a new window or tab, adding "nofollow", set link icon, styling, SEO friendly options and more. Easy install and go.
9
-
10
- == Description ==
11
-
12
- Configure settings for all external links on your site.
13
-
14
- = Features =
15
- * Open external links in new window or tab
16
- * Add "nofollow"
17
- * Set link title
18
- * Set link icon
19
- * Set classes (for your own styling)
20
- * Set no-icon class
21
- * SEO friendly
22
-
23
- = Easy to use =
24
- After activating the plugin all options are already set to make your external links SEO friendly. Optionally you can also set the target for opening in a new window or tab or styling options, like adding an icon.
25
-
26
- [See more documentation](http://wordpress.org/extend/plugins/wp-external-links/other_notes/).
27
-
28
- = Support =
29
- This plugin has the same [requirements](http://wordpress.org/about/requirements/) as WordPress.
30
- If you are experiencing any problems, just take a look at the [FAQ](http://wordpress.org/extend/plugins/wp-external-links/faq/) or report it in the [support section](http://wordpress.org/support/plugin/wp-external-links). You can also send me a mail with [this contactform](http://www.freelancephp.net/contact/).
31
-
32
- = Github =
33
- I published the code on [Github](https://github.com/freelancephp/WP-External-Links) so anybody can commit code changes.
34
-
35
- = Like this plugin? =
36
- Please [rate this plugin](http://wordpress.org/support/view/plugin-reviews/wp-external-links-plugin) or [post a comment](http://www.freelancephp.net/wp-external-links-plugin/) on my blog.
37
-
38
-
39
- == Installation ==
40
-
41
- 1. Go to `Plugins` in the Admin menu
42
- 1. Click on the button `Add new`
43
- 1. Search for `WP External Links` and click 'Install Now' OR click on the `upload` link to upload `wp-external-links.zip`
44
- 1. Click on `Activate plugin`
45
-
46
- == Frequently Asked Questions ==
47
-
48
- [Do you have a question? Please ask me](http://www.freelancephp.net/contact/)
49
-
50
- == Screenshots ==
51
-
52
- 1. Link Icon on the Site
53
- 1. Admin Settings Page
54
-
55
- == Documentation ==
56
-
57
- After activating the plugin all options are already set to make your external links SEO friendly. Optionally you can also set the target for opening in a new window or tab or styling options, like adding an icon.
58
-
59
- = Action hook =
60
- The plugin also has a hook when ready, f.e. to add extra filters:
61
- `function extra_filters($filter_callback, $object) {
62
- add_filter('some_filter', $filter_callback);
63
- }
64
- add_action('wpel_ready', 'extra_filters');`
65
-
66
- = Filter hook =
67
- The wpel_external_link filter gives you the possibility to manipulate output of the mailto created by the plugin, like:
68
- `function special_external_link($created_link, $original_link, $label, $attrs, $is_ignored_link) {
69
- // skip links that contain the class "not-external"
70
- if (isset($attrs['class']) && strpos($attrs['class'], 'not-external') !== false) {
71
- return $original_link;
72
- }
73
-
74
- return '<b>'. $created_link .'</b>';
75
- }
76
- add_filter('wpel_external_link', 'special_external_link', 10, 5);`
77
-
78
- Now all external links will be processed and wrapped around a `<b>`-tag. And links containing the class "not-external" will not be processed by the plugin at all (and stay the way they are).
79
-
80
- = Credits =
81
- * [jQuery Tipsy Plugin](http://plugins.jquery.com/project/tipsy) made by [Jason Frame](http://onehackoranother.com/)
82
- * [phpQuery](http://code.google.com/p/phpquery/) made by [Tobiasz Cudnik](http://tobiasz123.wordpress.com)
83
- * [Icon](http://findicons.com/icon/164579/link_go?id=427009) made by [FatCow Web Hosting](http://www.fatcow.com/)
84
-
85
- == Changelog ==
86
-
87
- = 1.51 =
88
- * Fixed also check url's starting with //
89
- * Fixed wpel_external_link also applied on ignored links
90
-
91
- = 1.50 =
92
- * Removed stylesheet file to save extra request
93
- * Added option for loading js file in wp_footer
94
- * Fixed bug with data-* attributes
95
- * Fixed bug url's with hash at the end
96
- * Fixed PHP errors
97
-
98
- = 1.41 =
99
- * Fixed Bug: wpmel_external_link filter hook was not working correctly
100
-
101
- = 1.40 =
102
- * Added action hook wpel_ready
103
- * Added filter hook wpel_external_link
104
- * Added output flush on wp_footer
105
- * Fixed Bug: spaces before url in href-attribute not recognized as external link
106
- * Fixed Bug: external links not processed (regexpr tag conflict starting with an a, like <aside> or <article>)
107
- * Cosmetic changes: added "Admin Settings", replaced help icon, restyled tooltip texts, removed "About this plugin" box
108
-
109
- = 1.31 =
110
- * Fixed passing arguments by reference using & (deprecated for PHP 5.4+)
111
- * Fixed options save failure by adding a non-ajax submit fallback
112
-
113
- = 1.30 =
114
- * Re-arranged options in metaboxes
115
- * Added option for no icons on images
116
-
117
- = 1.21 =
118
- * Fixed phpQuery bugs (class already exists and loading stylesheet)
119
- * Solved php notices
120
-
121
- = 1.20 =
122
- * Added option to ignore certain links or domains
123
- * Solved tweet button problem by adding link to new ignore option
124
- * Made JavaScript method consistent to not using JS
125
- * Solved PHP warnings
126
- * Solved bug adding own class
127
- * Changed bloginfo "url" to "wpurl"
128
-
129
- = 1.10 =
130
- * Resolved old parsing method (same as version 0.35)
131
- * Option to use phpQuery for parsing (for those who didn't experience problems with version 1.03)
132
-
133
- = 1.03 =
134
- * Workaround for echo DOCTYPE bug (caused by attributes in the head-tag)
135
-
136
- = 1.02 =
137
- * Solved the not working activation hook
138
-
139
- = 1.01 =
140
- * Solved bug after live testing
141
-
142
- = 1.00 =
143
- * Added option for setting title-attribute
144
- * Added option for excluding filtering certain external links
145
- * Added Admin help tooltips using jQuery Tipsy Plugin
146
- * Reorginized files and refactored code to PHP5 (no support for PHP4)
147
- * Added WP built-in meta box functionallity (using the `WP_Meta_Box_Page` Class)
148
- * Reorganized saving options and added Ajax save method (using the `WP_Option_Forms` Class)
149
- * Removed Regexp and using phpQuery
150
- * Choose menu position for this plugin (see "Screen Options")
151
- * Removed possibility to convert all `<a>` tags to xhtml clean code (so only external links will be converted)
152
- * Removed "Solve problem" options
153
-
154
- = 0.35 =
155
- * Widget Logic options bug
156
-
157
- = 0.34 =
158
- * Added option only converting external `<a>` tags to XHTML valid code
159
- * Changed script attribute `language` to `type`
160
- * Added support for widget_content filter of the Logic Widget plugin
161
-
162
- = 0.33 =
163
- * Added option to fix js problem
164
- * Fixed PHP / WP notices
165
-
166
- = 0.32 =
167
- * For jQuery uses live() function so also opens dynamicly created links in given target
168
- * Fixed bug of changing `<abbr>` tag
169
- * Small cosmetical adjustments
170
-
171
- = 0.31 =
172
- * Small cosmetical adjustments
173
-
174
- = 0.30 =
175
- * Improved Admin Options, f.e. target option looks more like the Blogroll target option
176
- * Added option for choosing which content should be filtered
177
-
178
- = 0.21 =
179
- * Solved bug removing icon stylesheet
180
-
181
- = 0.20 =
182
- * Put icon styles in external stylesheet
183
- * Can use "ext-icon-..." to show a specific icon on a link
184
- * Added option to set your own No-Icon class
185
- * Made "Class" optional, so it's not used for showing icons anymore
186
- * Added 3 more icons
187
-
188
- = 0.12 =
189
- * Options are organized more logical
190
- * Added some more icons
191
-
192
- = 0.11 =
193
- * JavaScript uses window.open() (tested in FireFox Opera, Safari, Chrome and IE6+)
194
- * Also possible to open all external links in the same new window
195
- * Some layout changes on the Admin Options Page
196
-
197
- = 0.10 =
198
- * Features: opening in a new window, set link icon, set "external", set "nofollow", set css-class
199
- * Replaces external links by clean XHTML <a> tags
200
- * Internalization implemented (no language files yet)
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === WP External Links (nofollow new window seo) ===
2
+ Contributors: freelancephp
3
+ Tags: links, external, icon, target, _blank, _new, _none, rel, nofollow, new window, new tab, javascript, xhtml, seo
4
+ Requires at least: 3.4.0
5
+ Tested up to: 3.8.2
6
+ Stable tag: 1.52
7
+
8
+ Open external links in a new window or tab, adding "nofollow", set link icon, styling, SEO friendly options and more. Easy install and go.
9
+
10
+ == Description ==
11
+
12
+ Configure settings for all external links on your site.
13
+
14
+ = Features =
15
+ * Open in new window or tab
16
+ * Add "nofollow"
17
+ * Choose from 20 icons
18
+ * Set other link options (like classes, title etc)
19
+ * Make it SEO friendly
20
+
21
+ = Easy to use =
22
+ After activating the plugin all options are already set to make your external links SEO friendly. Optionally you can also set the target for opening in a new window or tab or styling options, like adding an icon.
23
+
24
+ = Sources =
25
+ * [Documentation](http://wordpress.org/extend/plugins/wp-external-links/other_notes/)
26
+ * [FAQ](http://wordpress.org/extend/plugins/wp-external-links/faq/)
27
+ * [Github](https://github.com/freelancephp/WP-External-Links)
28
+
29
+ = Like this plugin? =
30
+ [Send your review](http://wordpress.org/support/view/plugin-reviews/wp-external-links-plugin).
31
+
32
+
33
+ == Installation ==
34
+
35
+ 1. Go to `Plugins` in the Admin menu
36
+ 1. Click on the button `Add new`
37
+ 1. Search for `WP External Links` and click 'Install Now' OR click on the `upload` link to upload `wp-external-links.zip`
38
+ 1. Click on `Activate plugin`
39
+
40
+ == Frequently Asked Questions ==
41
+
42
+ = I want internal links to be treated as external links. How? =
43
+
44
+ You could add `rel="external"` to those internal links that should be treated as external. The plugin settings will also be applied to those links.
45
+
46
+ = Links to my own domain are treated as external links. Why? =
47
+
48
+ Links pointing to your WordPress site are internal links. All other links will be treated as external links.
49
+
50
+ = I want links to my own domain not being treated as external links. =
51
+
52
+ Add your domain to the option "Ingore links (URL) containing...".
53
+
54
+ [Do you have a question? Please ask me](http://www.freelancephp.net/contact/)
55
+
56
+ == Screenshots ==
57
+
58
+ 1. Link Icon on the Site
59
+ 1. Admin Settings Page
60
+
61
+ == Documentation ==
62
+
63
+ After activating the plugin all options are already set to make your external links SEO friendly. Optionally you can also set the target for opening in a new window or tab or styling options, like adding an icon.
64
+
65
+ = Action hook =
66
+ The plugin also has a hook when ready, f.e. to add extra filters:
67
+ `function extra_filters($filter_callback, $object) {
68
+ add_filter('some_filter', $filter_callback);
69
+ }
70
+ add_action('wpel_ready', 'extra_filters');`
71
+
72
+ = Filter hook =
73
+ The wpel_external_link filter gives you the possibility to manipulate output of the mailto created by the plugin, like:
74
+ `function special_external_link($created_link, $original_link, $label, $attrs, $is_ignored_link) {
75
+ // skip links that contain the class "not-external"
76
+ if (isset($attrs['class']) && strpos($attrs['class'], 'not-external') !== false) {
77
+ return $original_link;
78
+ }
79
+
80
+ return '<b>'. $created_link .'</b>';
81
+ }
82
+ add_filter('wpel_external_link', 'special_external_link', 10, 5);`
83
+
84
+ Now all external links will be processed and wrapped around a `<b>`-tag. And links containing the class "not-external" will not be processed by the plugin at all (and stay the way they are).
85
+
86
+ = Credits =
87
+ * [jQuery Tipsy Plugin](http://plugins.jquery.com/project/tipsy) made by [Jason Frame](http://onehackoranother.com/)
88
+ * [phpQuery](http://code.google.com/p/phpquery/) made by [Tobiasz Cudnik](http://tobiasz123.wordpress.com)
89
+ * [Icon](http://findicons.com/icon/164579/link_go?id=427009) made by [FatCow Web Hosting](http://www.fatcow.com/)
90
+
91
+ == Changelog ==
92
+
93
+ = 1.52 =
94
+ * Added filter hook wpel_internal_link
95
+ * Fixed use_js option bug
96
+ * Fixed bug loading non-existing stylesheet
97
+ * Minified javascripts
98
+
99
+ = 1.51 =
100
+ * Fixed also check url's starting with //
101
+ * Fixed wpel_external_link also applied on ignored links
102
+
103
+ = 1.50 =
104
+ * Removed stylesheet file to save extra request
105
+ * Added option for loading js file in wp_footer
106
+ * Fixed bug with data-* attributes
107
+ * Fixed bug url's with hash at the end
108
+ * Fixed PHP errors
109
+
110
+ = 1.41 =
111
+ * Fixed Bug: wpmel_external_link filter hook was not working correctly
112
+
113
+ = 1.40 =
114
+ * Added action hook wpel_ready
115
+ * Added filter hook wpel_external_link
116
+ * Added output flush on wp_footer
117
+ * Fixed Bug: spaces before url in href-attribute not recognized as external link
118
+ * Fixed Bug: external links not processed (regexpr tag conflict starting with an a, like <aside> or <article>)
119
+ * Cosmetic changes: added "Admin Settings", replaced help icon, restyled tooltip texts, removed "About this plugin" box
120
+
121
+ = 1.31 =
122
+ * Fixed passing arguments by reference using & (deprecated for PHP 5.4+)
123
+ * Fixed options save failure by adding a non-ajax submit fallback
124
+
125
+ = 1.30 =
126
+ * Re-arranged options in metaboxes
127
+ * Added option for no icons on images
128
+
129
+ = 1.21 =
130
+ * Fixed phpQuery bugs (class already exists and loading stylesheet)
131
+ * Solved php notices
132
+
133
+ = 1.20 =
134
+ * Added option to ignore certain links or domains
135
+ * Solved tweet button problem by adding link to new ignore option
136
+ * Made JavaScript method consistent to not using JS
137
+ * Solved PHP warnings
138
+ * Solved bug adding own class
139
+ * Changed bloginfo "url" to "wpurl"
140
+
141
+ = 1.10 =
142
+ * Resolved old parsing method (same as version 0.35)
143
+ * Option to use phpQuery for parsing (for those who didn't experience problems with version 1.03)
144
+
145
+ = 1.03 =
146
+ * Workaround for echo DOCTYPE bug (caused by attributes in the head-tag)
147
+
148
+ = 1.02 =
149
+ * Solved the not working activation hook
150
+
151
+ = 1.01 =
152
+ * Solved bug after live testing
153
+
154
+ = 1.00 =
155
+ * Added option for setting title-attribute
156
+ * Added option for excluding filtering certain external links
157
+ * Added Admin help tooltips using jQuery Tipsy Plugin
158
+ * Reorginized files and refactored code to PHP5 (no support for PHP4)
159
+ * Added WP built-in meta box functionallity (using the `WP_Meta_Box_Page` Class)
160
+ * Reorganized saving options and added Ajax save method (using the `WP_Option_Forms` Class)
161
+ * Removed Regexp and using phpQuery
162
+ * Choose menu position for this plugin (see "Screen Options")
163
+ * Removed possibility to convert all `<a>` tags to xhtml clean code (so only external links will be converted)
164
+ * Removed "Solve problem" options
165
+
166
+ = 0.35 =
167
+ * Widget Logic options bug
168
+
169
+ = 0.34 =
170
+ * Added option only converting external `<a>` tags to XHTML valid code
171
+ * Changed script attribute `language` to `type`
172
+ * Added support for widget_content filter of the Logic Widget plugin
173
+
174
+ = 0.33 =
175
+ * Added option to fix js problem
176
+ * Fixed PHP / WP notices
177
+
178
+ = 0.32 =
179
+ * For jQuery uses live() function so also opens dynamicly created links in given target
180
+ * Fixed bug of changing `<abbr>` tag
181
+ * Small cosmetical adjustments
182
+
183
+ = 0.31 =
184
+ * Small cosmetical adjustments
185
+
186
+ = 0.30 =
187
+ * Improved Admin Options, f.e. target option looks more like the Blogroll target option
188
+ * Added option for choosing which content should be filtered
189
+
190
+ = 0.21 =
191
+ * Solved bug removing icon stylesheet
192
+
193
+ = 0.20 =
194
+ * Put icon styles in external stylesheet
195
+ * Can use "ext-icon-..." to show a specific icon on a link
196
+ * Added option to set your own No-Icon class
197
+ * Made "Class" optional, so it's not used for showing icons anymore
198
+ * Added 3 more icons
199
+
200
+ = 0.12 =
201
+ * Options are organized more logical
202
+ * Added some more icons
203
+
204
+ = 0.11 =
205
+ * JavaScript uses window.open() (tested in FireFox Opera, Safari, Chrome and IE6+)
206
+ * Also possible to open all external links in the same new window
207
+ * Some layout changes on the Admin Options Page
208
+
209
+ = 0.10 =
210
+ * Features: opening in a new window, set link icon, set "external", set "nofollow", set css-class
211
+ * Replaces external links by clean XHTML <a> tags
212
+ * Internalization implemented (no language files yet)
wp-external-links.php CHANGED
@@ -1,53 +1,53 @@
1
- <?php defined('ABSPATH') OR die('No direct access.');
2
- /*
3
- Plugin Name: WP External Links
4
- Plugin URI: http://www.freelancephp.net/wp-external-links-plugin
5
- Description: Open external links in a new window/tab, add "external" / "nofollow" to rel-attribute, set icon, XHTML strict, SEO friendly...
6
- Author: Victor Villaverde Laan
7
- Version: 1.51
8
- Author URI: http://www.freelancephp.net
9
- License: Dual licensed under the MIT and GPL licenses
10
- */
11
-
12
- // constants
13
- if (!defined('WP_EXTERNAL_LINKS_FILE')) { define('WP_EXTERNAL_LINKS_FILE', __FILE__); }
14
- if (!defined('WP_EXTERNAL_LINKS_VERSION')) { define('WP_EXTERNAL_LINKS_VERSION', '1.51'); }
15
- if (!defined('WP_EXTERNAL_LINKS_KEY')) { define('WP_EXTERNAL_LINKS_KEY', 'wp_external_links'); }
16
- if (!defined('WP_EXTERNAL_LINKS_DOMAIN')) { define('WP_EXTERNAL_LINKS_DOMAIN', 'wp-external-links'); }
17
- if (!defined('WP_EXTERNAL_LINKS_OPTIONS_NAME')) { define('WP_EXTERNAL_LINKS_OPTIONS_NAME', 'WP_External_Links_options'); }
18
- if (!defined('WP_EXTERNAL_LINKS_ADMIN_PAGE')) { define('WP_EXTERNAL_LINKS_ADMIN_PAGE', 'wp-external-links-settings'); }
19
-
20
- // check plugin compatibility
21
- if (isset($wp_version)
22
- AND version_compare(preg_replace('/-.*$/', '', $wp_version), '3.4', '>=')
23
- AND version_compare(phpversion(), '5.2.4', '>=')) {
24
-
25
- // include classes
26
- require_once('includes/wp-plugin-dev-classes/class-wp-meta-box-page.php');
27
- require_once('includes/wp-plugin-dev-classes/class-wp-option-forms.php');
28
- require_once('includes/class-admin-external-links.php');
29
- require_once('includes/class-wp-external-links.php');
30
-
31
- // create instance
32
- $WP_External_Links = new WP_External_Links();
33
-
34
- } else {
35
-
36
- // set error message
37
- if (!function_exists('wpel_error_notice')):
38
- function wpel_error_notice() {
39
- $plugin_title = get_admin_page_title();
40
-
41
- echo '<div class="error">'
42
- . sprintf(__('<p>Warning - The plugin <strong>%s</strong> requires PHP 5.2.4+ and WP 3.4+. Please upgrade your PHP and/or WordPress.'
43
- . '<br/>Disable the plugin to remove this message.</p>'
44
- , WP_EXTERNAL_LINKS_KEY), $plugin_title)
45
- . '</div>';
46
- }
47
-
48
- add_action('admin_notices', 'wpel_error_notice');
49
- endif;
50
-
51
- }
52
-
53
  /* ommit PHP closing tag, to prevent unwanted whitespace at the end of the parts generated by the included files */
1
+ <?php defined('ABSPATH') OR die('No direct access.');
2
+ /*
3
+ Plugin Name: WP External Links
4
+ Plugin URI: http://www.freelancephp.net/wp-external-links-plugin
5
+ Description: Open external links in a new window/tab, add "external" / "nofollow" to rel-attribute, set icon, XHTML strict, SEO friendly...
6
+ Author: Victor Villaverde Laan
7
+ Version: 1.52
8
+ Author URI: http://www.freelancephp.net
9
+ License: Dual licensed under the MIT and GPL licenses
10
+ */
11
+
12
+ // constants
13
+ if (!defined('WP_EXTERNAL_LINKS_FILE')) { define('WP_EXTERNAL_LINKS_FILE', __FILE__); }
14
+ if (!defined('WP_EXTERNAL_LINKS_VERSION')) { define('WP_EXTERNAL_LINKS_VERSION', '1.52'); }
15
+ if (!defined('WP_EXTERNAL_LINKS_KEY')) { define('WP_EXTERNAL_LINKS_KEY', 'wp_external_links'); }
16
+ if (!defined('WP_EXTERNAL_LINKS_DOMAIN')) { define('WP_EXTERNAL_LINKS_DOMAIN', 'wp-external-links'); }
17
+ if (!defined('WP_EXTERNAL_LINKS_OPTIONS_NAME')) { define('WP_EXTERNAL_LINKS_OPTIONS_NAME', 'WP_External_Links_options'); }
18
+ if (!defined('WP_EXTERNAL_LINKS_ADMIN_PAGE')) { define('WP_EXTERNAL_LINKS_ADMIN_PAGE', 'wp-external-links-settings'); }
19
+
20
+ // check plugin compatibility
21
+ if (isset($wp_version)
22
+ AND version_compare(preg_replace('/-.*$/', '', $wp_version), '3.4', '>=')
23
+ AND version_compare(phpversion(), '5.2.4', '>=')) {
24
+
25
+ // include classes
26
+ require_once('includes/wp-plugin-dev-classes/class-wp-meta-box-page.php');
27
+ require_once('includes/wp-plugin-dev-classes/class-wp-option-forms.php');
28
+ require_once('includes/class-admin-external-links.php');
29
+ require_once('includes/class-wp-external-links.php');
30
+
31
+ // create instance
32
+ $WP_External_Links = new WP_External_Links();
33
+
34
+ } else {
35
+
36
+ // set error message
37
+ if (!function_exists('wpel_error_notice')):
38
+ function wpel_error_notice() {
39
+ $plugin_title = get_admin_page_title();
40
+
41
+ echo '<div class="error">'
42
+ . sprintf(__('<p>Warning - The plugin <strong>%s</strong> requires PHP 5.2.4+ and WP 3.4+. Please upgrade your PHP and/or WordPress.'
43
+ . '<br/>Disable the plugin to remove this message.</p>'
44
+ , WP_EXTERNAL_LINKS_KEY), $plugin_title)
45
+ . '</div>';
46
+ }
47
+
48
+ add_action('admin_notices', 'wpel_error_notice');
49
+ endif;
50
+
51
+ }
52
+
53
  /* ommit PHP closing tag, to prevent unwanted whitespace at the end of the parts generated by the included files */