BJ Lazy Load - Version 0.7.0

Version Description

Download this release

Release Info

Developer bjornjohansen
Plugin Icon 128x128 BJ Lazy Load
Version 0.7.0
Comparing to
See all releases

Code changes from version 0.6.10 to 0.7.0

admin.php CHANGED
@@ -5,7 +5,7 @@ class BJLL_Admin_Page extends scbAdminPage {
5
  function setup() {
6
  $this->args = array(
7
  'menu_title' => 'BJ Lazy Load',
8
- 'page_title' => __( 'BJ Lazy Load Options', 'bj_lazy_load' ),
9
  );
10
  }
11
 
@@ -14,103 +14,103 @@ class BJLL_Admin_Page extends scbAdminPage {
14
 
15
  $optionfields = array(
16
  array(
17
- 'title' => __( 'Apply to content', 'bj_lazy_load' ),
18
  'type' => 'radio',
19
  'name' => 'filter_content',
20
- 'value' => array( 'yes' => __( 'Yes', 'bj_lazy_load' ), 'no' => __( 'No', 'bj_lazy_load' ) ),
21
  ),
22
  array(
23
- 'title' => __( 'Apply to post thumbnails', 'bj_lazy_load' ),
24
  'type' => 'radio',
25
  'name' => 'filter_post_thumbnails',
26
- 'value' => array( 'yes' => __( 'Yes', 'bj_lazy_load' ), 'no' => __( 'No', 'bj_lazy_load' ) ),
27
  ),
28
  array(
29
- 'title' => __( 'Apply to gravatars', 'bj_lazy_load' ),
30
  'type' => 'radio',
31
  'name' => 'filter_gravatars',
32
- 'value' => array( 'yes' => __( 'Yes', 'bj_lazy_load' ), 'no' => __( 'No', 'bj_lazy_load' ) ),
33
  ),
34
  array(
35
- 'title' => __( 'Lazy load images', 'bj_lazy_load' ),
36
  'type' => 'radio',
37
  'name' => 'lazy_load_images',
38
- 'value' => array( 'yes' => __( 'Yes', 'bj_lazy_load' ), 'no' => __( 'No', 'bj_lazy_load' ) ),
39
  ),
40
  array(
41
- 'title' => __( 'Lazy load iframes', 'bj_lazy_load' ),
42
  'type' => 'radio',
43
  'name' => 'lazy_load_iframes',
44
- 'value' => array( 'yes' => __( 'Yes', 'bj_lazy_load' ), 'no' => __( 'No', 'bj_lazy_load' ) ),
45
  ),
46
  array(
47
- 'title' => __( 'Theme loader function', 'bj_lazy_load' ),
48
  'type' => 'select',
49
  'name' => 'theme_loader_function',
50
  'value' => array( 'wp_footer', 'wp_head' ),
51
  ),
52
  array(
53
- 'title' => __( 'Placeholder Image URL', 'bj_lazy_load' ),
54
  'type' => 'text',
55
  'name' => 'placeholder_url',
56
- 'desc' => sprintf( '<p class="description">%s</p>', __( 'Leave blank for default', 'bj_lazy_load' ) ),
57
  ),
58
  array(
59
- 'title' => __( 'Skip images with classes', 'bj_lazy_load' ),
60
  'type' => 'text',
61
  'name' => 'skip_classes',
62
- 'desc' => sprintf( '<p class="description">%s</p>', __( 'Comma separated. Example: "no-lazy, lazy-ignore, image-235"', 'bj_lazy_load' ) ),
63
  ),
64
  array(
65
- 'title' => __( 'Threshold', 'bj_lazy_load' ),
66
  'type' => 'text',
67
  'name' => 'threshold',
68
- 'desc' => sprintf( '<p class="description">%s</p>', __( 'How close to the viewport the element should be when we load it. In pixels. Example: 200', 'bj_lazy_load' ) ),
69
  )
70
  );
71
 
72
  $optionfields[] = array(
73
- 'title' => __( 'Infinite scroll', 'bj_lazy_load' ),
74
  'type' => 'radio',
75
  'name' => 'infinite_scroll',
76
- 'value' => array( 'yes' => __( 'Yes', 'bj_lazy_load' ), 'no' => __( 'No', 'bj_lazy_load' ) ),
77
- 'desc' => sprintf( '<p class="description">%s</p>', __( 'Enable if your theme uses infinite scroll.', 'bj_lazy_load' ) ),
78
  );
79
 
80
  $optionfields[] = array(
81
- 'title' => __( 'Load hiDPI (retina) images', 'bj_lazy_load' ),
82
  'type' => 'radio',
83
  'name' => 'load_hidpi',
84
- 'value' => array( 'yes' => __( 'Yes', 'bj_lazy_load' ), 'no' => __( 'No', 'bj_lazy_load' ) ),
85
- 'desc' => sprintf( '<p class="description">%s</p>', __( 'Will load hiDPI version of the images if the current browser/screen supports them. (Experimental feature. Do NOT enable if you are using a CDN)', 'bj_lazy_load' ) ),
86
  );
87
 
88
  $optionfields[] = array(
89
- 'title' => __( 'Load responsive images', 'bj_lazy_load' ),
90
  'type' => 'radio',
91
  'name' => 'load_responsive',
92
- 'value' => array( 'yes' => __( 'Yes', 'bj_lazy_load' ), 'no' => __( 'No', 'bj_lazy_load' ) ),
93
- 'desc' => sprintf( '<p class="description">%s</p>', __( 'Will load scaled down version of the images if the image is scaled down in the theme. (Experimental feature. Do NOT enable if you are using a CDN)', 'bj_lazy_load' ) ),
94
  );
95
 
96
 
97
  if ( BJLL::has_wptouch() ) {
98
  $optionfields[] = array(
99
- 'title' => __( 'Disable on WPTouch', 'bj_lazy_load' ),
100
  'type' => 'radio',
101
  'name' => 'disable_on_wptouch',
102
- 'value' => array( 'yes' => __( 'Yes', 'bj_lazy_load' ), 'no' => __( 'No', 'bj_lazy_load' ) ),
103
- 'desc' => sprintf( '<p class="description">%s</p>', __( 'Disables BJ Lazy Load when the WPTouch mobile theme is used', 'bj_lazy_load' ) ),
104
  );
105
  }
106
 
107
  if ( BJLL::has_mobilepress() ) {
108
  $optionfields[] = array(
109
- 'title' => __( 'Disable on MobilePress', 'bj_lazy_load' ),
110
  'type' => 'radio',
111
  'name' => 'disable_on_mobilepress',
112
- 'value' => array( 'yes' => __( 'Yes', 'bj_lazy_load' ), 'no' => __( 'No', 'bj_lazy_load' ) ),
113
- 'desc' => sprintf( '<p class="description">%s</p>', __( 'Disables BJ Lazy Load when the MobilePress mobile theme is used', 'bj_lazy_load' ) ),
114
  );
115
  }
116
 
5
  function setup() {
6
  $this->args = array(
7
  'menu_title' => 'BJ Lazy Load',
8
+ 'page_title' => __( 'BJ Lazy Load Options', 'bj-lazy-load' ),
9
  );
10
  }
11
 
14
 
15
  $optionfields = array(
16
  array(
17
+ 'title' => __( 'Apply to content', 'bj-lazy-load' ),
18
  'type' => 'radio',
19
  'name' => 'filter_content',
20
+ 'value' => array( 'yes' => __( 'Yes', 'bj-lazy-load' ), 'no' => __( 'No', 'bj-lazy-load' ) ),
21
  ),
22
  array(
23
+ 'title' => __( 'Apply to post thumbnails', 'bj-lazy-load' ),
24
  'type' => 'radio',
25
  'name' => 'filter_post_thumbnails',
26
+ 'value' => array( 'yes' => __( 'Yes', 'bj-lazy-load' ), 'no' => __( 'No', 'bj-lazy-load' ) ),
27
  ),
28
  array(
29
+ 'title' => __( 'Apply to gravatars', 'bj-lazy-load' ),
30
  'type' => 'radio',
31
  'name' => 'filter_gravatars',
32
+ 'value' => array( 'yes' => __( 'Yes', 'bj-lazy-load' ), 'no' => __( 'No', 'bj-lazy-load' ) ),
33
  ),
34
  array(
35
+ 'title' => __( 'Lazy load images', 'bj-lazy-load' ),
36
  'type' => 'radio',
37
  'name' => 'lazy_load_images',
38
+ 'value' => array( 'yes' => __( 'Yes', 'bj-lazy-load' ), 'no' => __( 'No', 'bj-lazy-load' ) ),
39
  ),
40
  array(
41
+ 'title' => __( 'Lazy load iframes', 'bj-lazy-load' ),
42
  'type' => 'radio',
43
  'name' => 'lazy_load_iframes',
44
+ 'value' => array( 'yes' => __( 'Yes', 'bj-lazy-load' ), 'no' => __( 'No', 'bj-lazy-load' ) ),
45
  ),
46
  array(
47
+ 'title' => __( 'Theme loader function', 'bj-lazy-load' ),
48
  'type' => 'select',
49
  'name' => 'theme_loader_function',
50
  'value' => array( 'wp_footer', 'wp_head' ),
51
  ),
52
  array(
53
+ 'title' => __( 'Placeholder Image URL', 'bj-lazy-load' ),
54
  'type' => 'text',
55
  'name' => 'placeholder_url',
56
+ 'desc' => sprintf( '<p class="description">%s</p>', __( 'Leave blank for default', 'bj-lazy-load' ) ),
57
  ),
58
  array(
59
+ 'title' => __( 'Skip images with classes', 'bj-lazy-load' ),
60
  'type' => 'text',
61
  'name' => 'skip_classes',
62
+ 'desc' => sprintf( '<p class="description">%s</p>', __( 'Comma separated. Example: "no-lazy, lazy-ignore, image-235"', 'bj-lazy-load' ) ),
63
  ),
64
  array(
65
+ 'title' => __( 'Threshold', 'bj-lazy-load' ),
66
  'type' => 'text',
67
  'name' => 'threshold',
68
+ 'desc' => sprintf( '<p class="description">%s</p>', __( 'How close to the viewport the element should be when we load it. In pixels. Example: 200', 'bj-lazy-load' ) ),
69
  )
70
  );
71
 
72
  $optionfields[] = array(
73
+ 'title' => __( 'Infinite scroll', 'bj-lazy-load' ),
74
  'type' => 'radio',
75
  'name' => 'infinite_scroll',
76
+ 'value' => array( 'yes' => __( 'Yes', 'bj-lazy-load' ), 'no' => __( 'No', 'bj-lazy-load' ) ),
77
+ 'desc' => sprintf( '<p class="description">%s</p>', __( 'Enable if your theme uses infinite scroll.', 'bj-lazy-load' ) ),
78
  );
79
 
80
  $optionfields[] = array(
81
+ 'title' => __( 'Load hiDPI (retina) images', 'bj-lazy-load' ),
82
  'type' => 'radio',
83
  'name' => 'load_hidpi',
84
+ 'value' => array( 'yes' => __( 'Yes', 'bj-lazy-load' ), 'no' => __( 'No', 'bj-lazy-load' ) ),
85
+ 'desc' => sprintf( '<p class="description">%s</p>', __( 'Will load hiDPI version of the images if the current browser/screen supports them. (Experimental feature. Do NOT enable if you are using a CDN)', 'bj-lazy-load' ) ),
86
  );
87
 
88
  $optionfields[] = array(
89
+ 'title' => __( 'Load responsive images', 'bj-lazy-load' ),
90
  'type' => 'radio',
91
  'name' => 'load_responsive',
92
+ 'value' => array( 'yes' => __( 'Yes', 'bj-lazy-load' ), 'no' => __( 'No', 'bj-lazy-load' ) ),
93
+ 'desc' => sprintf( '<p class="description">%s</p>', __( 'Will load scaled down version of the images if the image is scaled down in the theme. (Experimental feature. Do NOT enable if you are using a CDN)', 'bj-lazy-load' ) ),
94
  );
95
 
96
 
97
  if ( BJLL::has_wptouch() ) {
98
  $optionfields[] = array(
99
+ 'title' => __( 'Disable on WPTouch', 'bj-lazy-load' ),
100
  'type' => 'radio',
101
  'name' => 'disable_on_wptouch',
102
+ 'value' => array( 'yes' => __( 'Yes', 'bj-lazy-load' ), 'no' => __( 'No', 'bj-lazy-load' ) ),
103
+ 'desc' => sprintf( '<p class="description">%s</p>', __( 'Disables BJ Lazy Load when the WPTouch mobile theme is used', 'bj-lazy-load' ) ),
104
  );
105
  }
106
 
107
  if ( BJLL::has_mobilepress() ) {
108
  $optionfields[] = array(
109
+ 'title' => __( 'Disable on MobilePress', 'bj-lazy-load' ),
110
  'type' => 'radio',
111
  'name' => 'disable_on_mobilepress',
112
+ 'value' => array( 'yes' => __( 'Yes', 'bj-lazy-load' ), 'no' => __( 'No', 'bj-lazy-load' ) ),
113
+ 'desc' => sprintf( '<p class="description">%s</p>', __( 'Disables BJ Lazy Load when the MobilePress mobile theme is used', 'bj-lazy-load' ) ),
114
  );
115
  }
116
 
bj-lazy-load.php CHANGED
@@ -3,9 +3,10 @@
3
  Plugin Name: BJ Lazy Load
4
  Plugin URI: http://wordpress.org/extend/plugins/bj-lazy-load/
5
  Description: Lazy image loading makes your site load faster and saves bandwidth.
6
- Version: 0.6.10
7
  Author: Bjørn Johansen
8
  Author URI: http://twitter.com/bjornjohansen
 
9
  License: GPL2
10
 
11
  Copyright 2011–2013 Bjørn Johansen (email : post@bjornjohansen.no)
@@ -26,11 +27,13 @@ License: GPL2
26
  */
27
 
28
  require_once( dirname(__FILE__) . '/scb/load.php' );
 
 
29
 
30
  if ( ! class_exists( 'BJLL' ) ) {
31
  class BJLL {
32
 
33
- const version = '0.6.10';
34
  protected $_placeholder_url;
35
  protected $_skip_classes;
36
 
@@ -59,6 +62,7 @@ if ( ! class_exists( 'BJLL' ) ) {
59
  }
60
 
61
  add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
 
62
 
63
  $skip_classes = $options->get( 'skip_classes' );
64
  if ( strlen( trim( $skip_classes ) ) ) {
@@ -67,7 +71,8 @@ if ( ! class_exists( 'BJLL' ) ) {
67
 
68
  $this->_placeholder_url = $options->get( 'placeholder_url' );
69
  if ( ! strlen( $this->_placeholder_url ) ) {
70
- $this->_placeholder_url = plugins_url( '/img/placeholder.gif', __FILE__ );
 
71
  }
72
 
73
  if ( $options->get( 'filter_content' ) == 'yes' ) {
@@ -138,6 +143,13 @@ if ( ! class_exists( 'BJLL' ) ) {
138
  }
139
 
140
  static function filter( $content ) {
 
 
 
 
 
 
 
141
 
142
  $BJLL = BJLL::singleton();
143
 
3
  Plugin Name: BJ Lazy Load
4
  Plugin URI: http://wordpress.org/extend/plugins/bj-lazy-load/
5
  Description: Lazy image loading makes your site load faster and saves bandwidth.
6
+ Version: 0.7.0
7
  Author: Bjørn Johansen
8
  Author URI: http://twitter.com/bjornjohansen
9
+ Text Domain: bj-lazy-load
10
  License: GPL2
11
 
12
  Copyright 2011–2013 Bjørn Johansen (email : post@bjornjohansen.no)
27
  */
28
 
29
  require_once( dirname(__FILE__) . '/scb/load.php' );
30
+ require_once( dirname(__FILE__) . '/inc/lang.php' );
31
+ require_once( dirname(__FILE__) . '/inc/class-bjll-skip-post.php' );
32
 
33
  if ( ! class_exists( 'BJLL' ) ) {
34
  class BJLL {
35
 
36
+ const version = '0.7.0';
37
  protected $_placeholder_url;
38
  protected $_skip_classes;
39
 
62
  }
63
 
64
  add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
65
+ add_filter( 'bj_lazy_load_html', array( __CLASS__, 'filter' ), 10, 1 );
66
 
67
  $skip_classes = $options->get( 'skip_classes' );
68
  if ( strlen( trim( $skip_classes ) ) ) {
71
 
72
  $this->_placeholder_url = $options->get( 'placeholder_url' );
73
  if ( ! strlen( $this->_placeholder_url ) ) {
74
+ //$this->_placeholder_url = plugins_url( '/img/placeholder.gif', __FILE__ );
75
+ $this->_placeholder_url = 'data:image/gif;base64,R0lGODdhAQABAPAAAP///wAAACwAAAAAAQABAEACAkQBADs=';
76
  }
77
 
78
  if ( $options->get( 'filter_content' ) == 'yes' ) {
143
  }
144
 
145
  static function filter( $content ) {
146
+
147
+ $run_filter = true;
148
+ $run_filter = apply_filters( 'bj_lazy_load_run_filter', $content );
149
+
150
+ if ( ! $run_filter ) {
151
+ return $content;
152
+ }
153
 
154
  $BJLL = BJLL::singleton();
155
 
inc/class-bjll-skip-post.php ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ License: GPL2
4
+
5
+ Copyright 2011–2013 Bjørn Johansen (email : post@bjornjohansen.no)
6
+
7
+ This program is free software; you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License, version 2, as
9
+ published by the Free Software Foundation.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program; if not, write to the Free Software
18
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
+
20
+ */
21
+
22
+ if ( ! class_exists( 'BJLL_Skip_Post' ) ) {
23
+
24
+ class BJLL_Skip_Post {
25
+ function __construct() {
26
+
27
+ add_action( 'add_meta_boxes', array( $this, 'bjll_add_meta_box' ) );
28
+ add_action( 'save_post', array( $this, 'bjll_save_post' ) );
29
+
30
+ add_filter( 'bj_lazy_load_run_filter', array( $this, 'run_filter_check' ), 10, 1 );
31
+ }
32
+
33
+ public function bjll_add_meta_box() {
34
+ $post_types = get_post_types( array( 'public' => true ), 'names' );
35
+
36
+ foreach ( $post_types as $post_type ) {
37
+ add_meta_box( 'bj_lazy_load_skip_post', __( 'Lazy Loading', 'bj-lazy-load' ), array( $this, 'bj_lazy_load_skip_post_meta_box' ), $post_type, 'side', 'low' );
38
+ }
39
+ }
40
+
41
+ public function bj_lazy_load_skip_post_meta_box( $post ) {
42
+ wp_nonce_field( 'bj_lazy_load_skip_post_meta_box', 'bj_lazy_load_skip_post_meta_box_nonce' );
43
+
44
+ $bj_lazy_load_skip_post_value = get_post_meta( $post->ID, '_bj_lazy_load_skip_post', true );
45
+
46
+ printf( '<input type="checkbox" id="bj_lazy_load_skip_post_value" name="bj_lazy_load_skip_post_value" value="true" size="25" %s>', checked( $bj_lazy_load_skip_post_value, 'true', false ) );
47
+
48
+ printf(
49
+ '<label for="bj_lazy_load_skip_post_value"> %s</label>',
50
+ $post->post_type == 'page' ? __( 'Skip lazy loading for this page', 'bj-lazy-load' ) : __( 'Skip lazy loading for this post', 'bj-lazy-load' )
51
+ );
52
+
53
+ }
54
+
55
+ public function bjll_save_post( $post_id ) {
56
+ // Check if our nonce is set.
57
+ if ( ! isset( $_POST['bj_lazy_load_skip_post_meta_box_nonce'] ) ) {
58
+ return $post_id;
59
+ }
60
+
61
+ // Verify that the nonce is valid.
62
+ if ( ! wp_verify_nonce( $_POST['bj_lazy_load_skip_post_meta_box_nonce'], 'bj_lazy_load_skip_post_meta_box' ) ) {
63
+ return $post_id;
64
+ }
65
+
66
+
67
+ $bj_lazy_load_skip_post_value = 'false';
68
+ if ( isset( $_POST['bj_lazy_load_skip_post_value'] ) && 'true' == $_POST['bj_lazy_load_skip_post_value'] ) {
69
+ $bj_lazy_load_skip_post_value = 'true';
70
+ }
71
+
72
+ // Update the meta field in the database.
73
+ update_post_meta( $post_id, '_bj_lazy_load_skip_post', $bj_lazy_load_skip_post_value );
74
+
75
+ }
76
+
77
+ public function run_filter_check( $content ) {
78
+
79
+ $run_filter = true;
80
+
81
+ if ( in_the_loop() && 'true' == get_post_meta( get_the_ID(), '_bj_lazy_load_skip_post', true ) ) {
82
+ $run_filter = false;
83
+ }
84
+
85
+ return $run_filter;
86
+
87
+ }
88
+ }
89
+
90
+ }
91
+
92
+ new BJLL_Skip_Post;
inc/index.html ADDED
File without changes
inc/lang.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function bjll_load_plugin_textdomain() {
4
+ load_plugin_textdomain( 'bj-lazy-load', false, 'bj-lazy-load/lang/' );
5
+ }
6
+ add_action( 'plugins_loaded', 'bjll_load_plugin_textdomain' );
inc/timthumb.php ADDED
@@ -0,0 +1,1251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * TimThumb by Ben Gillbanks and Mark Maunder
4
+ * Based on work done by Tim McDaniels and Darren Hoyt
5
+ * http://code.google.com/p/timthumb/
6
+ *
7
+ * GNU General Public License, version 2
8
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
9
+ *
10
+ * Examples and documentation available on the project homepage
11
+ * http://www.binarymoon.co.uk/projects/timthumb/
12
+ *
13
+ * $Rev$
14
+ */
15
+
16
+ /*
17
+ * --- TimThumb CONFIGURATION ---
18
+ * To edit the configs it is best to create a file called timthumb-config.php
19
+ * and define variables you want to customize in there. It will automatically be
20
+ * loaded by timthumb. This will save you having to re-edit these variables
21
+ * everytime you download a new version
22
+ */
23
+ define ('VERSION', '2.8.11'); // Version of this script
24
+ //Load a config file if it exists. Otherwise, use the values below
25
+ if( file_exists(dirname(__FILE__) . '/timthumb-config.php')) require_once('timthumb-config.php');
26
+ if(! defined('DEBUG_ON') ) define ('DEBUG_ON', false); // Enable debug logging to web server error log (STDERR)
27
+ if(! defined('DEBUG_LEVEL') ) define ('DEBUG_LEVEL', 1); // Debug level 1 is less noisy and 3 is the most noisy
28
+ if(! defined('MEMORY_LIMIT') ) define ('MEMORY_LIMIT', '30M'); // Set PHP memory limit
29
+ if(! defined('BLOCK_EXTERNAL_LEECHERS') ) define ('BLOCK_EXTERNAL_LEECHERS', false); // If the image or webshot is being loaded on an external site, display a red "No Hotlinking" gif.
30
+
31
+ //Image fetching and caching
32
+ if(! defined('ALLOW_EXTERNAL') ) define ('ALLOW_EXTERNAL', TRUE); // Allow image fetching from external websites. Will check against ALLOWED_SITES if ALLOW_ALL_EXTERNAL_SITES is false
33
+ if(! defined('ALLOW_ALL_EXTERNAL_SITES') ) define ('ALLOW_ALL_EXTERNAL_SITES', false); // Less secure.
34
+ if(! defined('FILE_CACHE_ENABLED') ) define ('FILE_CACHE_ENABLED', TRUE); // Should we store resized/modified images on disk to speed things up?
35
+ if(! defined('FILE_CACHE_TIME_BETWEEN_CLEANS')) define ('FILE_CACHE_TIME_BETWEEN_CLEANS', 86400); // How often the cache is cleaned
36
+
37
+ if(! defined('FILE_CACHE_MAX_FILE_AGE') ) define ('FILE_CACHE_MAX_FILE_AGE', 86400); // How old does a file have to be to be deleted from the cache
38
+ if(! defined('FILE_CACHE_SUFFIX') ) define ('FILE_CACHE_SUFFIX', '.timthumb.txt'); // What to put at the end of all files in the cache directory so we can identify them
39
+ if(! defined('FILE_CACHE_PREFIX') ) define ('FILE_CACHE_PREFIX', 'timthumb'); // What to put at the beg of all files in the cache directory so we can identify them
40
+ if(! defined('FILE_CACHE_DIRECTORY') ) define ('FILE_CACHE_DIRECTORY', './cache'); // Directory where images are cached. Left blank it will use the system temporary directory (which is better for security)
41
+ if(! defined('MAX_FILE_SIZE') ) define ('MAX_FILE_SIZE', 10485760); // 10 Megs is 10485760. This is the max internal or external file size that we'll process.
42
+ if(! defined('CURL_TIMEOUT') ) define ('CURL_TIMEOUT', 20); // Timeout duration for Curl. This only applies if you have Curl installed and aren't using PHP's default URL fetching mechanism.
43
+ if(! defined('WAIT_BETWEEN_FETCH_ERRORS') ) define ('WAIT_BETWEEN_FETCH_ERRORS', 3600); // Time to wait between errors fetching remote file
44
+
45
+ //Browser caching
46
+ if(! defined('BROWSER_CACHE_MAX_AGE') ) define ('BROWSER_CACHE_MAX_AGE', 864000); // Time to cache in the browser
47
+ if(! defined('BROWSER_CACHE_DISABLE') ) define ('BROWSER_CACHE_DISABLE', false); // Use for testing if you want to disable all browser caching
48
+
49
+ //Image size and defaults
50
+ if(! defined('MAX_WIDTH') ) define ('MAX_WIDTH', 1500); // Maximum image width
51
+ if(! defined('MAX_HEIGHT') ) define ('MAX_HEIGHT', 1500); // Maximum image height
52
+ if(! defined('NOT_FOUND_IMAGE') ) define ('NOT_FOUND_IMAGE', ''); // Image to serve if any 404 occurs
53
+ if(! defined('ERROR_IMAGE') ) define ('ERROR_IMAGE', ''); // Image to serve if an error occurs instead of showing error message
54
+ if(! defined('PNG_IS_TRANSPARENT') ) define ('PNG_IS_TRANSPARENT', FALSE); // Define if a png image should have a transparent background color. Use False value if you want to display a custom coloured canvas_colour
55
+ if(! defined('DEFAULT_Q') ) define ('DEFAULT_Q', 90); // Default image quality. Allows overrid in timthumb-config.php
56
+ if(! defined('DEFAULT_ZC') ) define ('DEFAULT_ZC', 1); // Default zoom/crop setting. Allows overrid in timthumb-config.php
57
+ if(! defined('DEFAULT_F') ) define ('DEFAULT_F', ''); // Default image filters. Allows overrid in timthumb-config.php
58
+ if(! defined('DEFAULT_S') ) define ('DEFAULT_S', 0); // Default sharpen value. Allows overrid in timthumb-config.php
59
+ if(! defined('DEFAULT_CC') ) define ('DEFAULT_CC', 'ffffff'); // Default canvas colour. Allows overrid in timthumb-config.php
60
+
61
+
62
+ //Image compression is enabled if either of these point to valid paths
63
+
64
+ //These are now disabled by default because the file sizes of PNGs (and GIFs) are much smaller than we used to generate.
65
+ //They only work for PNGs. GIFs and JPEGs are not affected.
66
+ if(! defined('OPTIPNG_ENABLED') ) define ('OPTIPNG_ENABLED', false);
67
+ if(! defined('OPTIPNG_PATH') ) define ('OPTIPNG_PATH', '/usr/bin/optipng'); //This will run first because it gives better compression than pngcrush.
68
+ if(! defined('PNGCRUSH_ENABLED') ) define ('PNGCRUSH_ENABLED', false);
69
+ if(! defined('PNGCRUSH_PATH') ) define ('PNGCRUSH_PATH', '/usr/bin/pngcrush'); //This will only run if OPTIPNG_PATH is not set or is not valid
70
+
71
+ /*
72
+ -------====Website Screenshots configuration - BETA====-------
73
+
74
+ If you just want image thumbnails and don't want website screenshots, you can safely leave this as is.
75
+
76
+ If you would like to get website screenshots set up, you will need root access to your own server.
77
+
78
+ Enable ALLOW_ALL_EXTERNAL_SITES so you can fetch any external web page. This is more secure now that we're using a non-web folder for cache.
79
+ Enable BLOCK_EXTERNAL_LEECHERS so that your site doesn't generate thumbnails for the whole Internet.
80
+
81
+ Instructions to get website screenshots enabled on Ubuntu Linux:
82
+
83
+ 1. Install Xvfb with the following command: sudo apt-get install subversion libqt4-webkit libqt4-dev g++ xvfb
84
+ 2. Go to a directory where you can download some code
85
+ 3. Check-out the latest version of CutyCapt with the following command: svn co https://cutycapt.svn.sourceforge.net/svnroot/cutycapt
86
+ 4. Compile CutyCapt by doing: cd cutycapt/CutyCapt
87
+ 5. qmake
88
+ 6. make
89
+ 7. cp CutyCapt /usr/local/bin/
90
+ 8. Test it by running: xvfb-run --server-args="-screen 0, 1024x768x24" CutyCapt --url="http://markmaunder.com/" --out=test.png
91
+ 9. If you get a file called test.png with something in it, it probably worked. Now test the script by accessing it as follows:
92
+ 10. http://yoursite.com/path/to/timthumb.php?src=http://markmaunder.com/&webshot=1
93
+
94
+ Notes on performance:
95
+ The first time a webshot loads, it will take a few seconds.
96
+ From then on it uses the regular timthumb caching mechanism with the configurable options above
97
+ and loading will be very fast.
98
+
99
+ --ADVANCED USERS ONLY--
100
+ If you'd like a slight speedup (about 25%) and you know Linux, you can run the following command which will keep Xvfb running in the background.
101
+ nohup Xvfb :100 -ac -nolisten tcp -screen 0, 1024x768x24 > /dev/null 2>&1 &
102
+ Then set WEBSHOT_XVFB_RUNNING = true below. This will save your server having to fire off a new Xvfb server and shut it down every time a new shot is generated.
103
+ You will need to take responsibility for keeping Xvfb running in case it crashes. (It seems pretty stable)
104
+ You will also need to take responsibility for server security if you're running Xvfb as root.
105
+
106
+
107
+ */
108
+ if(! defined('WEBSHOT_ENABLED') ) define ('WEBSHOT_ENABLED', false); //Beta feature. Adding webshot=1 to your query string will cause the script to return a browser screenshot rather than try to fetch an image.
109
+ if(! defined('WEBSHOT_CUTYCAPT') ) define ('WEBSHOT_CUTYCAPT', '/usr/local/bin/CutyCapt'); //The path to CutyCapt.
110
+ if(! defined('WEBSHOT_XVFB') ) define ('WEBSHOT_XVFB', '/usr/bin/xvfb-run'); //The path to the Xvfb server
111
+ if(! defined('WEBSHOT_SCREEN_X') ) define ('WEBSHOT_SCREEN_X', '1024'); //1024 works ok
112
+ if(! defined('WEBSHOT_SCREEN_Y') ) define ('WEBSHOT_SCREEN_Y', '768'); //768 works ok
113
+ if(! defined('WEBSHOT_COLOR_DEPTH') ) define ('WEBSHOT_COLOR_DEPTH', '24'); //I haven't tested anything besides 24
114
+ if(! defined('WEBSHOT_IMAGE_FORMAT') ) define ('WEBSHOT_IMAGE_FORMAT', 'png'); //png is about 2.5 times the size of jpg but is a LOT better quality
115
+ if(! defined('WEBSHOT_TIMEOUT') ) define ('WEBSHOT_TIMEOUT', '20'); //Seconds to wait for a webshot
116
+ if(! defined('WEBSHOT_USER_AGENT') ) define ('WEBSHOT_USER_AGENT', "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18"); //I hate to do this, but a non-browser robot user agent might not show what humans see. So we pretend to be Firefox
117
+ if(! defined('WEBSHOT_JAVASCRIPT_ON') ) define ('WEBSHOT_JAVASCRIPT_ON', true); //Setting to false might give you a slight speedup and block ads. But it could cause other issues.
118
+ if(! defined('WEBSHOT_JAVA_ON') ) define ('WEBSHOT_JAVA_ON', false); //Have only tested this as fase
119
+ if(! defined('WEBSHOT_PLUGINS_ON') ) define ('WEBSHOT_PLUGINS_ON', true); //Enable flash and other plugins
120
+ if(! defined('WEBSHOT_PROXY') ) define ('WEBSHOT_PROXY', ''); //In case you're behind a proxy server.
121
+ if(! defined('WEBSHOT_XVFB_RUNNING') ) define ('WEBSHOT_XVFB_RUNNING', false); //ADVANCED: Enable this if you've got Xvfb running in the background.
122
+
123
+
124
+ // If ALLOW_EXTERNAL is true and ALLOW_ALL_EXTERNAL_SITES is false, then external images will only be fetched from these domains and their subdomains.
125
+ if(! isset($ALLOWED_SITES)){
126
+ $ALLOWED_SITES = array (
127
+ 'flickr.com',
128
+ 'staticflickr.com',
129
+ 'picasa.com',
130
+ 'img.youtube.com',
131
+ 'upload.wikimedia.org',
132
+ 'photobucket.com',
133
+ 'imgur.com',
134
+ 'imageshack.us',
135
+ 'tinypic.com',
136
+ );
137
+ }
138
+ // -------------------------------------------------------------
139
+ // -------------- STOP EDITING CONFIGURATION HERE --------------
140
+ // -------------------------------------------------------------
141
+
142
+ timthumb::start();
143
+
144
+ class timthumb {
145
+ protected $src = "";
146
+ protected $is404 = false;
147
+ protected $docRoot = "";
148
+ protected $lastURLError = false;
149
+ protected $localImage = "";
150
+ protected $localImageMTime = 0;
151
+ protected $url = false;
152
+ protected $myHost = "";
153
+ protected $isURL = false;
154
+ protected $cachefile = '';
155
+ protected $errors = array();
156
+ protected $toDeletes = array();
157
+ protected $cacheDirectory = '';
158
+ protected $startTime = 0;
159
+ protected $lastBenchTime = 0;
160
+ protected $cropTop = false;
161
+ protected $salt = "";
162
+ protected $fileCacheVersion = 1; //Generally if timthumb.php is modifed (upgraded) then the salt changes and all cache files are recreated. This is a backup mechanism to force regen.
163
+ protected $filePrependSecurityBlock = "<?php die('Execution denied!'); //"; //Designed to have three letter mime type, space, question mark and greater than symbol appended. 6 bytes total.
164
+ protected static $curlDataWritten = 0;
165
+ protected static $curlFH = false;
166
+ public static function start(){
167
+ $tim = new timthumb();
168
+ $tim->handleErrors();
169
+ $tim->securityChecks();
170
+ if($tim->tryBrowserCache()){
171
+ exit(0);
172
+ }
173
+ $tim->handleErrors();
174
+ if(FILE_CACHE_ENABLED && $tim->tryServerCache()){
175
+ exit(0);
176
+ }
177
+ $tim->handleErrors();
178
+ $tim->run();
179
+ $tim->handleErrors();
180
+ exit(0);
181
+ }
182
+ public function __construct(){
183
+ global $ALLOWED_SITES;
184
+ $this->startTime = microtime(true);
185
+ date_default_timezone_set('UTC');
186
+ $this->debug(1, "Starting new request from " . $this->getIP() . " to " . $_SERVER['REQUEST_URI']);
187
+ $this->calcDocRoot();
188
+ //On windows systems I'm assuming fileinode returns an empty string or a number that doesn't change. Check this.
189
+ $this->salt = @filemtime(__FILE__) . '-' . @fileinode(__FILE__);
190
+ $this->debug(3, "Salt is: " . $this->salt);
191
+ if(FILE_CACHE_DIRECTORY){
192
+ if(! is_dir(FILE_CACHE_DIRECTORY)){
193
+ @mkdir(FILE_CACHE_DIRECTORY);
194
+ if(! is_dir(FILE_CACHE_DIRECTORY)){
195
+ $this->error("Could not create the file cache directory.");
196
+ return false;
197
+ }
198
+ }
199
+ $this->cacheDirectory = FILE_CACHE_DIRECTORY;
200
+ if (!touch($this->cacheDirectory . '/index.html')) {
201
+ $this->error("Could not create the index.html file - to fix this create an empty file named index.html file in the cache directory.");
202
+ }
203
+ } else {
204
+ $this->cacheDirectory = sys_get_temp_dir();
205
+ }
206
+ //Clean the cache before we do anything because we don't want the first visitor after FILE_CACHE_TIME_BETWEEN_CLEANS expires to get a stale image.
207
+ $this->cleanCache();
208
+
209
+ $this->myHost = preg_replace('/^www\./i', '', $_SERVER['HTTP_HOST']);
210
+ $this->src = $this->param('src');
211
+ $this->url = parse_url($this->src);
212
+ $this->src = preg_replace('/https?:\/\/(?:www\.)?' . $this->myHost . '/i', '', $this->src);
213
+
214
+ if(strlen($this->src) <= 3){
215
+ $this->error("No image specified");
216
+ return false;
217
+ }
218
+ if(BLOCK_EXTERNAL_LEECHERS && array_key_exists('HTTP_REFERER', $_SERVER) && (! preg_match('/^https?:\/\/(?:www\.)?' . $this->myHost . '(?:$|\/)/i', $_SERVER['HTTP_REFERER']))){
219
+ // base64 encoded red image that says 'no hotlinkers'
220
+ // nothing to worry about! :)
221
+ $imgData = base64_decode("R0lGODlhUAAMAIAAAP8AAP///yH5BAAHAP8ALAAAAABQAAwAAAJpjI+py+0Po5y0OgAMjjv01YUZ\nOGplhWXfNa6JCLnWkXplrcBmW+spbwvaVr/cDyg7IoFC2KbYVC2NQ5MQ4ZNao9Ynzjl9ScNYpneb\nDULB3RP6JuPuaGfuuV4fumf8PuvqFyhYtjdoeFgAADs=");
222
+ header('Content-Type: image/gif');
223
+ header('Content-Length: ' . sizeof($imgData));
224
+ header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
225
+ header("Pragma: no-cache");
226
+ header('Expires: ' . gmdate ('D, d M Y H:i:s', time()));
227
+ echo $imgData;
228
+ return false;
229
+ exit(0);
230
+ }
231
+ if(preg_match('/^https?:\/\/[^\/]+/i', $this->src)){
232
+ $this->debug(2, "Is a request for an external URL: " . $this->src);
233
+ $this->isURL = true;
234
+ } else {
235
+ $this->debug(2, "Is a request for an internal file: " . $this->src);
236
+ }
237
+ if($this->isURL && (! ALLOW_EXTERNAL)){
238
+ $this->error("You are not allowed to fetch images from an external website.");
239
+ return false;
240
+ }
241
+ if($this->isURL){
242
+ if(ALLOW_ALL_EXTERNAL_SITES){
243
+ $this->debug(2, "Fetching from all external sites is enabled.");
244
+ } else {
245
+ $this->debug(2, "Fetching only from selected external sites is enabled.");
246
+ $allowed = false;
247
+ foreach($ALLOWED_SITES as $site){
248
+ if ((strtolower(substr($this->url['host'],-strlen($site)-1)) === strtolower(".$site")) || (strtolower($this->url['host'])===strtolower($site))) {
249
+ $this->debug(3, "URL hostname {$this->url['host']} matches $site so allowing.");
250
+ $allowed = true;
251
+ }
252
+ }
253
+ if(! $allowed){
254
+ return $this->error("You may not fetch images from that site. To enable this site in timthumb, you can either add it to \$ALLOWED_SITES and set ALLOW_EXTERNAL=true. Or you can set ALLOW_ALL_EXTERNAL_SITES=true, depending on your security needs.");
255
+ }
256
+ }
257
+ }
258
+
259
+ $cachePrefix = ($this->isURL ? '_ext_' : '_int_');
260
+ if($this->isURL){
261
+ $arr = explode('&', $_SERVER ['QUERY_STRING']);
262
+ asort($arr);
263
+ $this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . implode('', $arr) . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
264
+ } else {
265
+ $this->localImage = $this->getLocalImagePath($this->src);
266
+ if(! $this->localImage){
267
+ $this->debug(1, "Could not find the local image: {$this->localImage}");
268
+ $this->error("Could not find the internal image you specified.");
269
+ $this->set404();
270
+ return false;
271
+ }
272
+ $this->debug(1, "Local image path is {$this->localImage}");
273
+ $this->localImageMTime = @filemtime($this->localImage);
274
+ //We include the mtime of the local file in case in changes on disk.
275
+ $this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . $this->localImageMTime . $_SERVER ['QUERY_STRING'] . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
276
+ }
277
+ $this->debug(2, "Cache file is: " . $this->cachefile);
278
+
279
+ return true;
280
+ }
281
+ public function __destruct(){
282
+ foreach($this->toDeletes as $del){
283
+ $this->debug(2, "Deleting temp file $del");
284
+ @unlink($del);
285
+ }
286
+ }
287
+ public function run(){
288
+ if($this->isURL){
289
+ if(! ALLOW_EXTERNAL){
290
+ $this->debug(1, "Got a request for an external image but ALLOW_EXTERNAL is disabled so returning error msg.");
291
+ $this->error("You are not allowed to fetch images from an external website.");
292
+ return false;
293
+ }
294
+ $this->debug(3, "Got request for external image. Starting serveExternalImage.");
295
+ if($this->param('webshot')){
296
+ if(WEBSHOT_ENABLED){
297
+ $this->debug(3, "webshot param is set, so we're going to take a webshot.");
298
+ $this->serveWebshot();
299
+ } else {
300
+ $this->error("You added the webshot parameter but webshots are disabled on this server. You need to set WEBSHOT_ENABLED == true to enable webshots.");
301
+ }
302
+ } else {
303
+ $this->debug(3, "webshot is NOT set so we're going to try to fetch a regular image.");
304
+ $this->serveExternalImage();
305
+
306
+ }
307
+ } else {
308
+ $this->debug(3, "Got request for internal image. Starting serveInternalImage()");
309
+ $this->serveInternalImage();
310
+ }
311
+ return true;
312
+ }
313
+ protected function handleErrors(){
314
+ if($this->haveErrors()){
315
+ if(NOT_FOUND_IMAGE && $this->is404()){
316
+ if($this->serveImg(NOT_FOUND_IMAGE)){
317
+ exit(0);
318
+ } else {
319
+ $this->error("Additionally, the 404 image that is configured could not be found or there was an error serving it.");
320
+ }
321
+ }
322
+ if(ERROR_IMAGE){
323
+ if($this->serveImg(ERROR_IMAGE)){
324
+ exit(0);
325
+ } else {
326
+ $this->error("Additionally, the error image that is configured could not be found or there was an error serving it.");
327
+ }
328
+ }
329
+ $this->serveErrors();
330
+ exit(0);
331
+ }
332
+ return false;
333
+ }
334
+ protected function tryBrowserCache(){
335
+ if(BROWSER_CACHE_DISABLE){ $this->debug(3, "Browser caching is disabled"); return false; }
336
+ if(!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) ){
337
+ $this->debug(3, "Got a conditional get");
338
+ $mtime = false;
339
+ //We've already checked if the real file exists in the constructor
340
+ if(! is_file($this->cachefile)){
341
+ //If we don't have something cached, regenerate the cached image.
342
+ return false;
343
+ }
344
+ if($this->localImageMTime){
345
+ $mtime = $this->localImageMTime;
346
+ $this->debug(3, "Local real file's modification time is $mtime");
347
+ } else if(is_file($this->cachefile)){ //If it's not a local request then use the mtime of the cached file to determine the 304
348
+ $mtime = @filemtime($this->cachefile);
349
+ $this->debug(3, "Cached file's modification time is $mtime");
350
+ }
351
+ if(! $mtime){ return false; }
352
+
353
+ $iftime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
354
+ $this->debug(3, "The conditional get's if-modified-since unixtime is $iftime");
355
+ if($iftime < 1){
356
+ $this->debug(3, "Got an invalid conditional get modified since time. Returning false.");
357
+ return false;
358
+ }
359
+ if($iftime < $mtime){ //Real file or cache file has been modified since last request, so force refetch.
360
+ $this->debug(3, "File has been modified since last fetch.");
361
+ return false;
362
+ } else { //Otherwise serve a 304
363
+ $this->debug(3, "File has not been modified since last get, so serving a 304.");
364
+ header ($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
365
+ $this->debug(1, "Returning 304 not modified");
366
+ return true;
367
+ }
368
+ }
369
+ return false;
370
+ }
371
+ protected function tryServerCache(){
372
+ $this->debug(3, "Trying server cache");
373
+ if(file_exists($this->cachefile)){
374
+ $this->debug(3, "Cachefile {$this->cachefile} exists");
375
+ if($this->isURL){
376
+ $this->debug(3, "This is an external request, so checking if the cachefile is empty which means the request failed previously.");
377
+ if(filesize($this->cachefile) < 1){
378
+ $this->debug(3, "Found an empty cachefile indicating a failed earlier request. Checking how old it is.");
379
+ //Fetching error occured previously
380
+ if(time() - @filemtime($this->cachefile) > WAIT_BETWEEN_FETCH_ERRORS){
381
+ $this->debug(3, "File is older than " . WAIT_BETWEEN_FETCH_ERRORS . " seconds. Deleting and returning false so app can try and load file.");
382
+ @unlink($this->cachefile);
383
+ return false; //to indicate we didn't serve from cache and app should try and load
384
+ } else {
385
+ $this->debug(3, "Empty cachefile is still fresh so returning message saying we had an error fetching this image from remote host.");
386
+ $this->set404();
387
+ $this->error("An error occured fetching image.");
388
+ return false;
389
+ }
390
+ }
391
+ } else {
392
+ $this->debug(3, "Trying to serve cachefile {$this->cachefile}");
393
+ }
394
+ if($this->serveCacheFile()){
395
+ $this->debug(3, "Succesfully served cachefile {$this->cachefile}");
396
+ return true;
397
+ } else {
398
+ $this->debug(3, "Failed to serve cachefile {$this->cachefile} - Deleting it from cache.");
399
+ //Image serving failed. We can't retry at this point, but lets remove it from cache so the next request recreates it
400
+ @unlink($this->cachefile);
401
+ return true;
402
+ }
403
+ }
404
+ }
405
+ protected function error($err){
406
+ $this->debug(3, "Adding error message: $err");
407
+ $this->errors[] = $err;
408
+ return false;
409
+
410
+ }
411
+ protected function haveErrors(){
412
+ if(sizeof($this->errors) > 0){
413
+ return true;
414
+ }
415
+ return false;
416
+ }
417
+ protected function serveErrors(){
418
+ header ($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');
419
+ $html = '<ul>';
420
+ foreach($this->errors as $err){
421
+ $html .= '<li>' . htmlentities($err) . '</li>';
422
+ }
423
+ $html .= '</ul>';
424
+ echo '<h1>A TimThumb error has occured</h1>The following error(s) occured:<br />' . $html . '<br />';
425
+ echo '<br />Query String : ' . htmlentities ($_SERVER['QUERY_STRING']);
426
+ echo '<br />TimThumb version : ' . VERSION . '</pre>';
427
+ }
428
+ protected function serveInternalImage(){
429
+ $this->debug(3, "Local image path is $this->localImage");
430
+ if(! $this->localImage){
431
+ $this->sanityFail("localImage not set after verifying it earlier in the code.");
432
+ return false;
433
+ }
434
+ $fileSize = filesize($this->localImage);
435
+ if($fileSize > MAX_FILE_SIZE){
436
+ $this->error("The file you specified is greater than the maximum allowed file size.");
437
+ return false;
438
+ }
439
+ if($fileSize <= 0){
440
+ $this->error("The file you specified is <= 0 bytes.");
441
+ return false;
442
+ }
443
+ $this->debug(3, "Calling processImageAndWriteToCache() for local image.");
444
+ if($this->processImageAndWriteToCache($this->localImage)){
445
+ $this->serveCacheFile();
446
+ return true;
447
+ } else {
448
+ return false;
449
+ }
450
+ }
451
+ protected function cleanCache(){
452
+ if (FILE_CACHE_TIME_BETWEEN_CLEANS < 0) {
453
+ return;
454
+ }
455
+ $this->debug(3, "cleanCache() called");
456
+ $lastCleanFile = $this->cacheDirectory . '/timthumb_cacheLastCleanTime.touch';
457
+
458
+ //If this is a new timthumb installation we need to create the file
459
+ if(! is_file($lastCleanFile)){
460
+ $this->debug(1, "File tracking last clean doesn't exist. Creating $lastCleanFile");
461
+ if (!touch($lastCleanFile)) {
462
+ $this->error("Could not create cache clean timestamp file.");
463
+ }
464
+ return;
465
+ }
466
+ if(@filemtime($lastCleanFile) < (time() - FILE_CACHE_TIME_BETWEEN_CLEANS) ){ //Cache was last cleaned more than 1 day ago
467
+ $this->debug(1, "Cache was last cleaned more than " . FILE_CACHE_TIME_BETWEEN_CLEANS . " seconds ago. Cleaning now.");
468
+ // Very slight race condition here, but worst case we'll have 2 or 3 servers cleaning the cache simultaneously once a day.
469
+ if (!touch($lastCleanFile)) {
470
+ $this->error("Could not create cache clean timestamp file.");
471
+ }
472
+ $files = glob($this->cacheDirectory . '/*' . FILE_CACHE_SUFFIX);
473
+ if ($files) {
474
+ $timeAgo = time() - FILE_CACHE_MAX_FILE_AGE;
475
+ foreach($files as $file){
476
+ if(@filemtime($file) < $timeAgo){
477
+ $this->debug(3, "Deleting cache file $file older than max age: " . FILE_CACHE_MAX_FILE_AGE . " seconds");
478
+ @unlink($file);
479
+ }
480
+ }
481
+ }
482
+ return true;
483
+ } else {
484
+ $this->debug(3, "Cache was cleaned less than " . FILE_CACHE_TIME_BETWEEN_CLEANS . " seconds ago so no cleaning needed.");
485
+ }
486
+ return false;
487
+ }
488
+ protected function processImageAndWriteToCache($localImage){
489
+ $sData = getimagesize($localImage);
490
+ $origType = $sData[2];
491
+ $mimeType = $sData['mime'];
492
+
493
+ $this->debug(3, "Mime type of image is $mimeType");
494
+ if(! preg_match('/^image\/(?:gif|jpg|jpeg|png)$/i', $mimeType)){
495
+ return $this->error("The image being resized is not a valid gif, jpg or png.");
496
+ }
497
+
498
+ if (!function_exists ('imagecreatetruecolor')) {
499
+ return $this->error('GD Library Error: imagecreatetruecolor does not exist - please contact your webhost and ask them to install the GD library');
500
+ }
501
+
502
+ if (function_exists ('imagefilter') && defined ('IMG_FILTER_NEGATE')) {
503
+ $imageFilters = array (
504
+ 1 => array (IMG_FILTER_NEGATE, 0),
505
+ 2 => array (IMG_FILTER_GRAYSCALE, 0),
506
+ 3 => array (IMG_FILTER_BRIGHTNESS, 1),
507
+ 4 => array (IMG_FILTER_CONTRAST, 1),
508
+ 5 => array (IMG_FILTER_COLORIZE, 4),
509
+ 6 => array (IMG_FILTER_EDGEDETECT, 0),
510
+ 7 => array (IMG_FILTER_EMBOSS, 0),
511
+ 8 => array (IMG_FILTER_GAUSSIAN_BLUR, 0),
512
+ 9 => array (IMG_FILTER_SELECTIVE_BLUR, 0),
513
+ 10 => array (IMG_FILTER_MEAN_REMOVAL, 0),
514
+ 11 => array (IMG_FILTER_SMOOTH, 0),
515
+ );
516
+ }
517
+
518
+ // get standard input properties
519
+ $new_width = (int) abs ($this->param('w', 0));
520
+ $new_height = (int) abs ($this->param('h', 0));
521
+ $zoom_crop = (int) $this->param('zc', DEFAULT_ZC);
522
+ $quality = (int) abs ($this->param('q', DEFAULT_Q));
523
+ $align = $this->cropTop ? 't' : $this->param('a', 'c');
524
+ $filters = $this->param('f', DEFAULT_F);
525
+ $sharpen = (bool) $this->param('s', DEFAULT_S);
526
+ $canvas_color = $this->param('cc', DEFAULT_CC);
527
+ $canvas_trans = (bool) $this->param('ct', '1');
528
+
529
+ // set default width and height if neither are set already
530
+ if ($new_width == 0 && $new_height == 0) {
531
+ $new_width = 100;
532
+ $new_height = 100;
533
+ }
534
+
535
+ // ensure size limits can not be abused
536
+ $new_width = min ($new_width, MAX_WIDTH);
537
+ $new_height = min ($new_height, MAX_HEIGHT);
538
+
539
+ // set memory limit to be able to have enough space to resize larger images
540
+ $this->setMemoryLimit();
541
+
542
+ // open the existing image
543
+ $image = $this->openImage ($mimeType, $localImage);
544
+ if ($image === false) {
545
+ return $this->error('Unable to open image.');
546
+ }
547
+
548
+ // Get original width and height
549
+ $width = imagesx ($image);
550
+ $height = imagesy ($image);
551
+ $origin_x = 0;
552
+ $origin_y = 0;
553
+
554
+ // generate new w/h if not provided
555
+ if ($new_width && !$new_height) {
556
+ $new_height = floor ($height * ($new_width / $width));
557
+ } else if ($new_height && !$new_width) {
558
+ $new_width = floor ($width * ($new_height / $height));
559
+ }
560
+
561
+ // scale down and add borders
562
+ if ($zoom_crop == 3) {
563
+
564
+ $final_height = $height * ($new_width / $width);
565
+
566
+ if ($final_height > $new_height) {
567
+ $new_width = $width * ($new_height / $height);
568
+ } else {
569
+ $new_height = $final_height;
570
+ }
571
+
572
+ }
573
+
574
+ // create a new true color image
575
+ $canvas = imagecreatetruecolor ($new_width, $new_height);
576
+ imagealphablending ($canvas, false);
577
+
578
+ if (strlen($canvas_color) == 3) { //if is 3-char notation, edit string into 6-char notation
579
+ $canvas_color = str_repeat(substr($canvas_color, 0, 1), 2) . str_repeat(substr($canvas_color, 1, 1), 2) . str_repeat(substr($canvas_color, 2, 1), 2);
580
+ } else if (strlen($canvas_color) != 6) {
581
+ $canvas_color = DEFAULT_CC; // on error return default canvas color
582
+ }
583
+
584
+ $canvas_color_R = hexdec (substr ($canvas_color, 0, 2));
585
+ $canvas_color_G = hexdec (substr ($canvas_color, 2, 2));
586
+ $canvas_color_B = hexdec (substr ($canvas_color, 4, 2));
587
+
588
+ // Create a new transparent color for image
589
+ // If is a png and PNG_IS_TRANSPARENT is false then remove the alpha transparency
590
+ // (and if is set a canvas color show it in the background)
591
+ if(preg_match('/^image\/png$/i', $mimeType) && !PNG_IS_TRANSPARENT && $canvas_trans){
592
+ $color = imagecolorallocatealpha ($canvas, $canvas_color_R, $canvas_color_G, $canvas_color_B, 127);
593
+ }else{
594
+ $color = imagecolorallocatealpha ($canvas, $canvas_color_R, $canvas_color_G, $canvas_color_B, 0);
595
+ }
596
+
597
+
598
+ // Completely fill the background of the new image with allocated color.
599
+ imagefill ($canvas, 0, 0, $color);
600
+
601
+ // scale down and add borders
602
+ if ($zoom_crop == 2) {
603
+
604
+ $final_height = $height * ($new_width / $width);
605
+
606
+ if ($final_height > $new_height) {
607
+
608
+ $origin_x = $new_width / 2;
609
+ $new_width = $width * ($new_height / $height);
610
+ $origin_x = round ($origin_x - ($new_width / 2));
611
+
612
+ } else {
613
+
614
+ $origin_y = $new_height / 2;
615
+ $new_height = $final_height;
616
+ $origin_y = round ($origin_y - ($new_height / 2));
617
+
618
+ }
619
+
620
+ }
621
+
622
+ // Restore transparency blending
623
+ imagesavealpha ($canvas, true);
624
+
625
+ if ($zoom_crop > 0) {
626
+
627
+ $src_x = $src_y = 0;
628
+ $src_w = $width;
629
+ $src_h = $height;
630
+
631
+ $cmp_x = $width / $new_width;
632
+ $cmp_y = $height / $new_height;
633
+
634
+ // calculate x or y coordinate and width or height of source
635
+ if ($cmp_x > $cmp_y) {
636
+
637
+ $src_w = round ($width / $cmp_x * $cmp_y);
638
+ $src_x = round (($width - ($width / $cmp_x * $cmp_y)) / 2);
639
+
640
+ } else if ($cmp_y > $cmp_x) {
641
+
642
+ $src_h = round ($height / $cmp_y * $cmp_x);
643
+ $src_y = round (($height - ($height / $cmp_y * $cmp_x)) / 2);
644
+
645
+ }
646
+
647
+ // positional cropping!
648
+ if ($align) {
649
+ if (strpos ($align, 't') !== false) {
650
+ $src_y = 0;
651
+ }
652
+ if (strpos ($align, 'b') !== false) {
653
+ $src_y = $height - $src_h;
654
+ }
655
+ if (strpos ($align, 'l') !== false) {
656
+ $src_x = 0;
657
+ }
658
+ if (strpos ($align, 'r') !== false) {
659
+ $src_x = $width - $src_w;
660
+ }
661
+ }
662
+
663
+ imagecopyresampled ($canvas, $image, $origin_x, $origin_y, $src_x, $src_y, $new_width, $new_height, $src_w, $src_h);
664
+
665
+ } else {
666
+
667
+ // copy and resize part of an image with resampling
668
+ imagecopyresampled ($canvas, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
669
+
670
+ }
671
+
672
+ if ($filters != '' && function_exists ('imagefilter') && defined ('IMG_FILTER_NEGATE')) {
673
+ // apply filters to image
674
+ $filterList = explode ('|', $filters);
675
+ foreach ($filterList as $fl) {
676
+
677
+ $filterSettings = explode (',', $fl);
678
+ if (isset ($imageFilters[$filterSettings[0]])) {
679
+
680
+ for ($i = 0; $i < 4; $i ++) {
681
+ if (!isset ($filterSettings[$i])) {
682
+ $filterSettings[$i] = null;
683
+ } else {
684
+ $filterSettings[$i] = (int) $filterSettings[$i];
685
+ }
686
+ }
687
+
688
+ switch ($imageFilters[$filterSettings[0]][1]) {
689
+
690
+ case 1:
691
+
692
+ imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1]);
693
+ break;
694
+
695
+ case 2:
696
+
697
+ imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2]);
698
+ break;
699
+
700
+ case 3:
701
+
702
+ imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3]);
703
+ break;
704
+
705
+ case 4:
706
+
707
+ imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3], $filterSettings[4]);
708
+ break;
709
+
710
+ default:
711
+
712
+ imagefilter ($canvas, $imageFilters[$filterSettings[0]][0]);
713
+ break;
714
+
715
+ }
716
+ }
717
+ }
718
+ }
719
+
720
+ // sharpen image
721
+ if ($sharpen && function_exists ('imageconvolution')) {
722
+
723
+ $sharpenMatrix = array (
724
+ array (-1,-1,-1),
725
+ array (-1,16,-1),
726
+ array (-1,-1,-1),
727
+ );
728
+
729
+ $divisor = 8;
730
+ $offset = 0;
731
+
732
+ imageconvolution ($canvas, $sharpenMatrix, $divisor, $offset);
733
+
734
+ }
735
+ //Straight from Wordpress core code. Reduces filesize by up to 70% for PNG's
736
+ if ( (IMAGETYPE_PNG == $origType || IMAGETYPE_GIF == $origType) && function_exists('imageistruecolor') && !imageistruecolor( $image ) && imagecolortransparent( $image ) > 0 ){
737
+ imagetruecolortopalette( $canvas, false, imagecolorstotal( $image ) );
738
+ }
739
+
740
+ $imgType = "";
741
+ $tempfile = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
742
+ if(preg_match('/^image\/(?:jpg|jpeg)$/i', $mimeType)){
743
+ $imgType = 'jpg';
744
+ imagejpeg($canvas, $tempfile, $quality);
745
+ } else if(preg_match('/^image\/png$/i', $mimeType)){
746
+ $imgType = 'png';
747
+ imagepng($canvas, $tempfile, floor($quality * 0.09));
748
+ } else if(preg_match('/^image\/gif$/i', $mimeType)){
749
+ $imgType = 'gif';
750
+ imagegif($canvas, $tempfile);
751
+ } else {
752
+ return $this->sanityFail("Could not match mime type after verifying it previously.");
753
+ }
754
+
755
+ if($imgType == 'png' && OPTIPNG_ENABLED && OPTIPNG_PATH && @is_file(OPTIPNG_PATH)){
756
+ $exec = OPTIPNG_PATH;
757
+ $this->debug(3, "optipng'ing $tempfile");
758
+ $presize = filesize($tempfile);
759
+ $out = `$exec -o1 $tempfile`; //you can use up to -o7 but it really slows things down
760
+ clearstatcache();
761
+ $aftersize = filesize($tempfile);
762
+ $sizeDrop = $presize - $aftersize;
763
+ if($sizeDrop > 0){
764
+ $this->debug(1, "optipng reduced size by $sizeDrop");
765
+ } else if($sizeDrop < 0){
766
+ $this->debug(1, "optipng increased size! Difference was: $sizeDrop");
767
+ } else {
768
+ $this->debug(1, "optipng did not change image size.");
769
+ }
770
+ } else if($imgType == 'png' && PNGCRUSH_ENABLED && PNGCRUSH_PATH && @is_file(PNGCRUSH_PATH)){
771
+ $exec = PNGCRUSH_PATH;
772
+ $tempfile2 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
773
+ $this->debug(3, "pngcrush'ing $tempfile to $tempfile2");
774
+ $out = `$exec $tempfile $tempfile2`;
775
+ $todel = "";
776
+ if(is_file($tempfile2)){
777
+ $sizeDrop = filesize($tempfile) - filesize($tempfile2);
778
+ if($sizeDrop > 0){
779
+ $this->debug(1, "pngcrush was succesful and gave a $sizeDrop byte size reduction");
780
+ $todel = $tempfile;
781
+ $tempfile = $tempfile2;
782
+ } else {
783
+ $this->debug(1, "pngcrush did not reduce file size. Difference was $sizeDrop bytes.");
784
+ $todel = $tempfile2;
785
+ }
786
+ } else {
787
+ $this->debug(3, "pngcrush failed with output: $out");
788
+ $todel = $tempfile2;
789
+ }
790
+ @unlink($todel);
791
+ }
792
+
793
+ $this->debug(3, "Rewriting image with security header.");
794
+ $tempfile4 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
795
+ $context = stream_context_create ();
796
+ $fp = fopen($tempfile,'r',0,$context);
797
+ file_put_contents($tempfile4, $this->filePrependSecurityBlock . $imgType . ' ?' . '>'); //6 extra bytes, first 3 being image type
798
+ file_put_contents($tempfile4, $fp, FILE_APPEND);
799
+ fclose($fp);
800
+ @unlink($tempfile);
801
+ $this->debug(3, "Locking and replacing cache file.");
802
+ $lockFile = $this->cachefile . '.lock';
803
+ $fh = fopen($lockFile, 'w');
804
+ if(! $fh){
805
+ return $this->error("Could not open the lockfile for writing an image.");
806
+ }
807
+ if(flock($fh, LOCK_EX)){
808
+ @unlink($this->cachefile); //rename generally overwrites, but doing this in case of platform specific quirks. File might not exist yet.
809
+ rename($tempfile4, $this->cachefile);
810
+ flock($fh, LOCK_UN);
811
+ fclose($fh);
812
+ @unlink($lockFile);
813
+ } else {
814
+ fclose($fh);
815
+ @unlink($lockFile);
816
+ @unlink($tempfile4);
817
+ return $this->error("Could not get a lock for writing.");
818
+ }
819
+ $this->debug(3, "Done image replace with security header. Cleaning up and running cleanCache()");
820
+ imagedestroy($canvas);
821
+ imagedestroy($image);
822
+ return true;
823
+ }
824
+ protected function calcDocRoot(){
825
+ $docRoot = @$_SERVER['DOCUMENT_ROOT'];
826
+ if (defined('LOCAL_FILE_BASE_DIRECTORY')) {
827
+ $docRoot = LOCAL_FILE_BASE_DIRECTORY;
828
+ }
829
+ if(!isset($docRoot)){
830
+ $this->debug(3, "DOCUMENT_ROOT is not set. This is probably windows. Starting search 1.");
831
+ if(isset($_SERVER['SCRIPT_FILENAME'])){
832
+ $docRoot = str_replace( '\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0-strlen($_SERVER['PHP_SELF'])));
833
+ $this->debug(3, "Generated docRoot using SCRIPT_FILENAME and PHP_SELF as: $docRoot");
834
+ }
835
+ }
836
+ if(!isset($docRoot)){
837
+ $this->debug(3, "DOCUMENT_ROOT still is not set. Starting search 2.");
838
+ if(isset($_SERVER['PATH_TRANSLATED'])){
839
+ $docRoot = str_replace( '\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0-strlen($_SERVER['PHP_SELF'])));
840
+ $this->debug(3, "Generated docRoot using PATH_TRANSLATED and PHP_SELF as: $docRoot");
841
+ }
842
+ }
843
+ if($docRoot && $_SERVER['DOCUMENT_ROOT'] != '/'){ $docRoot = preg_replace('/\/$/', '', $docRoot); }
844
+ $this->debug(3, "Doc root is: " . $docRoot);
845
+ $this->docRoot = $docRoot;
846
+
847
+ }
848
+ protected function getLocalImagePath($src){
849
+ $src = ltrim($src, '/'); //strip off the leading '/'
850
+ if(! $this->docRoot){
851
+ $this->debug(3, "We have no document root set, so as a last resort, lets check if the image is in the current dir and serve that.");
852
+ //We don't support serving images outside the current dir if we don't have a doc root for security reasons.
853
+ $file = preg_replace('/^.*?([^\/\\\\]+)$/', '$1', $src); //strip off any path info and just leave the filename.
854
+ if(is_file($file)){
855
+ return $this->realpath($file);
856
+ }
857
+ return $this->error("Could not find your website document root and the file specified doesn't exist in timthumbs directory. We don't support serving files outside timthumb's directory without a document root for security reasons.");
858
+ } //Do not go past this point without docRoot set
859
+
860
+ //Try src under docRoot
861
+ if(file_exists ($this->docRoot . '/' . $src)) {
862
+ $this->debug(3, "Found file as " . $this->docRoot . '/' . $src);
863
+ $real = $this->realpath($this->docRoot . '/' . $src);
864
+ if(stripos($real, $this->docRoot) === 0){
865
+ return $real;
866
+ } else {
867
+ $this->debug(1, "Security block: The file specified occurs outside the document root.");
868
+ //allow search to continue
869
+ }
870
+ }
871
+ //Check absolute paths and then verify the real path is under doc root
872
+ $absolute = $this->realpath('/' . $src);
873
+ if($absolute && file_exists($absolute)){ //realpath does file_exists check, so can probably skip the exists check here
874
+ $this->debug(3, "Found absolute path: $absolute");
875
+ if(! $this->docRoot){ $this->sanityFail("docRoot not set when checking absolute path."); }
876
+ if(stripos($absolute, $this->docRoot) === 0){
877
+ return $absolute;
878
+ } else {
879
+ $this->debug(1, "Security block: The file specified occurs outside the document root.");
880
+ //and continue search
881
+ }
882
+ }
883
+
884
+ $base = $this->docRoot;
885
+
886
+ // account for Windows directory structure
887
+ if (strstr($_SERVER['SCRIPT_FILENAME'],':')) {
888
+ $sub_directories = explode('\\', str_replace($this->docRoot, '', $_SERVER['SCRIPT_FILENAME']));
889
+ } else {
890
+ $sub_directories = explode('/', str_replace($this->docRoot, '', $_SERVER['SCRIPT_FILENAME']));
891
+ }
892
+
893
+ foreach ($sub_directories as $sub){
894
+ $base .= $sub . '/';
895
+ $this->debug(3, "Trying file as: " . $base . $src);
896
+ if(file_exists($base . $src)){
897
+ $this->debug(3, "Found file as: " . $base . $src);
898
+ $real = $this->realpath($base . $src);
899
+ if(stripos($real, $this->realpath($this->docRoot)) === 0){
900
+ return $real;
901
+ } else {
902
+ $this->debug(1, "Security block: The file specified occurs outside the document root.");
903
+ //And continue search
904
+ }
905
+ }
906
+ }
907
+ return false;
908
+ }
909
+ protected function realpath($path){
910
+ //try to remove any relative paths
911
+ $remove_relatives = '/\w+\/\.\.\//';
912
+ while(preg_match($remove_relatives,$path)){
913
+ $path = preg_replace($remove_relatives, '', $path);
914
+ }
915
+ //if any remain use PHP realpath to strip them out, otherwise return $path
916
+ //if using realpath, any symlinks will also be resolved
917
+ return preg_match('#^\.\./|/\.\./#', $path) ? realpath($path) : $path;
918
+ }
919
+ protected function toDelete($name){
920
+ $this->debug(3, "Scheduling file $name to delete on destruct.");
921
+ $this->toDeletes[] = $name;
922
+ }
923
+ protected function serveWebshot(){
924
+ $this->debug(3, "Starting serveWebshot");
925
+ $instr = "Please follow the instructions at http://code.google.com/p/timthumb/ to set your server up for taking website screenshots.";
926
+ if(! is_file(WEBSHOT_CUTYCAPT)){
927
+ return $this->error("CutyCapt is not installed. $instr");
928
+ }
929
+ if(! is_file(WEBSHOT_XVFB)){
930
+ return $this->Error("Xvfb is not installed. $instr");
931
+ }
932
+ $cuty = WEBSHOT_CUTYCAPT;
933
+ $xv = WEBSHOT_XVFB;
934
+ $screenX = WEBSHOT_SCREEN_X;
935
+ $screenY = WEBSHOT_SCREEN_Y;
936
+ $colDepth = WEBSHOT_COLOR_DEPTH;
937
+ $format = WEBSHOT_IMAGE_FORMAT;
938
+ $timeout = WEBSHOT_TIMEOUT * 1000;
939
+ $ua = WEBSHOT_USER_AGENT;
940
+ $jsOn = WEBSHOT_JAVASCRIPT_ON ? 'on' : 'off';
941
+ $javaOn = WEBSHOT_JAVA_ON ? 'on' : 'off';
942
+ $pluginsOn = WEBSHOT_PLUGINS_ON ? 'on' : 'off';
943
+ $proxy = WEBSHOT_PROXY ? ' --http-proxy=' . WEBSHOT_PROXY : '';
944
+ $tempfile = tempnam($this->cacheDirectory, 'timthumb_webshot');
945
+ $url = $this->src;
946
+ if(! preg_match('/^https?:\/\/[a-zA-Z0-9\.\-]+/i', $url)){
947
+ return $this->error("Invalid URL supplied.");
948
+ }
949
+ $url = preg_replace('/[^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=]+/', '', $url); //RFC 3986
950
+ //Very important we don't allow injection of shell commands here. URL is between quotes and we are only allowing through chars allowed by a the RFC
951
+ // which AFAIKT can't be used for shell injection.
952
+ if(WEBSHOT_XVFB_RUNNING){
953
+ putenv('DISPLAY=:100.0');
954
+ $command = "$cuty $proxy --max-wait=$timeout --user-agent=\"$ua\" --javascript=$jsOn --java=$javaOn --plugins=$pluginsOn --js-can-open-windows=off --url=\"$url\" --out-format=$format --out=$tempfile";
955
+ } else {
956
+ $command = "$xv --server-args=\"-screen 0, {$screenX}x{$screenY}x{$colDepth}\" $cuty $proxy --max-wait=$timeout --user-agent=\"$ua\" --javascript=$jsOn --java=$javaOn --plugins=$pluginsOn --js-can-open-windows=off --url=\"$url\" --out-format=$format --out=$tempfile";
957
+ }
958
+ $this->debug(3, "Executing command: $command");
959
+ $out = `$command`;
960
+ $this->debug(3, "Received output: $out");
961
+ if(! is_file($tempfile)){
962
+ $this->set404();
963
+ return $this->error("The command to create a thumbnail failed.");
964
+ }
965
+ $this->cropTop = true;
966
+ if($this->processImageAndWriteToCache($tempfile)){
967
+ $this->debug(3, "Image processed succesfully. Serving from cache");
968
+ return $this->serveCacheFile();
969
+ } else {
970
+ return false;
971
+ }
972
+ }
973
+ protected function serveExternalImage(){
974
+ if(! preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+/i', $this->src)){
975
+ $this->error("Invalid URL supplied.");
976
+ return false;
977
+ }
978
+ $tempfile = tempnam($this->cacheDirectory, 'timthumb');
979
+ $this->debug(3, "Fetching external image into temporary file $tempfile");
980
+ $this->toDelete($tempfile);
981
+ #fetch file here
982
+ if(! $this->getURL($this->src, $tempfile)){
983
+ @unlink($this->cachefile);
984
+ touch($this->cachefile);
985
+ $this->debug(3, "Error fetching URL: " . $this->lastURLError);
986
+ $this->error("Error reading the URL you specified from remote host." . $this->lastURLError);
987
+ return false;
988
+ }
989
+
990
+ $mimeType = $this->getMimeType($tempfile);
991
+ if(! preg_match("/^image\/(?:jpg|jpeg|gif|png)$/i", $mimeType)){
992
+ $this->debug(3, "Remote file has invalid mime type: $mimeType");
993
+ @unlink($this->cachefile);
994
+ touch($this->cachefile);
995
+ $this->error("The remote file is not a valid image. Mimetype = '" . $mimeType . "'" . $tempfile);
996
+ return false;
997
+ }
998
+ if($this->processImageAndWriteToCache($tempfile)){
999
+ $this->debug(3, "Image processed succesfully. Serving from cache");
1000
+ return $this->serveCacheFile();
1001
+ } else {
1002
+ return false;
1003
+ }
1004
+ }
1005
+ public static function curlWrite($h, $d){
1006
+ fwrite(self::$curlFH, $d);
1007
+ self::$curlDataWritten += strlen($d);
1008
+ if(self::$curlDataWritten > MAX_FILE_SIZE){
1009
+ return 0;
1010
+ } else {
1011
+ return strlen($d);
1012
+ }
1013
+ }
1014
+ protected function serveCacheFile(){
1015
+ $this->debug(3, "Serving {$this->cachefile}");
1016
+ if(! is_file($this->cachefile)){
1017
+ $this->error("serveCacheFile called in timthumb but we couldn't find the cached file.");
1018
+ return false;
1019
+ }
1020
+ $fp = fopen($this->cachefile, 'rb');
1021
+ if(! $fp){ return $this->error("Could not open cachefile."); }
1022
+ fseek($fp, strlen($this->filePrependSecurityBlock), SEEK_SET);
1023
+ $imgType = fread($fp, 3);
1024
+ fseek($fp, 3, SEEK_CUR);
1025
+ if(ftell($fp) != strlen($this->filePrependSecurityBlock) + 6){
1026
+ @unlink($this->cachefile);
1027
+ return $this->error("The cached image file seems to be corrupt.");
1028
+ }
1029
+ $imageDataSize = filesize($this->cachefile) - (strlen($this->filePrependSecurityBlock) + 6);
1030
+ $this->sendImageHeaders($imgType, $imageDataSize);
1031
+ $bytesSent = @fpassthru($fp);
1032
+ fclose($fp);
1033
+ if($bytesSent > 0){
1034
+ return true;
1035
+ }
1036
+ $content = file_get_contents ($this->cachefile);
1037
+ if ($content != FALSE) {
1038
+ $content = substr($content, strlen($this->filePrependSecurityBlock) + 6);
1039
+ echo $content;
1040
+ $this->debug(3, "Served using file_get_contents and echo");
1041
+ return true;
1042
+ } else {
1043
+ $this->error("Cache file could not be loaded.");
1044
+ return false;
1045
+ }
1046
+ }
1047
+ protected function sendImageHeaders($mimeType, $dataSize){
1048
+ if(! preg_match('/^image\//i', $mimeType)){
1049
+ $mimeType = 'image/' . $mimeType;
1050
+ }
1051
+ if(strtolower($mimeType) == 'image/jpg'){
1052
+ $mimeType = 'image/jpeg';
1053
+ }
1054
+ $gmdate_expires = gmdate ('D, d M Y H:i:s', strtotime ('now +10 days')) . ' GMT';
1055
+ $gmdate_modified = gmdate ('D, d M Y H:i:s') . ' GMT';
1056
+ // send content headers then display image
1057
+ header ('Content-Type: ' . $mimeType);
1058
+ header ('Accept-Ranges: none'); //Changed this because we don't accept range requests
1059
+ header ('Last-Modified: ' . $gmdate_modified);
1060
+ header ('Content-Length: ' . $dataSize);
1061
+ if(BROWSER_CACHE_DISABLE){
1062
+ $this->debug(3, "Browser cache is disabled so setting non-caching headers.");
1063
+ header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
1064
+ header("Pragma: no-cache");
1065
+ header('Expires: ' . gmdate ('D, d M Y H:i:s', time()));
1066
+ } else {
1067
+ $this->debug(3, "Browser caching is enabled");
1068
+ header('Cache-Control: max-age=' . BROWSER_CACHE_MAX_AGE . ', must-revalidate');
1069
+ header('Expires: ' . $gmdate_expires);
1070
+ }
1071
+ return true;
1072
+ }
1073
+ protected function securityChecks(){
1074
+ }
1075
+ protected function param($property, $default = ''){
1076
+ if (isset ($_GET[$property])) {
1077
+ return $_GET[$property];
1078
+ } else {
1079
+ return $default;
1080
+ }
1081
+ }
1082
+ protected function openImage($mimeType, $src){
1083
+ switch ($mimeType) {
1084
+ case 'image/jpeg':
1085
+ $image = imagecreatefromjpeg ($src);
1086
+ break;
1087
+
1088
+ case 'image/png':
1089
+ $image = imagecreatefrompng ($src);
1090
+ imagealphablending( $image, true );
1091
+ imagesavealpha( $image, true );
1092
+ break;
1093
+
1094
+ case 'image/gif':
1095
+ $image = imagecreatefromgif ($src);
1096
+ break;
1097
+
1098
+ default:
1099
+ $this->error("Unrecognised mimeType");
1100
+ }
1101
+
1102
+ return $image;
1103
+ }
1104
+ protected function getIP(){
1105
+ $rem = @$_SERVER["REMOTE_ADDR"];
1106
+ $ff = @$_SERVER["HTTP_X_FORWARDED_FOR"];
1107
+ $ci = @$_SERVER["HTTP_CLIENT_IP"];
1108
+ if(preg_match('/^(?:192\.168|172\.16|10\.|127\.)/', $rem)){
1109
+ if($ff){ return $ff; }
1110
+ if($ci){ return $ci; }
1111
+ return $rem;
1112
+ } else {
1113
+ if($rem){ return $rem; }
1114
+ if($ff){ return $ff; }
1115
+ if($ci){ return $ci; }
1116
+ return "UNKNOWN";
1117
+ }
1118
+ }
1119
+ protected function debug($level, $msg){
1120
+ if(DEBUG_ON && $level <= DEBUG_LEVEL){
1121
+ $execTime = sprintf('%.6f', microtime(true) - $this->startTime);
1122
+ $tick = sprintf('%.6f', 0);
1123
+ if($this->lastBenchTime > 0){
1124
+ $tick = sprintf('%.6f', microtime(true) - $this->lastBenchTime);
1125
+ }
1126
+ $this->lastBenchTime = microtime(true);
1127
+ error_log("TimThumb Debug line " . __LINE__ . " [$execTime : $tick]: $msg");
1128
+ }
1129
+ }
1130
+ protected function sanityFail($msg){
1131
+ return $this->error("There is a problem in the timthumb code. Message: Please report this error at <a href='http://code.google.com/p/timthumb/issues/list'>timthumb's bug tracking page</a>: $msg");
1132
+ }
1133
+ protected function getMimeType($file){
1134
+ $info = getimagesize($file);
1135
+ if(is_array($info) && $info['mime']){
1136
+ return $info['mime'];
1137
+ }
1138
+ return '';
1139
+ }
1140
+ protected function setMemoryLimit(){
1141
+ $inimem = ini_get('memory_limit');
1142
+ $inibytes = timthumb::returnBytes($inimem);
1143
+ $ourbytes = timthumb::returnBytes(MEMORY_LIMIT);
1144
+ if($inibytes < $ourbytes){
1145
+ ini_set ('memory_limit', MEMORY_LIMIT);
1146
+ $this->debug(3, "Increased memory from $inimem to " . MEMORY_LIMIT);
1147
+ } else {
1148
+ $this->debug(3, "Not adjusting memory size because the current setting is " . $inimem . " and our size of " . MEMORY_LIMIT . " is smaller.");
1149
+ }
1150
+ }
1151
+ protected static function returnBytes($size_str){
1152
+ switch (substr ($size_str, -1))
1153
+ {
1154
+ case 'M': case 'm': return (int)$size_str * 1048576;
1155
+ case 'K': case 'k': return (int)$size_str * 1024;
1156
+ case 'G': case 'g': return (int)$size_str * 1073741824;
1157
+ default: return $size_str;
1158
+ }
1159
+ }
1160
+
1161
+ protected function getURL($url, $tempfile){
1162
+ $this->lastURLError = false;
1163
+ $url = preg_replace('/ /', '%20', $url);
1164
+ if(function_exists('curl_init')){
1165
+ $this->debug(3, "Curl is installed so using it to fetch URL.");
1166
+ self::$curlFH = fopen($tempfile, 'w');
1167
+ if(! self::$curlFH){
1168
+ $this->error("Could not open $tempfile for writing.");
1169
+ return false;
1170
+ }
1171
+ self::$curlDataWritten = 0;
1172
+ $this->debug(3, "Fetching url with curl: $url");
1173
+ $curl = curl_init($url);
1174
+ curl_setopt ($curl, CURLOPT_TIMEOUT, CURL_TIMEOUT);
1175
+ curl_setopt ($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30");
1176
+ curl_setopt ($curl, CURLOPT_RETURNTRANSFER, TRUE);
1177
+ curl_setopt ($curl, CURLOPT_HEADER, 0);
1178
+ curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
1179
+ curl_setopt ($curl, CURLOPT_WRITEFUNCTION, 'timthumb::curlWrite');
1180
+ @curl_setopt ($curl, CURLOPT_FOLLOWLOCATION, true);
1181
+ @curl_setopt ($curl, CURLOPT_MAXREDIRS, 10);
1182
+
1183
+ $curlResult = curl_exec($curl);
1184
+ fclose(self::$curlFH);
1185
+ $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
1186
+ if($httpStatus == 404){
1187
+ $this->set404();
1188
+ }
1189
+ if($httpStatus == 302){
1190
+ $this->error("External Image is Redirecting. Try alternate image url");
1191
+ return false;
1192
+ }
1193
+ if($curlResult){
1194
+ curl_close($curl);
1195
+ return true;
1196
+ } else {
1197
+ $this->lastURLError = curl_error($curl);
1198
+ curl_close($curl);
1199
+ return false;
1200
+ }
1201
+ } else {
1202
+ $img = @file_get_contents ($url);
1203
+ if($img === false){
1204
+ $err = error_get_last();
1205
+ if(is_array($err) && $err['message']){
1206
+ $this->lastURLError = $err['message'];
1207
+ } else {
1208
+ $this->lastURLError = $err;
1209
+ }
1210
+ if(preg_match('/404/', $this->lastURLError)){
1211
+ $this->set404();
1212
+ }
1213
+
1214
+ return false;
1215
+ }
1216
+ if(! file_put_contents($tempfile, $img)){
1217
+ $this->error("Could not write to $tempfile.");
1218
+ return false;
1219
+ }
1220
+ return true;
1221
+ }
1222
+
1223
+ }
1224
+ protected function serveImg($file){
1225
+ $s = getimagesize($file);
1226
+ if(! ($s && $s['mime'])){
1227
+ return false;
1228
+ }
1229
+ header ('Content-Type: ' . $s['mime']);
1230
+ header ('Content-Length: ' . filesize($file) );
1231
+ header ('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
1232
+ header ("Pragma: no-cache");
1233
+ $bytes = @readfile($file);
1234
+ if($bytes > 0){
1235
+ return true;
1236
+ }
1237
+ $content = @file_get_contents ($file);
1238
+ if ($content != FALSE){
1239
+ echo $content;
1240
+ return true;
1241
+ }
1242
+ return false;
1243
+
1244
+ }
1245
+ protected function set404(){
1246
+ $this->is404 = true;
1247
+ }
1248
+ protected function is404(){
1249
+ return $this->is404;
1250
+ }
1251
+ }
js/bj-lazy-load.js CHANGED
@@ -109,10 +109,10 @@ var BJLL = BJLL || {};
109
  return dec;
110
  }
111
 
112
- $( document ).bind( 'ready', bj_lazy_load_init ); // using .on is more efficient, but requires jQuery 1.7
113
- if ( BJLL.infinite_scroll == 'yes' ) {
114
- $( window ).bind( 'scroll', bj_lazy_load_init ); // using .on is more efficient, but requires jQuery 1.7
115
  }
116
- $(window).on("resize", function() { $(document).trigger("scroll"); });
117
 
118
  })(jQuery);
109
  return dec;
110
  }
111
 
112
+ $( document ).on( 'ready', bj_lazy_load_init );
113
+ if ( 'yes' == BJLL.infinite_scroll ) {
114
+ $( window ).on( 'scroll', bj_lazy_load_init );
115
  }
116
+ $( window ).on( 'resize', function() { $( document ).trigger( 'scroll' ); } );
117
 
118
  })(jQuery);
js/combined.min.js CHANGED
@@ -1 +1,25 @@
1
- !function(a,b,c,d){a.fn.sonar=function(b,c){return"boolean"==typeof b&&(c=b,b=d),a.sonar(this[0],b,c)};var l,e=c.body,f=a(b),g="scrollin",h="scrollout",i=function(a,f,g){if(a){e||(e=c.body);var h=a,i=0,j=e.offsetHeight,k=b.innerHeight||c.documentElement.clientHeight||e.clientHeight||0,l=c.documentElement.scrollTop||b.pageYOffset||e.scrollTop||0,m=a.offsetHeight||0;if(!a.sonarElemTop||a.sonarBodyHeight!==j){if(h.offsetParent)do i+=h.offsetTop;while(h=h.offsetParent);a.sonarElemTop=i,a.sonarBodyHeight=j}return f=f===d?0:f,!(a.sonarElemTop+(g?0:m)<l-f||a.sonarElemTop+(g?m:0)>l+k+f)}},j={},k=0,m=function(){l&&clearTimeout(l),l=setTimeout(function(){var b,c,d,e,f,g,k;for(d in j)for(c=j[d],g=0,k=c.length;k>g;g++)e=c[g],b=e.elem,f=i(b,e.px,e.full),(d===h?!f:f)?e.tr||(b["_"+d]?(a(b).trigger(d),e.tr=1):(c.splice(g,1),g--,k--)):e.tr=0},0)},n=function(a,b){a["_"+b]=0},o=function(c,d){var e=d.px,l=d.full,n=d.evt,p=i(c,e,l),q=0;c["_"+n]=1,(n===h?!p:p)&&(setTimeout(function(){a(c).trigger(n===h?h:g)},0),q=1),j[n].push({elem:c,px:e,full:l,tr:q}),k||(f.bind("scroll",m),k=1)};a.sonar=i,j[g]=[],a.event.special[g]={add:function(a){var b=a.data||{},c=this;c[g]||o(this,{px:b.distance,full:b.full,evt:g})},remove:function(){n(this,g)}},j[h]=[],a.event.special[h]={add:function(a){var b=a.data||{},c=this;c[h]||o(c,{px:b.distance,full:b.full,evt:h})},remove:function(){n(this,h)}}}(jQuery,window,document);var BJLL=BJLL||{};!function(a){function b(){var b=200;"undefined"!=typeof BJLL.threshold&&(b=parseInt(BJLL.threshold)),a(".lazy-hidden").not(".data-lazy-ready").one("scrollin.bj_lazy_load",{distance:b},function(){var b=a(this),d=b.attr("data-lazy-type");if("image"==d){var e=b.attr("data-lazy-src");if("yes"==BJLL.load_responsive||"yes"==BJLL.load_hidpi){var f=document.createElement("a");if(f.href=b.attr("data-lazy-src"),!f.hostname.length||f.hostname==window.location.hostname){var g=parseInt(b.css("width"));window.devicePixelRatio>1&&"yes"==BJLL.load_hidpi&&(g=Math.ceil(window.devicePixelRatio*g));var h=b.attr("data-lazy-src");"undefined"!=typeof BJLL.site_url&&"undefined"!=typeof BJLL.network_site_url&&(h=h.replace(BJLL.site_url,BJLL.network_site_url)),e=BJLL.thumb_base+escape(h)+"&w="+g}}b.hide().attr("src",e).removeClass("lazy-hidden").fadeIn()}else"iframe"==d&&b.replaceWith(c(b.attr("data-lazy-src")))}).addClass("data-lazy-ready")}function c(a){var c,d,e,f,g,h,i,j,b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",k=0,l=0,m="",n=[];if(!a)return a;a+="";do f=b.indexOf(a.charAt(k++)),g=b.indexOf(a.charAt(k++)),h=b.indexOf(a.charAt(k++)),i=b.indexOf(a.charAt(k++)),j=f<<18|g<<12|h<<6|i,c=255&j>>16,d=255&j>>8,e=255&j,n[l++]=64==h?String.fromCharCode(c):64==i?String.fromCharCode(c,d):String.fromCharCode(c,d,e);while(k<a.length);return m=n.join("")}a(document).bind("ready",b),"yes"==BJLL.infinite_scroll&&a(window).bind("scroll",b),a(window).on("resize",function(){a(document).trigger("scroll")})}(jQuery);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ !function(e,t,n,o){e.fn.sonar=function(t,n){return"boolean"==typeof t&&(n=t,t=o),e.sonar(this[0],t,n)}
2
+ var r,a=n.body,i=e(t),l="scrollin",s="scrollout",d=function(e,r,i){if(e){a||(a=n.body)
3
+ var l=e,s=0,d=a.offsetHeight,c=t.innerHeight||n.documentElement.clientHeight||a.clientHeight||0,f=n.documentElement.scrollTop||t.pageYOffset||a.scrollTop||0,u=e.offsetHeight||0
4
+ if(!e.sonarElemTop||e.sonarBodyHeight!==d){if(l.offsetParent)do s+=l.offsetTop
5
+ while(l=l.offsetParent)
6
+ e.sonarElemTop=s,e.sonarBodyHeight=d}return r=r===o?0:r,!(e.sonarElemTop+(i?0:u)<f-r||e.sonarElemTop+(i?u:0)>f+c+r)}},c={},f=0,u=function(){r&&clearTimeout(r),r=setTimeout(function(){var t,n,o,r,a,i,l
7
+ for(o in c)for(n=c[o],i=0,l=n.length;l>i;i++)r=n[i],t=r.elem,a=d(t,r.px,r.full),(o===s?!a:a)?r.tr||(t["_"+o]?(e(t).triggerHandler(o),r.tr=1):(n.splice(i,1),i--,l--)):r.tr=0},0)},h=function(e,t){e["_"+t]=0},m=function(t,n){var o=n.px,r=n.full,a=n.evt,h=d(t,o,r),m=0
8
+ t["_"+a]=1,(a===s?!h:h)&&(setTimeout(function(){e(t).triggerHandler(a===s?s:l)},0),m=1),c[a].push({elem:t,px:o,full:r,tr:m}),f||(i.bind("scroll",u),f=1)}
9
+ e.sonar=d,c[l]=[],e.event.special[l]={add:function(e){var t=e.data||{},n=this
10
+ n[l]||m(this,{px:t.distance,full:t.full,evt:l})},remove:function(){h(this,l)}},c[s]=[],e.event.special[s]={add:function(e){var t=e.data||{},n=this
11
+ n[s]||m(n,{px:t.distance,full:t.full,evt:s})},remove:function(){h(this,s)}}}(jQuery,window,document)
12
+ var BJLL=BJLL||{}
13
+ !function(e){function t(){var t=200
14
+ void 0!==BJLL.threshold&&(t=parseInt(BJLL.threshold)),e(".lazy-hidden").not(".data-lazy-ready").one("scrollin.bj_lazy_load",{distance:t},function(){var t=e(this),o=t.attr("data-lazy-type")
15
+ if("image"==o){var r=t.attr("data-lazy-src")
16
+ if("yes"==BJLL.load_responsive||"yes"==BJLL.load_hidpi){var a=document.createElement("a")
17
+ if(a.href=t.attr("data-lazy-src"),!a.hostname.length||a.hostname==window.location.hostname){var i=parseInt(t.css("width"))
18
+ window.devicePixelRatio>1&&"yes"==BJLL.load_hidpi&&(i=Math.ceil(window.devicePixelRatio*i))
19
+ var l=t.attr("data-lazy-src")
20
+ void 0!==BJLL.site_url&&void 0!==BJLL.network_site_url&&(l=l.replace(BJLL.site_url,BJLL.network_site_url)),r=BJLL.thumb_base+escape(l)+"&w="+i}}t.hide().attr("src",r).removeClass("lazy-hidden").fadeIn()}else"iframe"==o&&t.replaceWith(n(t.attr("data-lazy-src")))}).addClass("data-lazy-ready")}function n(e){var t,n,o,r,a,i,l,s,d="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",c=0,f=0,u="",h=[]
21
+ if(!e)return e
22
+ e+=""
23
+ do r=d.indexOf(e.charAt(c++)),a=d.indexOf(e.charAt(c++)),i=d.indexOf(e.charAt(c++)),l=d.indexOf(e.charAt(c++)),s=r<<18|a<<12|i<<6|l,t=255&s>>16,n=255&s>>8,o=255&s,h[f++]=64==i?String.fromCharCode(t):64==l?String.fromCharCode(t,n):String.fromCharCode(t,n,o)
24
+ while(c<e.length)
25
+ return u=h.join("")}e(document).on("ready",t),"yes"==BJLL.infinite_scroll&&e(window).on("scroll",t),e(window).on("resize",function(){e(document).trigger("scroll")})}(jQuery)
js/jquery.sonar.js CHANGED
@@ -5,10 +5,10 @@
5
 
6
  General Usage:
7
 
8
- * Place the library anywhere in your JavaScript code before you
9
  intend to call the function.
10
 
11
- * To initialize Sonar with a different default distance, modify
12
  the sonar = Sonar() line immediately following the Sonar
13
  library definition. Example:
14
 
@@ -21,19 +21,19 @@
21
 
22
  sonar.detect() Usage
23
 
24
- * Use sonar.detect(elem, distance) to check if the
25
  elem is within screen boundaries.
26
 
27
  @elem - The elem you want to detect visibility.
28
  @distance - The distance from the screen edge that should
29
  count in the check. Uses default distance if not specified.
30
 
31
- * Note: sonar.detect() adds a property to
32
  ojbects called sonarElemTop. Test to ensure there
33
  aren't any conflicts with your code. If there
34
  are, rename sonarElemTop to something else in the code.
35
 
36
- * sonar.detect() returns:
37
  true if the elem is within the screen boundaries
38
  false if th elem is out of the screen boundaries
39
 
@@ -53,10 +53,10 @@
53
 
54
  sonar.add() Usage
55
 
56
- * This method stores elems that are then polled
57
  on user scroll by the Sonar.detect() method.
58
 
59
- * Polling initializes once the sonar.add() method is passed
60
  an elem with the following properties:
61
 
62
  obj : A reference to the elem to observe until it is within
@@ -65,7 +65,7 @@
65
  id : An alternative to the obj parameter, an "id" can be used
66
  to grab the elem to observe.
67
 
68
- call: The function to call when the elem is within the
69
  specified distance (px). The @elem argument will
70
  include the elem that triggered the callback.
71
 
@@ -102,7 +102,7 @@
102
 
103
  Notes:
104
 
105
- * Setting the body or html of your page to 100% will cause sonar to have
106
  an invalid height calculation in Firefox. It is recommended that you
107
  do not set this CSS property.
108
 
@@ -112,13 +112,13 @@
112
  height:100%; // Do not do this.
113
  }
114
 
115
- * If you want to set the default distance to something other
116
  than 0, either update the property directly in the code or
117
  you can do this:
118
 
119
  sonar.blip.d = 100; // Where 100 = 100 pixels above and below the screen edge.
120
 
121
- * Sleep well at night knowing Sonar automatically cleans up the
122
  event listeners on the scroll event once all calls have executed.
123
 
124
  Code History:
@@ -277,14 +277,14 @@ var body = doc.body,
277
  // If the elem is not detected (offscreen) or detected (onscreen)
278
  // remove the elem from the queue and fire the callback.
279
  if ( screenEvent === offScreenEvent ? !detected : detected ) {
280
- // // console.log(screenEvent);
281
  if (!options.tr) {
282
 
283
  if ( elem[ '_' + screenEvent ] ) {
284
  // console.log("triggered:" + elem.id);
285
  // Trigger the onscreen or offscreen event depending
286
  // on the desired event.
287
- $(elem).trigger( screenEvent );
288
 
289
  options.tr = 1;
290
 
@@ -335,7 +335,7 @@ var body = doc.body,
335
  // Trigger the onscreen event at the next possible cycle.
336
  // Artz: Ask the jQuery team why I needed to do this.
337
  setTimeout(function(){
338
- $(elem).trigger( screenEvent === offScreenEvent ? offScreenEvent : onScreenEvent );
339
  }, 0);
340
  triggered = 1;
341
  // Otherwise, add it to the polling queue.
@@ -418,4 +418,4 @@ var body = doc.body,
418
  };
419
 
420
  // console.log(pollQueue);
421
- })( jQuery, window, document );
5
 
6
  General Usage:
7
 
8
+ * Place the library anywhere in your JavaScript code before you
9
  intend to call the function.
10
 
11
+ * To initialize Sonar with a different default distance, modify
12
  the sonar = Sonar() line immediately following the Sonar
13
  library definition. Example:
14
 
21
 
22
  sonar.detect() Usage
23
 
24
+ * Use sonar.detect(elem, distance) to check if the
25
  elem is within screen boundaries.
26
 
27
  @elem - The elem you want to detect visibility.
28
  @distance - The distance from the screen edge that should
29
  count in the check. Uses default distance if not specified.
30
 
31
+ * Note: sonar.detect() adds a property to
32
  ojbects called sonarElemTop. Test to ensure there
33
  aren't any conflicts with your code. If there
34
  are, rename sonarElemTop to something else in the code.
35
 
36
+ * sonar.detect() returns:
37
  true if the elem is within the screen boundaries
38
  false if th elem is out of the screen boundaries
39
 
53
 
54
  sonar.add() Usage
55
 
56
+ * This method stores elems that are then polled
57
  on user scroll by the Sonar.detect() method.
58
 
59
+ * Polling initializes once the sonar.add() method is passed
60
  an elem with the following properties:
61
 
62
  obj : A reference to the elem to observe until it is within
65
  id : An alternative to the obj parameter, an "id" can be used
66
  to grab the elem to observe.
67
 
68
+ call: The function to call when the elem is within the
69
  specified distance (px). The @elem argument will
70
  include the elem that triggered the callback.
71
 
102
 
103
  Notes:
104
 
105
+ * Setting the body or html of your page to 100% will cause sonar to have
106
  an invalid height calculation in Firefox. It is recommended that you
107
  do not set this CSS property.
108
 
112
  height:100%; // Do not do this.
113
  }
114
 
115
+ * If you want to set the default distance to something other
116
  than 0, either update the property directly in the code or
117
  you can do this:
118
 
119
  sonar.blip.d = 100; // Where 100 = 100 pixels above and below the screen edge.
120
 
121
+ * Sleep well at night knowing Sonar automatically cleans up the
122
  event listeners on the scroll event once all calls have executed.
123
 
124
  Code History:
277
  // If the elem is not detected (offscreen) or detected (onscreen)
278
  // remove the elem from the queue and fire the callback.
279
  if ( screenEvent === offScreenEvent ? !detected : detected ) {
280
+ // // console.log(screenEvent);
281
  if (!options.tr) {
282
 
283
  if ( elem[ '_' + screenEvent ] ) {
284
  // console.log("triggered:" + elem.id);
285
  // Trigger the onscreen or offscreen event depending
286
  // on the desired event.
287
+ $(elem).triggerHandler( screenEvent );
288
 
289
  options.tr = 1;
290
 
335
  // Trigger the onscreen event at the next possible cycle.
336
  // Artz: Ask the jQuery team why I needed to do this.
337
  setTimeout(function(){
338
+ $(elem).triggerHandler( screenEvent === offScreenEvent ? offScreenEvent : onScreenEvent );
339
  }, 0);
340
  triggered = 1;
341
  // Otherwise, add it to the polling queue.
418
  };
419
 
420
  // console.log(pollQueue);
421
+ })( jQuery, window, document );
lang/bj-lazy-load-nb_NO.mo ADDED
Binary file
lang/bj-lazy-load-nb_NO.po ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ msgid ""
2
+ msgstr ""
3
+ "MIME-Version: 1.0\n"
4
+ "Content-Type: text/plain; charset=UTF-8\n"
5
+ "Content-Transfer-Encoding: 8bit\n"
6
+ "X-Generator: POEditor.com\n"
7
+ "Project-Id-Version: BJ Lazy Load\n"
8
+ "Language: nb\n"
9
+
10
+ #: admin.php:8
11
+ msgid "BJ Lazy Load Options"
12
+ msgstr "Innstillinger for BJ Lazy Load"
13
+
14
+ #: admin.php:17
15
+ msgid "Apply to content"
16
+ msgstr "Bruk på innhold"
17
+
18
+ #: admin.php:20 admin.php:26 admin.php:32 admin.php:38 admin.php:44
19
+ #: admin.php:76 admin.php:84 admin.php:92 admin.php:102 admin.php:112
20
+ msgid "Yes"
21
+ msgstr "Ja"
22
+
23
+ #: admin.php:20 admin.php:26 admin.php:32 admin.php:38 admin.php:44
24
+ #: admin.php:76 admin.php:84 admin.php:92 admin.php:102 admin.php:112
25
+ msgid "No"
26
+ msgstr "Nei"
27
+
28
+ #: admin.php:23
29
+ msgid "Apply to post thumbnails"
30
+ msgstr "Bruk på fremhevede bilder"
31
+
32
+ #: admin.php:29
33
+ msgid "Apply to gravatars"
34
+ msgstr "Bruk på gravatarer"
35
+
36
+ #: admin.php:35
37
+ msgid "Lazy load images"
38
+ msgstr "Lat lasting av bilder"
39
+
40
+ #: admin.php:41
41
+ msgid "Lazy load iframes"
42
+ msgstr "Lat lasting av iframes"
43
+
44
+ #: admin.php:47
45
+ msgid "Theme loader function"
46
+ msgstr "Innlastingsfunksjon i temaet"
47
+
48
+ #: admin.php:53
49
+ msgid "Placeholder Image URL"
50
+ msgstr "URL til plassholder-bilde"
51
+
52
+ #: admin.php:56
53
+ msgid "Leave blank for default"
54
+ msgstr "La være blank for standard"
55
+
56
+ #: admin.php:59
57
+ msgid "Skip images with classes"
58
+ msgstr "Ikke bruk på bilder med følgende klasser"
59
+
60
+ #: admin.php:62
61
+ msgid "Comma separated. Example: \"no-lazy, lazy-ignore, image-235\""
62
+ msgstr "Kommaseparert. Eks.: \"no-lazy, lazy-ignore, image-235\""
63
+
64
+ #: admin.php:65
65
+ msgid "Threshold"
66
+ msgstr "Terskel"
67
+
68
+ #: admin.php:68
69
+ msgid "How close to the viewport the element should be when we load it. In pixels. Example: 200"
70
+ msgstr "Hvor nære elmentet skal være det synlige nettleserviduet før vi laster det inn. Oppgitt i piksler. Eks.: 200"
71
+
72
+ #: admin.php:73
73
+ msgid "Infinite scroll"
74
+ msgstr "Uendelig scrolling"
75
+
76
+ #: admin.php:77
77
+ msgid "Enable if your theme uses infinite scroll."
78
+ msgstr "Skru på hvis temaet ditt bruker uendelig scrolling"
79
+
80
+ #: admin.php:81
81
+ msgid "Load hiDPI (retina) images"
82
+ msgstr "Last hiDPI-bilder (retina-bilder)"
83
+
84
+ #: admin.php:85
85
+ msgid "Will load hiDPI version of the images if the current browser/screen supports them. (Experimental feature. Do NOT enable if you are using a CDN)"
86
+ msgstr "Vil laste hiDPI-versjoner av bildene hvis nettleseren/skjermen har støtte for det. (Eksperimentell funksjonalitet. IKKE bruk dette hvis du bruker CDN)"
87
+
88
+ #: admin.php:89
89
+ msgid "Load responsive images"
90
+ msgstr "Last responsive bilder"
91
+
92
+ #: admin.php:93
93
+ msgid "Will load scaled down version of the images if the image is scaled down in the theme. (Experimental feature. Do NOT enable if you are using a CDN)"
94
+ msgstr "Vil laste nedskalerte versjoner av bildene hvis de er nedskalert i temaet. (Eksperimentell funksjonalitet. IKKE bruk dette hvis du bruker CDN)"
95
+
96
+ #: admin.php:99
97
+ msgid "Disable on WPTouch"
98
+ msgstr "Avslått på WPTouch"
99
+
100
+ #: admin.php:103
101
+ msgid "Disables BJ Lazy Load when the WPTouch mobile theme is used"
102
+ msgstr "Slår av BJ Lazy Load når det mobile temaet fra WPTouch er i bruk"
103
+
104
+ #: admin.php:109
105
+ msgid "Disable on MobilePress"
106
+ msgstr "Avslått på MobilePress"
107
+
108
+ #: admin.php:113
109
+ msgid "Disables BJ Lazy Load when the MobilePress mobile theme is used"
110
+ msgstr "Slår av BJ Lazy Load når det mobile temaet fra MobilePress er i bruk"
111
+
112
+ #: inc/class-bjll-skip-post.php:37
113
+ msgid "Lazy Loading"
114
+ msgstr "Lat lasting"
115
+
116
+ #: inc/class-bjll-skip-post.php:50
117
+ msgid "Skip lazy loading for this page"
118
+ msgstr "Ikke bruk lat lasting på denne siden"
119
+
120
+ #: inc/class-bjll-skip-post.php:50
121
+ msgid "Skip lazy loading for this post"
122
+ msgstr "Ikke bruk lat lasting på dette innlegget"
123
+
124
+ #: scb/AdminPage.php:227
125
+ msgid "Settings <strong>saved</strong>."
126
+ msgstr "Innstillinger er <strong>lagret</strong>."
127
+
128
+ #: scb/AdminPage.php:466
129
+ msgid "Settings"
130
+ msgstr "Innstillinger"
131
+
132
+ #. Plugin Name of the plugin/theme
133
+ #:
134
+ msgid "BJ Lazy Load"
135
+ msgstr "BJ Lazy Load"
136
+
137
+ #. Plugin URI of the plugin/theme
138
+ #:
139
+ msgid "http://wordpress.org/extend/plugins/bj-lazy-load/"
140
+ msgstr "http://wordpress.org/extend/plugins/bj-lazy-load/"
141
+
142
+ #. Description of the plugin/theme
143
+ #:
144
+ msgid "Lazy image loading makes your site load faster and saves bandwidth."
145
+ msgstr "Lat lasting gjør at nettstedet ditt laster raskere og du sparer båndbredde."
146
+
147
+ #. Author of the plugin/theme
148
+ #:
149
+ msgid "Bjørn Johansen"
150
+ msgstr "Bjørn Johansen"
151
+
152
+ #. Author URI of the plugin/theme
153
+ #:
154
+ msgid "http://twitter.com/bjornjohansen"
155
+ msgstr "http://twitter.com/bjornjohansen"
156
+
lang/bj-lazy-load-ru_RU.mo ADDED
Binary file
lang/bj-lazy-load-ru_RU.po ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ msgid ""
2
+ msgstr ""
3
+ "MIME-Version: 1.0\n"
4
+ "Content-Type: text/plain; charset=UTF-8\n"
5
+ "Content-Transfer-Encoding: 8bit\n"
6
+ "X-Generator: Poedit 1.5.7\n"
7
+ "Project-Id-Version: BJ Lazy Load\n"
8
+ "Language: RUSSIAN\n"
9
+ "POT-Creation-Date: \n"
10
+ "PO-Revision-Date: \n"
11
+ "Last-Translator: Elvis TEAM <elviswebteam@gmail.com>\n"
12
+ "Language-Team: Elvis WT <elviswebteam@gmail.com>\n"
13
+
14
+ #: admin.php:8
15
+ msgid "BJ Lazy Load Options"
16
+ msgstr "Настройки плагина"
17
+
18
+ #: admin.php:17
19
+ msgid "Apply to content"
20
+ msgstr "Применять к изображениям в записях"
21
+
22
+ #: admin.php:20 admin.php:26 admin.php:32 admin.php:38 admin.php:44
23
+ #: admin.php:76 admin.php:84 admin.php:92 admin.php:102 admin.php:112
24
+ msgid "Yes"
25
+ msgstr "Да"
26
+
27
+ #: admin.php:20 admin.php:26 admin.php:32 admin.php:38 admin.php:44
28
+ #: admin.php:76 admin.php:84 admin.php:92 admin.php:102 admin.php:112
29
+ msgid "No"
30
+ msgstr "Нет"
31
+
32
+ #: admin.php:23
33
+ msgid "Apply to post thumbnails"
34
+ msgstr "Применять к миниатюрам записей"
35
+
36
+ #: admin.php:29
37
+ msgid "Apply to gravatars"
38
+ msgstr "Применять к изображениям граватар"
39
+
40
+ #: admin.php:35
41
+ msgid "Lazy load images"
42
+ msgstr "Постзагрузка изображений"
43
+
44
+ #: admin.php:41
45
+ msgid "Lazy load iframes"
46
+ msgstr "Постзагрузка фреймов iframe"
47
+
48
+ #: admin.php:47
49
+ msgid "Theme loader function"
50
+ msgstr "Функция загрузчика темы"
51
+
52
+ #: admin.php:53
53
+ msgid "Placeholder Image URL"
54
+ msgstr "Ссылка на графический плейсхолдер"
55
+
56
+ #: admin.php:56
57
+ msgid "Leave blank for default"
58
+ msgstr "Чтобы использовать значение по умолчанию, оставьте поле путым"
59
+
60
+ #: admin.php:59
61
+ msgid "Skip images with classes"
62
+ msgstr "Пропускать изображения, использующие значения class:"
63
+
64
+ #: admin.php:62
65
+ msgid "Comma separated. Example: \"no-lazy, lazy-ignore, image-235\""
66
+ msgstr "Разделять запятыми. Например: \"no-lazy, lazy-ignore, image-235\""
67
+
68
+ #: admin.php:65
69
+ msgid "Threshold"
70
+ msgstr "Начало загрузки"
71
+
72
+ #: admin.php:68
73
+ msgid ""
74
+ "How close to the viewport the element should be when we load it. In pixels. "
75
+ "Example: 200"
76
+ msgstr ""
77
+ "Как \"близко\" к изображению нужно находиться, чтобы оно стало загружаться. "
78
+ "В пикселях. Например: 200"
79
+
80
+ #: admin.php:73
81
+ msgid "Infinite scroll"
82
+ msgstr "Бесконечная прокрутка"
83
+
84
+ #: admin.php:77
85
+ msgid "Enable if your theme uses infinite scroll."
86
+ msgstr ""
87
+ "Включите опцию, если используется бесконечная прокрутка (т.е. нет "
88
+ "постраничной навигации, контент подгружается при прокрутке страницы вниз)"
89
+
90
+ #: admin.php:81
91
+ msgid "Load hiDPI (retina) images"
92
+ msgstr "Загрузка hiDPI изображений (ретина)"
93
+
94
+ #: admin.php:85
95
+ msgid ""
96
+ "Will load hiDPI version of the images if the current browser/screen supports "
97
+ "them. (Experimental feature. Do NOT enable if you are using a CDN)"
98
+ msgstr ""
99
+ "Будет загружать hiDPI изображения, если браузер/экран поддерживает их "
100
+ "(экспериментальная функция, не использовать с CDN)."
101
+
102
+ #: admin.php:89
103
+ msgid "Load responsive images"
104
+ msgstr "Загрузка \"гибких\" изображений"
105
+
106
+ #: admin.php:93
107
+ msgid ""
108
+ "Will load scaled down version of the images if the image is scaled down in "
109
+ "the theme. (Experimental feature. Do NOT enable if you are using a CDN)"
110
+ msgstr ""
111
+ "Будет загружать уменьшенные изображения, если тема оформления меняет "
112
+ "разрешение изображений для мобильных браузеров (экспериментальная функция, "
113
+ "не использовать с CDN)."
114
+
115
+ #: admin.php:99
116
+ msgid "Disable on WPTouch"
117
+ msgstr "Отключить для WPTouch"
118
+
119
+ #: admin.php:103
120
+ msgid "Disables BJ Lazy Load when the WPTouch mobile theme is used"
121
+ msgstr ""
122
+ "Отключить BJ Lazy Load если используется мобильная тема оформления, "
123
+ "созданная плагином WPTouch"
124
+
125
+ #: admin.php:109
126
+ msgid "Disable on MobilePress"
127
+ msgstr "Отключить для MobilePress"
128
+
129
+ #: admin.php:113
130
+ msgid "Disables BJ Lazy Load when the MobilePress mobile theme is used"
131
+ msgstr ""
132
+ "Отключить BJ Lazy Load если используется мобильная тема оформления, "
133
+ "созданная плагином MobilePress"
134
+
135
+ #: inc/class-bjll-skip-post.php:37
136
+ msgid "Lazy Loading"
137
+ msgstr "\"Ленивая\" загрузка"
138
+
139
+ #: inc/class-bjll-skip-post.php:50
140
+ msgid "Skip lazy loading for this page"
141
+ msgstr "Пропустить обработку изображений для этой страницы"
142
+
143
+ #: inc/class-bjll-skip-post.php:50
144
+ msgid "Skip lazy loading for this post"
145
+ msgstr "Пропустить обработку изображений для этой записи"
146
+
147
+ #: scb/AdminPage.php:227
148
+ msgid "Settings <strong>saved</strong>."
149
+ msgstr "Настройки <strong>сохранены</strong>."
150
+
151
+ #: scb/AdminPage.php:466
152
+ msgid "Settings"
153
+ msgstr "Настройки"
154
+
155
+ #. Plugin Name of the plugin/theme
156
+ msgid "BJ Lazy Load"
157
+ msgstr "BJ Lazy Load"
158
+
159
+ #. Plugin URI of the plugin/theme
160
+ msgid "http://wordpress.org/extend/plugins/bj-lazy-load/"
161
+ msgstr "http://wordpress.org/extend/plugins/bj-lazy-load/"
162
+
163
+ #. Description of the plugin/theme
164
+ msgid "Lazy image loading makes your site load faster and saves bandwidth."
165
+ msgstr ""
166
+ "С помощью плагина изображения загружаются после страницы, ускоряя время "
167
+ "загрузки и уменьшая трафик."
168
+
169
+ #. Author of the plugin/theme
170
+ msgid "Bjørn Johansen"
171
+ msgstr "Bjørn Johansen"
172
+
173
+ #. Author URI of the plugin/theme
174
+ msgid "http://twitter.com/bjornjohansen"
175
+ msgstr "http://twitter.com/bjornjohansen"
lang/bj-lazy-load.pot ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2013 BJ Lazy Load
2
+ # This file is distributed under the same license as the BJ Lazy Load package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: BJ Lazy Load 0.7.0\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/bj-lazy-load\n"
7
+ "POT-Creation-Date: 2013-11-13 22:26:06+00:00\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=UTF-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2013-MO-DA HO:MI+ZONE\n"
12
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
+ "Language-Team: LANGUAGE <LL@li.org>\n"
14
+
15
+ #: admin.php:8
16
+ msgid "BJ Lazy Load Options"
17
+ msgstr ""
18
+
19
+ #: admin.php:17
20
+ msgid "Apply to content"
21
+ msgstr ""
22
+
23
+ #: admin.php:20 admin.php:26 admin.php:32 admin.php:38 admin.php:44
24
+ #: admin.php:76 admin.php:84 admin.php:92 admin.php:102 admin.php:112
25
+ msgid "Yes"
26
+ msgstr ""
27
+
28
+ #: admin.php:20 admin.php:26 admin.php:32 admin.php:38 admin.php:44
29
+ #: admin.php:76 admin.php:84 admin.php:92 admin.php:102 admin.php:112
30
+ msgid "No"
31
+ msgstr ""
32
+
33
+ #: admin.php:23
34
+ msgid "Apply to post thumbnails"
35
+ msgstr ""
36
+
37
+ #: admin.php:29
38
+ msgid "Apply to gravatars"
39
+ msgstr ""
40
+
41
+ #: admin.php:35
42
+ msgid "Lazy load images"
43
+ msgstr ""
44
+
45
+ #: admin.php:41
46
+ msgid "Lazy load iframes"
47
+ msgstr ""
48
+
49
+ #: admin.php:47
50
+ msgid "Theme loader function"
51
+ msgstr ""
52
+
53
+ #: admin.php:53
54
+ msgid "Placeholder Image URL"
55
+ msgstr ""
56
+
57
+ #: admin.php:56
58
+ msgid "Leave blank for default"
59
+ msgstr ""
60
+
61
+ #: admin.php:59
62
+ msgid "Skip images with classes"
63
+ msgstr ""
64
+
65
+ #: admin.php:62
66
+ msgid "Comma separated. Example: \"no-lazy, lazy-ignore, image-235\""
67
+ msgstr ""
68
+
69
+ #: admin.php:65
70
+ msgid "Threshold"
71
+ msgstr ""
72
+
73
+ #: admin.php:68
74
+ msgid ""
75
+ "How close to the viewport the element should be when we load it. In pixels. "
76
+ "Example: 200"
77
+ msgstr ""
78
+
79
+ #: admin.php:73
80
+ msgid "Infinite scroll"
81
+ msgstr ""
82
+
83
+ #: admin.php:77
84
+ msgid "Enable if your theme uses infinite scroll."
85
+ msgstr ""
86
+
87
+ #: admin.php:81
88
+ msgid "Load hiDPI (retina) images"
89
+ msgstr ""
90
+
91
+ #: admin.php:85
92
+ msgid ""
93
+ "Will load hiDPI version of the images if the current browser/screen supports "
94
+ "them. (Experimental feature. Do NOT enable if you are using a CDN)"
95
+ msgstr ""
96
+
97
+ #: admin.php:89
98
+ msgid "Load responsive images"
99
+ msgstr ""
100
+
101
+ #: admin.php:93
102
+ msgid ""
103
+ "Will load scaled down version of the images if the image is scaled down in "
104
+ "the theme. (Experimental feature. Do NOT enable if you are using a CDN)"
105
+ msgstr ""
106
+
107
+ #: admin.php:99
108
+ msgid "Disable on WPTouch"
109
+ msgstr ""
110
+
111
+ #: admin.php:103
112
+ msgid "Disables BJ Lazy Load when the WPTouch mobile theme is used"
113
+ msgstr ""
114
+
115
+ #: admin.php:109
116
+ msgid "Disable on MobilePress"
117
+ msgstr ""
118
+
119
+ #: admin.php:113
120
+ msgid "Disables BJ Lazy Load when the MobilePress mobile theme is used"
121
+ msgstr ""
122
+
123
+ #: inc/class-bjll-skip-post.php:37
124
+ msgid "Lazy Loading"
125
+ msgstr ""
126
+
127
+ #: inc/class-bjll-skip-post.php:50
128
+ msgid "Skip lazy loading for this page"
129
+ msgstr ""
130
+
131
+ #: inc/class-bjll-skip-post.php:50
132
+ msgid "Skip lazy loading for this post"
133
+ msgstr ""
134
+
135
+ #: scb/AdminPage.php:227
136
+ msgid "Settings <strong>saved</strong>."
137
+ msgstr ""
138
+
139
+ #: scb/AdminPage.php:466
140
+ msgid "Settings"
141
+ msgstr ""
142
+
143
+ #. Plugin Name of the plugin/theme
144
+ msgid "BJ Lazy Load"
145
+ msgstr ""
146
+
147
+ #. Plugin URI of the plugin/theme
148
+ msgid "http://wordpress.org/extend/plugins/bj-lazy-load/"
149
+ msgstr ""
150
+
151
+ #. Description of the plugin/theme
152
+ msgid "Lazy image loading makes your site load faster and saves bandwidth."
153
+ msgstr ""
154
+
155
+ #. Author of the plugin/theme
156
+ msgid "Bjørn Johansen"
157
+ msgstr ""
158
+
159
+ #. Author URI of the plugin/theme
160
+ msgid "http://twitter.com/bjornjohansen"
161
+ msgstr ""
lang/index.html ADDED
File without changes
readme.txt CHANGED
@@ -3,9 +3,8 @@ Contributors: bjornjohansen
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NLUWR4SHCJRBJ
4
  Tags: images, iframes, lazy loading, jquery, javascript, optimize, performance, bandwidth, responsive design, hidpi, retina
5
  Author URI: http://twitter.com/bjornjohansen
6
- Requires at least: 3.3
7
- Tested up to: 3.6
8
- Stable tag: 0.6.10
9
 
10
  Lazy loading makes your site load faster and saves bandwidth. Uses jQuery and degrades gracefully for non-js users. Works with both images and iframes.
11
 
@@ -30,15 +29,17 @@ Please let me know if you have any issues. Fastest way to get a response is by T
30
  3. Activate the plugin through the 'Plugins' menu in WordPress.
31
 
32
  == Optional usage ==
33
- If you have images output in custom templates or want to lazy load other images in your theme, you may filter the HTML through BJLL::filter():
 
34
  `<?php
35
- $img = '<img src="myimage.jpg" alt="">';
36
- if ( class_exists( 'BJLL' ) ) {
37
- $img = BJLL::filter( $img );
38
- }
39
- echo $img;
40
  ?>`
41
 
 
 
 
42
  == Frequently Asked Questions ==
43
 
44
  = Whoa, this plugin is using JavaScript. What about visitors without JS? =
@@ -60,6 +61,16 @@ Check your HTML source or see the magic at work in Web Inspector, FireBug or sim
60
 
61
  == Changelog ==
62
 
 
 
 
 
 
 
 
 
 
 
63
  = Version 0.6.10 =
64
  * Responsive and HiDPI images works with MultiSite subfolders
65
  * Lazy loading is disabled on Opera Mini
@@ -75,7 +86,7 @@ Check your HTML source or see the magic at work in Web Inspector, FireBug or sim
75
 
76
  = Version 0.6.7 =
77
  * Combined JS files for faster loading
78
- * Bugfix for when viewport is resized – now triggering scroll event (thanks kReEsTaL)
79
 
80
  = Version 0.6.6 =
81
  * Option to disable BJ Lazy Load for MobilePress
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NLUWR4SHCJRBJ
4
  Tags: images, iframes, lazy loading, jquery, javascript, optimize, performance, bandwidth, responsive design, hidpi, retina
5
  Author URI: http://twitter.com/bjornjohansen
6
+ Requires at least: 3.5
7
+ Stable tag: 0.7.0
 
8
 
9
  Lazy loading makes your site load faster and saves bandwidth. Uses jQuery and degrades gracefully for non-js users. Works with both images and iframes.
10
 
29
  3. Activate the plugin through the 'Plugins' menu in WordPress.
30
 
31
  == Optional usage ==
32
+ If you have images output in custom templates or want to lazy load other images in your theme, you may pass the HTML through a filter:
33
+
34
  `<?php
35
+ $img_html = '<img src="myimage.jpg" alt="">';
36
+ $img_html = apply_filters( 'bj_lazy_load_html', $img_html );
37
+ echo $img_html;
 
 
38
  ?>`
39
 
40
+ Note for developers: The filter has a priority of 10.
41
+
42
+
43
  == Frequently Asked Questions ==
44
 
45
  = Whoa, this plugin is using JavaScript. What about visitors without JS? =
61
 
62
  == Changelog ==
63
 
64
+ = Version 0.7.0 =
65
+ * Added meta box to all public post types to exclude BJ Lazy Load for individual posts/pages
66
+ * Placeholder image is replaced with a really short data-uri (thanks @jruizcantero)
67
+ * Added a proper WordPress filter method for arbitrary HTML filtering with: apply_filters( 'bj_lazy_load_html', $html )
68
+ * Updated scbFramework to release 58 (no more strict warnings in admin)
69
+ * Updated jQuery.sonar to latest version (as of 2013-11-13)
70
+ * Added POT file (Go translate!)
71
+ * Added translation to Norwegian Bokmål (nb_NO)
72
+ * Added translation to Russian (ru_RU) by Elvisrk
73
+
74
  = Version 0.6.10 =
75
  * Responsive and HiDPI images works with MultiSite subfolders
76
  * Lazy loading is disabled on Opera Mini
86
 
87
  = Version 0.6.7 =
88
  * Combined JS files for faster loading
89
+ * Bugfix for when viewport is resized – now triggering scroll event (thanks @kReEsTaL)
90
 
91
  = Version 0.6.6 =
92
  * Option to disable BJ Lazy Load for MobilePress
scb/AdminPage.php CHANGED
@@ -1,13 +1,15 @@
1
  <?php
2
 
3
- // Administration page base class
4
-
 
5
  abstract class scbAdminPage {
6
  /** Page args
7
  * $page_title string (mandatory)
8
  * $parent (string) (default: options-general.php)
9
  * $capability (string) (default: 'manage_options')
10
  * $menu_title (string) (default: $page_title)
 
11
  * $page_slug (string) (default: sanitized $page_title)
12
  * $toplevel (string) If not empty, will create a new top level menu (for expected values see http://codex.wordpress.org/Administration_Menus#Using_add_submenu_page)
13
  * - $icon_url (string) URL to an icon for the top level menu
@@ -40,6 +42,13 @@ abstract class scbAdminPage {
40
 
41
  private static $registered = array();
42
 
 
 
 
 
 
 
 
43
  static function register( $class, $file, $options = null ) {
44
  if ( isset( self::$registered[$class] ) )
45
  return false;
@@ -51,6 +60,12 @@ abstract class scbAdminPage {
51
  return true;
52
  }
53
 
 
 
 
 
 
 
54
  static function replace( $old_class, $new_class ) {
55
  if ( ! isset( self::$registered[$old_class] ) )
56
  return false;
@@ -61,6 +76,11 @@ abstract class scbAdminPage {
61
  return true;
62
  }
63
 
 
 
 
 
 
64
  static function remove( $class ) {
65
  if ( ! isset( self::$registered[$class] ) )
66
  return false;
@@ -79,7 +99,12 @@ abstract class scbAdminPage {
79
  // ____________MAIN METHODS____________
80
 
81
 
82
- // Constructor
 
 
 
 
 
83
  function __construct( $file = false, $options = null ) {
84
  if ( is_a( $options, 'scbOptions' ) )
85
  $this->options = $options;
@@ -105,7 +130,9 @@ abstract class scbAdminPage {
105
  }
106
  }
107
 
108
- // This is where all the page args can be set
 
 
109
  function setup(){}
110
 
111
  /**
@@ -115,43 +142,64 @@ abstract class scbAdminPage {
115
  */
116
  function page_loaded() {}
117
 
118
- // This is where the css and js go
119
- // Both wp_enqueue_*() and inline code can be added
 
 
120
  function page_head(){}
121
 
122
- // This is where the contextual help goes
123
- // @return string
 
 
124
  function page_help(){}
125
 
126
- // A generic page header
 
 
127
  function page_header() {
128
  echo "<div class='wrap'>\n";
129
  screen_icon( $this->args['screen_icon'] );
130
- echo "<h2>" . $this->args['page_title'] . "</h2>\n";
131
  }
132
 
133
- // This is where the page content goes
 
 
134
  abstract function page_content();
135
 
136
- // A generic page footer
 
 
137
  function page_footer() {
138
  echo "</div>\n";
139
  }
140
 
141
- // This is where the form data should be validated
 
 
 
 
 
 
 
142
  function validate( $new_data, $old_data ) {
143
  return $new_data;
144
  }
145
 
146
- // Manually handle option saving ( use Settings API instead )
 
 
 
 
147
  function form_handler() {
148
- if ( empty( $_POST['action'] ) )
149
  return false;
150
 
151
  check_admin_referer( $this->nonce );
152
 
153
  if ( !isset($this->options) ) {
154
- trigger_error('options handler not set', E_USER_WARNING);
155
  return false;
156
  }
157
 
@@ -164,10 +212,17 @@ abstract class scbAdminPage {
164
  $this->options->set( $new_data );
165
 
166
  $this->admin_msg();
 
 
167
  }
168
 
169
- // Manually generate a standard admin notice ( use Settings API instead )
170
- function admin_msg( $msg = '', $class = "updated" ) {
 
 
 
 
 
171
  if ( empty( $msg ) )
172
  $msg = __( 'Settings <strong>saved</strong>.', $this->textdomain );
173
 
@@ -178,52 +233,47 @@ abstract class scbAdminPage {
178
  // ____________UTILITIES____________
179
 
180
 
181
- // Generates a form submit button
182
- function submit_button( $value = '', $action = 'action', $class = "button" ) {
183
- if ( is_array( $value ) ) {
184
- extract( wp_parse_args( $value, array(
185
- 'value' => __( 'Save Changes', $this->textdomain ),
186
- 'action' => 'action',
187
- 'class' => 'button',
188
- 'ajax' => true
189
- ) ) );
190
-
191
- if ( ! $ajax )
192
- $class .= ' no-ajax';
193
- }
194
- else {
195
- if ( empty( $value ) )
196
- $value = __( 'Save Changes', $this->textdomain );
197
- }
198
-
199
- $input_args = array(
200
- 'type' => 'submit',
201
- 'name' => $action,
202
- 'value' => $value,
203
- 'extra' => '',
204
- 'desc' => false,
205
- 'wrap' => html( 'p class="submit"', scbForms::TOKEN )
206
- );
207
 
208
- if ( ! empty( $class ) )
209
- $input_args['extra'] = compact( 'class' );
 
 
 
 
210
 
211
- return scbForms::input( $input_args );
212
  }
213
 
214
- /*
215
- Mimics scbForms::form_wrap()
216
-
217
- $this->form_wrap( $content ); // generates a form with a default submit button
218
-
219
- $this->form_wrap( $content, false ); // generates a form with no submit button
220
-
221
- // the second argument is sent to submit_button()
222
- $this->form_wrap( $content, array( 'text' => 'Save changes',
223
- 'name' => 'action',
224
- 'ajax' => true,
225
- ) );
226
- */
 
 
 
 
 
 
 
227
  function form_wrap( $content, $submit_button = true ) {
228
  if ( is_array( $submit_button ) ) {
229
  $content .= $this->submit_button( $submit_button );
@@ -231,15 +281,24 @@ abstract class scbAdminPage {
231
  $content .= $this->submit_button();
232
  } elseif ( false !== strpos( $submit_button, '<input' ) ) {
233
  $content .= $submit_button;
 
 
234
  } elseif ( false !== $submit_button ) {
235
  $button_args = array_slice( func_get_args(), 1 );
236
- $content .= call_user_func_array( array( $this, 'submit_button' ), $button_args );
237
  }
238
 
239
  return scbForms::form_wrap( $content, $this->nonce );
240
  }
241
 
242
- // Generates a table wrapped in a form
 
 
 
 
 
 
 
243
  function form_table( $rows, $formdata = false ) {
244
  $output = '';
245
  foreach ( $rows as $row )
@@ -250,7 +309,13 @@ abstract class scbAdminPage {
250
  return $output;
251
  }
252
 
253
- // Wraps the given content in a <form><table>
 
 
 
 
 
 
254
  function form_table_wrap( $content ) {
255
  $output = $this->table_wrap( $content );
256
  $output = $this->form_wrap( $output );
@@ -258,7 +323,14 @@ abstract class scbAdminPage {
258
  return $output;
259
  }
260
 
261
- // Generates a form table
 
 
 
 
 
 
 
262
  function table( $rows, $formdata = false ) {
263
  $output = '';
264
  foreach ( $rows as $row )
@@ -269,26 +341,28 @@ abstract class scbAdminPage {
269
  return $output;
270
  }
271
 
272
- // Generates a table row
 
 
 
 
 
 
 
273
  function table_row( $args, $formdata = false ) {
274
  return $this->row_wrap( $args['title'], $this->input( $args, $formdata ) );
275
  }
276
 
277
- // Wraps the given content in a <table>
278
- function table_wrap( $content ) {
279
- return
280
- html( 'table class="form-table"', $content );
281
- }
282
-
283
- // Wraps the given content in a <tr><td>
284
- function row_wrap( $title, $content ) {
285
- return
286
- html( 'tr',
287
- html( 'th scope="row"', $title )
288
- .html( 'td', $content ) );
289
- }
290
-
291
- // Mimic scbForms inheritance
292
  function __call( $method, $args ) {
293
  if ( in_array( $method, array( 'input', 'form' ) ) ) {
294
  if ( empty( $args[1] ) && isset( $this->options ) )
@@ -301,29 +375,66 @@ abstract class scbAdminPage {
301
  return call_user_func_array( array( 'scbForms', $method ), $args );
302
  }
303
 
304
- // Wraps a string in a <script> tag
 
 
 
 
 
 
305
  function js_wrap( $string ) {
306
- return "\n<script type='text/javascript'>\n" . $string . "\n</script>\n";
307
  }
308
 
309
- // Wraps a string in a <style> tag
 
 
 
 
 
 
310
  function css_wrap( $string ) {
311
- return "\n<style type='text/css'>\n" . $string . "\n</style>\n";
312
  }
313
 
314
 
315
  // ____________INTERNAL METHODS____________
316
 
317
 
318
- // Registers a page
 
 
319
  function page_init() {
320
- extract( $this->args );
321
 
322
- if ( ! $toplevel ) {
323
- $this->pagehook = add_submenu_page( $parent, $page_title, $menu_title, $capability, $page_slug, array( $this, '_page_content_hook' ) );
 
 
 
 
 
 
 
324
  } else {
325
- $func = 'add_' . $toplevel . '_page';
326
- $this->pagehook = $func( $page_title, $menu_title, $capability, $page_slug, array( $this, '_page_content_hook' ), $icon_url, $position );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  }
328
 
329
  if ( ! $this->pagehook )
@@ -331,11 +442,6 @@ abstract class scbAdminPage {
331
 
332
  add_action( 'load-' . $this->pagehook, array( $this, 'page_loaded' ) );
333
 
334
- if ( $ajax_submit ) {
335
- $this->ajax_response();
336
- add_action( 'admin_footer', array( $this, 'ajax_submit' ), 20 );
337
- }
338
-
339
  add_action( 'admin_print_styles-' . $this->pagehook, array( $this, 'page_head' ) );
340
  }
341
 
@@ -348,20 +454,22 @@ abstract class scbAdminPage {
348
  trigger_error( 'Page title cannot be empty', E_USER_WARNING );
349
 
350
  $this->args = wp_parse_args( $this->args, array(
351
- 'toplevel' => '',
352
- 'position' => null,
353
- 'icon_url' => '',
354
- 'screen_icon' => '',
355
- 'parent' => 'options-general.php',
356
- 'capability' => 'manage_options',
357
- 'menu_title' => $this->args['page_title'],
358
- 'page_slug' => '',
359
- 'nonce' => '',
360
- 'action_link' => __( 'Settings', $this->textdomain ),
361
- 'ajax_submit' => false,
362
  'admin_action_priority' => 10,
363
  ) );
364
 
 
 
 
365
  if ( empty( $this->args['page_slug'] ) )
366
  $this->args['page_slug'] = sanitize_title_with_dashes( $this->args['menu_title'] );
367
 
@@ -369,6 +477,12 @@ abstract class scbAdminPage {
369
  $this->nonce = $this->args['page_slug'];
370
  }
371
 
 
 
 
 
 
 
372
  function _contextual_help( $help, $screen ) {
373
  if ( is_object( $screen ) )
374
  $screen = $screen->id;
@@ -381,59 +495,6 @@ abstract class scbAdminPage {
381
  return $help;
382
  }
383
 
384
- function ajax_response() {
385
- if ( ! isset( $_POST['_ajax_submit'] ) || $_POST['_ajax_submit'] != $this->pagehook )
386
- return;
387
-
388
- $this->form_handler();
389
- die;
390
- }
391
-
392
- function ajax_submit() {
393
- global $page_hook;
394
-
395
- if ( $page_hook != $this->pagehook )
396
- return;
397
- ?>
398
- <script type="text/javascript">
399
- jQuery( document ).ready( function( $ ){
400
- var $spinner = $( new Image() ).attr( 'src', '<?php echo admin_url( "images/wpspin_light.gif" ); ?>' );
401
-
402
- $( ':submit' ).click( function( ev ){
403
- var $submit = $( this );
404
- var $form = $submit.parents( 'form' );
405
-
406
- if ( $submit.hasClass( 'no-ajax' ) || $form.attr( 'method' ).toLowerCase() != 'post' )
407
- return true;
408
-
409
- var $this_spinner = $spinner.clone();
410
-
411
- $submit.before( $this_spinner ).hide();
412
-
413
- var data = $form.serializeArray();
414
- data.push( {name: $submit.attr( 'name' ), value: $submit.val()} );
415
- data.push( {name: '_ajax_submit', value: '<?php echo $this->pagehook; ?>'} );
416
-
417
- $.post( location.href, data, function( response ){
418
- var $prev = $( '.wrap > .updated, .wrap > .error' );
419
- var $msg = $( response ).hide().insertAfter( $( '.wrap h2' ) );
420
- if ( $prev.length > 0 )
421
- $prev.fadeOut( 'slow', function(){ $msg.fadeIn( 'slow' ); } );
422
- else
423
- $msg.fadeIn( 'slow' );
424
-
425
- $this_spinner.hide();
426
- $submit.show();
427
- } );
428
-
429
- ev.stopPropagation();
430
- ev.preventDefault();
431
- } );
432
- } );
433
- </script>
434
- <?php
435
- }
436
-
437
  function _page_content_hook() {
438
  $this->form_handler();
439
 
@@ -442,6 +503,11 @@ jQuery( document ).ready( function( $ ){
442
  $this->page_footer();
443
  }
444
 
 
 
 
 
 
445
  function _action_link( $links ) {
446
  $url = add_query_arg( 'page', $this->args['page_slug'], admin_url( $this->args['parent'] ) );
447
 
1
  <?php
2
 
3
+ /**
4
+ * Administration page base class
5
+ */
6
  abstract class scbAdminPage {
7
  /** Page args
8
  * $page_title string (mandatory)
9
  * $parent (string) (default: options-general.php)
10
  * $capability (string) (default: 'manage_options')
11
  * $menu_title (string) (default: $page_title)
12
+ * $submenu_title (string) (default: $menu_title)
13
  * $page_slug (string) (default: sanitized $page_title)
14
  * $toplevel (string) If not empty, will create a new top level menu (for expected values see http://codex.wordpress.org/Administration_Menus#Using_add_submenu_page)
15
  * - $icon_url (string) URL to an icon for the top level menu
42
 
43
  private static $registered = array();
44
 
45
+ /**
46
+ * @param string $class
47
+ * @param string $file
48
+ * @param scbOptions $options
49
+ *
50
+ * @return bool
51
+ */
52
  static function register( $class, $file, $options = null ) {
53
  if ( isset( self::$registered[$class] ) )
54
  return false;
60
  return true;
61
  }
62
 
63
+ /**
64
+ * @param string $old_class
65
+ * @param string $new_class
66
+ *
67
+ * @return bool
68
+ */
69
  static function replace( $old_class, $new_class ) {
70
  if ( ! isset( self::$registered[$old_class] ) )
71
  return false;
76
  return true;
77
  }
78
 
79
+ /**
80
+ * @param string $class
81
+ *
82
+ * @return bool
83
+ */
84
  static function remove( $class ) {
85
  if ( ! isset( self::$registered[$class] ) )
86
  return false;
99
  // ____________MAIN METHODS____________
100
 
101
 
102
+ /**
103
+ * Constructor
104
+ *
105
+ * @param string|bool $file
106
+ * @param scbOptions $options
107
+ */
108
  function __construct( $file = false, $options = null ) {
109
  if ( is_a( $options, 'scbOptions' ) )
110
  $this->options = $options;
130
  }
131
  }
132
 
133
+ /**
134
+ * This is where all the page args can be set
135
+ */
136
  function setup(){}
137
 
138
  /**
142
  */
143
  function page_loaded() {}
144
 
145
+ /**
146
+ * This is where the css and js go
147
+ * Both wp_enqueue_*() and inline code can be added
148
+ */
149
  function page_head(){}
150
 
151
+ /**
152
+ * This is where the contextual help goes
153
+ * @return string
154
+ */
155
  function page_help(){}
156
 
157
+ /**
158
+ * A generic page header
159
+ */
160
  function page_header() {
161
  echo "<div class='wrap'>\n";
162
  screen_icon( $this->args['screen_icon'] );
163
+ echo html( 'h2', $this->args['page_title'] );
164
  }
165
 
166
+ /**
167
+ * This is where the page content goes
168
+ */
169
  abstract function page_content();
170
 
171
+ /**
172
+ * A generic page footer
173
+ */
174
  function page_footer() {
175
  echo "</div>\n";
176
  }
177
 
178
+ /**
179
+ * This is where the form data should be validated
180
+ *
181
+ * @param array $new_data
182
+ * @param array $old_data
183
+ *
184
+ * @return array
185
+ */
186
  function validate( $new_data, $old_data ) {
187
  return $new_data;
188
  }
189
 
190
+ /**
191
+ * Manually handle option saving ( use Settings API instead )
192
+ *
193
+ * @return bool
194
+ */
195
  function form_handler() {
196
+ if ( empty( $_POST['submit'] ) && empty( $_POST['action'] ) )
197
  return false;
198
 
199
  check_admin_referer( $this->nonce );
200
 
201
  if ( !isset($this->options) ) {
202
+ trigger_error( 'options handler not set', E_USER_WARNING );
203
  return false;
204
  }
205
 
212
  $this->options->set( $new_data );
213
 
214
  $this->admin_msg();
215
+
216
+ return true;
217
  }
218
 
219
+ /**
220
+ * Manually generate a standard admin notice ( use Settings API instead )
221
+ *
222
+ * @param string $msg
223
+ * @param string $class
224
+ */
225
+ function admin_msg( $msg = '', $class = 'updated' ) {
226
  if ( empty( $msg ) )
227
  $msg = __( 'Settings <strong>saved</strong>.', $this->textdomain );
228
 
233
  // ____________UTILITIES____________
234
 
235
 
236
+ /**
237
+ * Generates a form submit button
238
+ *
239
+ * @param string|array $value button text or array of arguments
240
+ * @param string $action
241
+ * @param string $class
242
+ *
243
+ * @return string
244
+ */
245
+ function submit_button( $value = '', $action = 'submit', $class = 'button' ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
+ $args = is_array( $value ) ? $value : compact( 'value', 'action', 'class' );
248
+ $args = wp_parse_args( $args, array(
249
+ 'value' => null,
250
+ 'action' => $action,
251
+ 'class' => $class,
252
+ ) );
253
 
254
+ return get_submit_button( $args['value'], $args['class'], $args['action'] );
255
  }
256
 
257
+ /**
258
+ * Mimics scbForms::form_wrap()
259
+ *
260
+ * $this->form_wrap( $content ); // generates a form with a default submit button
261
+ *
262
+ * $this->form_wrap( $content, false ); // generates a form with no submit button
263
+ *
264
+ * // the second argument is sent to submit_button()
265
+ * $this->form_wrap( $content, array(
266
+ * 'text' => 'Save changes',
267
+ * 'name' => 'action',
268
+ * ) );
269
+ *
270
+ * @see scbForms::form_wrap()
271
+ *
272
+ * @param string $content
273
+ * @param boolean|string|array $submit_button
274
+ *
275
+ * @return string
276
+ */
277
  function form_wrap( $content, $submit_button = true ) {
278
  if ( is_array( $submit_button ) ) {
279
  $content .= $this->submit_button( $submit_button );
281
  $content .= $this->submit_button();
282
  } elseif ( false !== strpos( $submit_button, '<input' ) ) {
283
  $content .= $submit_button;
284
+ } elseif ( false !== strpos( $submit_button, '<button' ) ) {
285
+ $content .= $submit_button;
286
  } elseif ( false !== $submit_button ) {
287
  $button_args = array_slice( func_get_args(), 1 );
288
+ $content .= call_user_func_array( array( $this, 'submit_button' ), $button_args );
289
  }
290
 
291
  return scbForms::form_wrap( $content, $this->nonce );
292
  }
293
 
294
+ /**
295
+ * Generates a table wrapped in a form
296
+ *
297
+ * @param array $rows
298
+ * @param array|boolean $formdata
299
+ *
300
+ * @return string
301
+ */
302
  function form_table( $rows, $formdata = false ) {
303
  $output = '';
304
  foreach ( $rows as $row )
309
  return $output;
310
  }
311
 
312
+ /**
313
+ * Wraps the given content in a <form><table>
314
+ *
315
+ * @param string $content
316
+ *
317
+ * @return string
318
+ */
319
  function form_table_wrap( $content ) {
320
  $output = $this->table_wrap( $content );
321
  $output = $this->form_wrap( $output );
323
  return $output;
324
  }
325
 
326
+ /**
327
+ * Generates a form table
328
+ *
329
+ * @param array $rows
330
+ * @param array|boolean $formdata
331
+ *
332
+ * @return string
333
+ */
334
  function table( $rows, $formdata = false ) {
335
  $output = '';
336
  foreach ( $rows as $row )
341
  return $output;
342
  }
343
 
344
+ /**
345
+ * Generates a table row
346
+ *
347
+ * @param array $args
348
+ * @param array|boolean $formdata
349
+ *
350
+ * @return string
351
+ */
352
  function table_row( $args, $formdata = false ) {
353
  return $this->row_wrap( $args['title'], $this->input( $args, $formdata ) );
354
  }
355
 
356
+ /**
357
+ * Mimic scbForms inheritance
358
+ *
359
+ * @see scbForms
360
+ *
361
+ * @param string $method
362
+ * @param array $args
363
+ *
364
+ * @return mixed
365
+ */
 
 
 
 
 
366
  function __call( $method, $args ) {
367
  if ( in_array( $method, array( 'input', 'form' ) ) ) {
368
  if ( empty( $args[1] ) && isset( $this->options ) )
375
  return call_user_func_array( array( 'scbForms', $method ), $args );
376
  }
377
 
378
+ /**
379
+ * Wraps a string in a <script> tag
380
+ *
381
+ * @param string $string
382
+ *
383
+ * @return string
384
+ */
385
  function js_wrap( $string ) {
386
+ return html( "script type='text/javascript'", $string );
387
  }
388
 
389
+ /**
390
+ * Wraps a string in a <style> tag
391
+ *
392
+ * @param string $string
393
+ *
394
+ * @return string
395
+ */
396
  function css_wrap( $string ) {
397
+ return html( "style type='text/css'", $string );
398
  }
399
 
400
 
401
  // ____________INTERNAL METHODS____________
402
 
403
 
404
+ /**
405
+ * Registers a page
406
+ */
407
  function page_init() {
 
408
 
409
+ if ( ! $this->args['toplevel'] ) {
410
+ $this->pagehook = add_submenu_page(
411
+ $this->args['parent'],
412
+ $this->args['page_title'],
413
+ $this->args['menu_title'],
414
+ $this->args['capability'],
415
+ $this->args['page_slug'],
416
+ array( $this, '_page_content_hook' )
417
+ );
418
  } else {
419
+ $func = 'add_' . $this->args['toplevel'] . '_page';
420
+ $this->pagehook = $func(
421
+ $this->args['page_title'],
422
+ $this->args['menu_title'],
423
+ $this->args['capability'],
424
+ $this->args['page_slug'],
425
+ null,
426
+ $this->args['icon_url'],
427
+ $this->args['position']
428
+ );
429
+
430
+ add_submenu_page(
431
+ $this->args['page_slug'],
432
+ $this->args['page_title'],
433
+ $this->args['submenu_title'],
434
+ $this->args['capability'],
435
+ $this->args['page_slug'],
436
+ array( $this, '_page_content_hook' )
437
+ );
438
  }
439
 
440
  if ( ! $this->pagehook )
442
 
443
  add_action( 'load-' . $this->pagehook, array( $this, 'page_loaded' ) );
444
 
 
 
 
 
 
445
  add_action( 'admin_print_styles-' . $this->pagehook, array( $this, 'page_head' ) );
446
  }
447
 
454
  trigger_error( 'Page title cannot be empty', E_USER_WARNING );
455
 
456
  $this->args = wp_parse_args( $this->args, array(
457
+ 'toplevel' => '',
458
+ 'position' => null,
459
+ 'icon_url' => '',
460
+ 'screen_icon' => '',
461
+ 'parent' => 'options-general.php',
462
+ 'capability' => 'manage_options',
463
+ 'menu_title' => $this->args['page_title'],
464
+ 'page_slug' => '',
465
+ 'nonce' => '',
466
+ 'action_link' => __( 'Settings', $this->textdomain ),
 
467
  'admin_action_priority' => 10,
468
  ) );
469
 
470
+ if ( empty( $this->args['submenu_title'] ) )
471
+ $this->args['submenu_title'] = $this->args['menu_title'];
472
+
473
  if ( empty( $this->args['page_slug'] ) )
474
  $this->args['page_slug'] = sanitize_title_with_dashes( $this->args['menu_title'] );
475
 
477
  $this->nonce = $this->args['page_slug'];
478
  }
479
 
480
+ /**
481
+ * @param string $help
482
+ * @param string|object $screen
483
+ *
484
+ * @return string
485
+ */
486
  function _contextual_help( $help, $screen ) {
487
  if ( is_object( $screen ) )
488
  $screen = $screen->id;
495
  return $help;
496
  }
497
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
  function _page_content_hook() {
499
  $this->form_handler();
500
 
503
  $this->page_footer();
504
  }
505
 
506
+ /**
507
+ * @param array $links
508
+ *
509
+ * @return array
510
+ */
511
  function _action_link( $links ) {
512
  $url = add_query_arg( 'page', $this->args['page_slug'], admin_url( $this->args['parent'] ) );
513
 
scb/BoxesPage.php CHANGED
@@ -166,35 +166,55 @@ abstract class scbBoxesPage extends scbAdminPage {
166
  ) );
167
 
168
  $registered = array();
 
169
  foreach ( $this->boxes as $box_args ) {
170
- foreach ( array( 'name', 'title', 'context', 'priority', 'args' ) as $i => $arg ) {
171
- if ( isset( $box_args[$i] ) )
172
- $$arg = $box_args[$i];
173
- }
 
 
 
 
 
174
 
175
- if ( empty( $title ) )
176
- $title = ucfirst( $name );
177
- if ( empty( $context ) )
178
- $context = 'normal';
179
- if ( empty( $priority ) )
180
- $priority = 'default';
181
- if ( empty( $args ) )
182
- $args = array();
183
-
184
- if ( isset( $registered[$name] ) ) {
185
- if ( empty( $args ) )
186
  trigger_error( "Duplicate box name: $name", E_USER_NOTICE );
 
187
 
188
  $name = $this->_increment( $name );
189
  } else {
190
- $registered[$name] = true;
191
  }
192
 
193
- add_meta_box( $name, $title, array( $this, '_intermediate_callback' ), $this->pagehook, $context, $priority, $args );
 
 
 
 
 
 
 
 
194
  }
195
  }
196
 
197
- // Make it so that $args is actually what's passed to the callback
 
 
 
 
 
 
 
 
 
 
 
 
198
  function _intermediate_callback( $_, $box ) {
199
  list( $name ) = explode( '-', $box['id'] );
200
 
166
  ) );
167
 
168
  $registered = array();
169
+
170
  foreach ( $this->boxes as $box_args ) {
171
+ $box_args = self::numeric_to_assoc( $box_args, array( 'name', 'title', 'context', 'priority', 'args' ) );
172
+
173
+ $defaults = array(
174
+ 'title' => ucfirst( $box_args['name'] ),
175
+ 'context' => 'normal',
176
+ 'priority' => 'default',
177
+ 'args' => array()
178
+ );
179
+ $box_args = array_merge( $defaults, $box_args );
180
 
181
+ $name = $box_args['name'];
182
+
183
+ if ( isset( $registered[ $name ] ) ) {
184
+ if ( empty( $box_args['args'] ) ) {
 
 
 
 
 
 
 
185
  trigger_error( "Duplicate box name: $name", E_USER_NOTICE );
186
+ }
187
 
188
  $name = $this->_increment( $name );
189
  } else {
190
+ $registered[ $name ] = true;
191
  }
192
 
193
+ add_meta_box(
194
+ $name,
195
+ $box_args['title'],
196
+ array( $this, '_intermediate_callback' ),
197
+ $this->pagehook,
198
+ $box_args['context'],
199
+ $box_args['priority'],
200
+ $box_args['args']
201
+ );
202
  }
203
  }
204
 
205
+ private static function numeric_to_assoc( $argv, $keys ) {
206
+ $args = array();
207
+
208
+ foreach ( $keys as $i => $key ) {
209
+ if ( isset( $argv[ $i ] ) )
210
+ $args[ $key ] = $argv[ $i ];
211
+ }
212
+
213
+ return $args;
214
+ }
215
+
216
+ // Since we don't pass an object to do_meta_boxes(),
217
+ // pass $box['args'] directly to each method.
218
  function _intermediate_callback( $_, $box ) {
219
  list( $name ) = explode( '-', $box['id'] );
220
 
scb/Cron.php CHANGED
@@ -1,7 +1,8 @@
1
  <?php
2
 
3
- // wp-cron job container
4
-
 
5
  class scbCron {
6
  protected $schedule;
7
  protected $interval;
@@ -13,41 +14,40 @@ class scbCron {
13
  /**
14
  * Create a new cron job
15
  *
16
- * @param string Reference to main plugin file
17
- * @param array List of args:
18
- string $action OR callback $callback
19
- string $schedule OR number $interval
20
- array $callback_args (optional)
21
  */
22
  function __construct( $file = false, $args ) {
23
- extract( $args, EXTR_SKIP );
24
 
25
  // Set time & schedule
26
- if ( isset( $time ) )
27
- $this->time = $time;
28
-
29
- if ( isset( $interval ) ) {
30
- $this->schedule = $interval . 'secs';
31
- $this->interval = $interval;
32
- } elseif ( isset( $schedule ) ) {
33
- $this->schedule = $schedule;
34
  }
35
 
36
  // Set hook
37
- if ( isset( $action ) ) {
38
- $this->hook = $action;
39
- } elseif ( isset( $callback ) ) {
40
- $this->hook = self::_callback_to_string( $callback );
41
- add_action( $this->hook, $callback );
42
  } elseif ( method_exists( $this, 'callback' ) ) {
43
  $this->hook = self::_callback_to_string( array( $this, 'callback' ) );
44
- add_action( $this->hook, $callback );
45
  } else {
46
  trigger_error( '$action OR $callback not set', E_USER_WARNING );
47
  }
48
 
49
- if ( isset( $callback_args ) )
50
- $this->callback_args = (array) $callback_args;
51
 
52
  if ( $file && $this->schedule ) {
53
  scbUtil::add_activation_hook( $file, array( $this, 'reset' ) );
@@ -57,23 +57,23 @@ class scbCron {
57
  add_filter( 'cron_schedules', array( $this, '_add_timing' ) );
58
  }
59
 
60
- /* Change the interval of the cron job
 
61
  *
62
- * @param array List of args:
63
- string $schedule OR number $interval
64
- timestamp $time ( optional )
65
  */
66
  function reschedule( $args ) {
67
- extract( $args );
68
 
69
- if ( $schedule && $this->schedule != $schedule ) {
70
- $this->schedule = $schedule;
71
- } elseif ( $interval && $this->interval != $interval ) {
72
- $this->schedule = $interval . 'secs';
73
- $this->interval = $interval;
74
  }
75
 
76
- $this->time = $time;
77
 
78
  $this->reset();
79
  }
@@ -107,8 +107,8 @@ class scbCron {
107
 
108
  /**
109
  * Execute the job with a given delay
110
- * @param int $delay in seconds
111
- * @param array $args List of arguments to pass to the callback
112
  */
113
  function do_once( $delay = 0, $args = null ) {
114
  if ( is_null( $args ) )
@@ -121,13 +121,19 @@ class scbCron {
121
 
122
  //_____INTERNAL METHODS_____
123
 
124
-
 
 
 
 
125
  function _add_timing( $schedules ) {
126
  if ( isset( $schedules[$this->schedule] ) )
127
  return $schedules;
128
 
129
- $schedules[$this->schedule] = array( 'interval' => $this->interval,
130
- 'display' => $this->interval . ' seconds' );
 
 
131
 
132
  return $schedules;
133
  }
@@ -139,6 +145,9 @@ class scbCron {
139
  wp_schedule_event( $this->time, $this->schedule, $this->hook, $this->callback_args );
140
  }
141
 
 
 
 
142
  protected static function really_clear_scheduled_hook( $name ) {
143
  $crons = _get_cron_array();
144
 
@@ -147,13 +156,18 @@ class scbCron {
147
  if ( $hook == $name )
148
  unset( $crons[$timestamp][$hook] );
149
 
150
- if ( empty( $hooks ) )
151
  unset( $crons[$timestamp] );
152
  }
153
 
154
  _set_cron_array( $crons );
155
  }
156
 
 
 
 
 
 
157
  protected static function _callback_to_string( $callback ) {
158
  if ( ! is_array( $callback ) )
159
  $str = $callback;
1
  <?php
2
 
3
+ /**
4
+ * wp-cron job container
5
+ */
6
  class scbCron {
7
  protected $schedule;
8
  protected $interval;
14
  /**
15
  * Create a new cron job
16
  *
17
+ * @param string|bool $file Reference to main plugin file
18
+ * @param array $args List of args:
19
+ * string $action OR callback $callback
20
+ * string $schedule OR number $interval
21
+ * array $callback_args (optional)
22
  */
23
  function __construct( $file = false, $args ) {
 
24
 
25
  // Set time & schedule
26
+ if ( isset( $args['time'] ) )
27
+ $this->time = $args['time'];
28
+
29
+ if ( isset( $args['interval'] ) ) {
30
+ $this->schedule = $args['interval'] . 'secs';
31
+ $this->interval = $args['interval'];
32
+ } elseif ( isset( $args['schedule'] ) ) {
33
+ $this->schedule = $args['schedule'];
34
  }
35
 
36
  // Set hook
37
+ if ( isset( $args['action'] ) ) {
38
+ $this->hook = $args['action'];
39
+ } elseif ( isset( $args['callback'] ) ) {
40
+ $this->hook = self::_callback_to_string( $args['callback'] );
41
+ add_action( $this->hook, $args['callback'] );
42
  } elseif ( method_exists( $this, 'callback' ) ) {
43
  $this->hook = self::_callback_to_string( array( $this, 'callback' ) );
44
+ add_action( $this->hook, $args['callback'] );
45
  } else {
46
  trigger_error( '$action OR $callback not set', E_USER_WARNING );
47
  }
48
 
49
+ if ( isset( $args['callback_args'] ) )
50
+ $this->callback_args = (array) $args['callback_args'];
51
 
52
  if ( $file && $this->schedule ) {
53
  scbUtil::add_activation_hook( $file, array( $this, 'reset' ) );
57
  add_filter( 'cron_schedules', array( $this, '_add_timing' ) );
58
  }
59
 
60
+ /**
61
+ * Change the interval of the cron job
62
  *
63
+ * @param array $args List of args:
64
+ * string $schedule OR number $interval
65
+ * timestamp $time ( optional )
66
  */
67
  function reschedule( $args ) {
 
68
 
69
+ if ( $args['schedule'] && $this->schedule != $args['schedule'] ) {
70
+ $this->schedule = $args['schedule'];
71
+ } elseif ( $args['interval'] && $this->interval != $args['interval'] ) {
72
+ $this->schedule = $args['interval'] . 'secs';
73
+ $this->interval = $args['interval'];
74
  }
75
 
76
+ $this->time = $args['time'];
77
 
78
  $this->reset();
79
  }
107
 
108
  /**
109
  * Execute the job with a given delay
110
+ * @param int $delay in seconds
111
+ * @param array $args List of arguments to pass to the callback
112
  */
113
  function do_once( $delay = 0, $args = null ) {
114
  if ( is_null( $args ) )
121
 
122
  //_____INTERNAL METHODS_____
123
 
124
+ /**
125
+ * @param array $schedules
126
+ *
127
+ * @return array
128
+ */
129
  function _add_timing( $schedules ) {
130
  if ( isset( $schedules[$this->schedule] ) )
131
  return $schedules;
132
 
133
+ $schedules[$this->schedule] = array(
134
+ 'interval' => $this->interval,
135
+ 'display' => $this->interval . ' seconds',
136
+ );
137
 
138
  return $schedules;
139
  }
145
  wp_schedule_event( $this->time, $this->schedule, $this->hook, $this->callback_args );
146
  }
147
 
148
+ /**
149
+ * @param string $name
150
+ */
151
  protected static function really_clear_scheduled_hook( $name ) {
152
  $crons = _get_cron_array();
153
 
156
  if ( $hook == $name )
157
  unset( $crons[$timestamp][$hook] );
158
 
159
+ if ( empty( $crons[$timestamp] ) )
160
  unset( $crons[$timestamp] );
161
  }
162
 
163
  _set_cron_array( $crons );
164
  }
165
 
166
+ /**
167
+ * @param callback $callback
168
+ *
169
+ * @return string
170
+ */
171
  protected static function _callback_to_string( $callback ) {
172
  if ( ! is_array( $callback ) )
173
  $str = $callback;
scb/Forms.php CHANGED
@@ -1,88 +1,45 @@
1
  <?php
2
 
3
- // Data-aware form generator
4
-
 
5
  class scbForms {
6
 
7
  const TOKEN = '%input%';
8
 
9
- protected static $cur_name;
10
-
11
- function input_with_value( $args, $value ) {
12
- if ( is_null( $value ) && isset( $args['default'] ) )
13
- $value = $args['default'];
14
-
15
- if ( !is_null( $value ) ) {
16
- switch ( $args['type'] ) {
17
- case 'select':
18
- case 'radio':
19
- $args['selected'] = $value;
20
- break;
21
- case 'checkbox':
22
- if ( is_array( $value ) )
23
- $args['checked'] = $value;
24
- else
25
- $args['checked'] = ( $value || ( isset( $args['value'] ) && $value == $args['value'] ) );
26
- break;
27
- default:
28
- $args['value'] = $value;
29
- }
30
- }
31
 
32
- return self::input( $args );
33
  }
34
 
35
- static function input( $args, $formdata = false ) {
36
- if ( false !== $formdata ) {
37
- $form = new scbForm( $formdata );
38
- return $form->input( $args );
39
- }
40
-
41
- if ( empty( $args['name'] ) ) {
42
- return trigger_error( 'Empty name', E_USER_WARNING );
43
- }
44
-
45
- $args = wp_parse_args( $args, array(
46
- 'desc' => '',
47
- 'desc_pos' => 'after',
48
- 'wrap' => self::TOKEN,
49
- 'wrap_each' => self::TOKEN,
50
- ) );
51
-
52
- if ( isset( $args['value'] ) && is_array( $args['value'] ) ) {
53
- $args['values'] = $args['value'];
54
- unset( $args['value'] );
55
- }
56
-
57
- if ( isset( $args['extra'] ) && !is_array( $args['extra'] ) )
58
- $args['extra'] = shortcode_parse_atts( $args['extra'] );
59
-
60
- self::$cur_name = self::get_name( $args['name'] );
61
-
62
- switch ( $args['type'] ) {
63
- case 'select':
64
- case 'radio':
65
- $input = self::_single_choice( $args );
66
- break;
67
- case 'checkbox':
68
- if ( isset( $args['values'] ) )
69
- $input = self::_multiple_choice( $args );
70
- else
71
- $input = self::_checkbox( $args );
72
- break;
73
- default:
74
- $input = self::_input( $args );
75
- }
76
 
77
- return str_replace( self::TOKEN, $input, $args['wrap'] );
78
  }
79
 
80
-
81
- // ____________UTILITIES____________
82
-
83
-
84
- // Generates a table wrapped in a form
85
- static function form_table( $rows, $formdata = NULL ) {
 
 
 
86
  $output = '';
87
  foreach ( $rows as $row )
88
  $output .= self::table_row( $row, $formdata );
@@ -92,8 +49,16 @@ class scbForms {
92
  return $output;
93
  }
94
 
95
- // Generates a form
96
- static function form( $inputs, $formdata = NULL, $nonce ) {
 
 
 
 
 
 
 
 
97
  $output = '';
98
  foreach ( $inputs as $input )
99
  $output .= self::input( $input, $formdata );
@@ -103,8 +68,15 @@ class scbForms {
103
  return $output;
104
  }
105
 
106
- // Generates a table
107
- static function table( $rows, $formdata = NULL ) {
 
 
 
 
 
 
 
108
  $output = '';
109
  foreach ( $rows as $row )
110
  $output .= self::table_row( $row, $formdata );
@@ -114,244 +86,70 @@ class scbForms {
114
  return $output;
115
  }
116
 
117
- // Generates a table row
118
- static function table_row( $args, $formdata = NULL ) {
 
 
 
 
 
 
 
119
  return self::row_wrap( $args['title'], self::input( $args, $formdata ) );
120
  }
121
 
122
 
123
  // ____________WRAPPERS____________
124
 
125
-
126
- // Wraps the given content in a <form><table>
 
 
 
 
127
  static function form_table_wrap( $content, $nonce = 'update_options' ) {
128
- $output = self::table_wrap( $content );
129
- $output = self::form_wrap( $output, $nonce );
130
-
131
- return $output;
132
  }
133
 
134
- // Wraps the given content in a <form> tag
 
 
 
 
 
135
  static function form_wrap( $content, $nonce = 'update_options' ) {
136
- $output = "\n<form method='post' action=''>\n";
137
- $output .= $content;
138
- $output .= wp_nonce_field( $action = $nonce, $name = "_wpnonce", $referer = true , $echo = false );
139
- $output .= "\n</form>\n";
140
-
141
- return $output;
142
  }
143
 
144
- // Wraps the given content in a <table>
 
 
 
 
145
  static function table_wrap( $content ) {
146
- $output = "\n<table class='form-table'>\n" . $content . "\n</table>\n";
147
-
148
- return $output;
149
  }
150
 
151
- // Wraps the given content in a <tr><td>
 
 
 
 
 
152
  static function row_wrap( $title, $content ) {
153
- return "\n<tr>\n\t<th scope='row'>" . $title . "</th>\n\t<td>\n\t\t" . $content . "\t</td>\n\n</tr>";
 
 
 
154
  }
155
 
156
 
157
  // ____________PRIVATE METHODS____________
158
 
159
 
160
- private static function _single_choice( $args ) {
161
- $args = wp_parse_args( $args, array(
162
- 'numeric' => false, // use numeric array instead of associative
163
- 'selected' => array( 'foo' ), // hack to make default blank
164
- ) );
165
-
166
- self::_expand_values( $args );
167
-
168
- if ( 'select' == $args['type'] )
169
- return self::_select( $args );
170
- else
171
- return self::_radio( $args );
172
- }
173
-
174
- private static function _multiple_choice( $args ) {
175
- $args = wp_parse_args( $args, array(
176
- 'numeric' => false, // use numeric array instead of associative
177
- 'checked' => null,
178
- ) );
179
-
180
- self::$cur_name .= '[]';
181
-
182
- self::_expand_values( $args );
183
-
184
- extract( $args );
185
-
186
- if ( !is_array( $checked ) )
187
- $checked = array();
188
-
189
- $opts = '';
190
- foreach ( $values as $value => $title ) {
191
- $single_input = self::_checkbox( array(
192
- 'type' => 'checkbox',
193
- 'value' => $value,
194
- 'checked' => in_array( $value, $checked ),
195
- 'desc' => $title,
196
- 'desc_pos' => 'after'
197
- ) );
198
-
199
- $opts .= str_replace( self::TOKEN, $single_input, $args['wrap_each'] );
200
- }
201
-
202
- return self::add_desc( $opts, $desc, $desc_pos );
203
- }
204
-
205
- private static function _expand_values( &$args ) {
206
- $values =& $args['values'];
207
-
208
- if ( !empty( $values ) && !self::is_associative( $values ) ) {
209
- if ( is_array( $args['desc'] ) ) {
210
- $values = array_combine( $values, $args['desc'] ); // back-compat
211
- $args['desc'] = false;
212
- } elseif ( !isset( $args['numeric'] ) || !$args['numeric'] ) {
213
- $values = array_combine( $values, $values );
214
- }
215
- }
216
- }
217
-
218
- private static function _radio( $args ) {
219
- extract( $args );
220
-
221
- if ( array( 'foo' ) == $selected ) {
222
- // radio buttons should always have one option selected
223
- $selected = key( $values );
224
- }
225
-
226
- $opts = '';
227
- foreach ( $values as $value => $title ) {
228
- $single_input = self::_checkbox( array(
229
- 'type' => 'radio',
230
- 'value' => $value,
231
- 'checked' => ( (string) $value == (string) $selected ),
232
- 'desc' => $title,
233
- 'desc_pos' => 'after'
234
- ) );
235
-
236
- $opts .= str_replace( self::TOKEN, $single_input, $args['wrap_each'] );
237
- }
238
-
239
- return self::add_desc( $opts, $desc, $desc_pos );
240
- }
241
-
242
- private static function _select( $args ) {
243
- extract( wp_parse_args( $args, array(
244
- 'text' => false,
245
- 'extra' => array()
246
- ) ) );
247
-
248
- $options = array();
249
-
250
- if ( false !== $text ) {
251
- $options[] = array(
252
- 'value' => '',
253
- 'selected' => ( $selected == array( 'foo' ) ),
254
- 'title' => $text
255
- );
256
- }
257
-
258
- foreach ( $values as $value => $title ) {
259
- $options[] = array(
260
- 'value' => $value,
261
- 'selected' => ( (string) $value == (string) $selected ),
262
- 'title' => $title
263
- );
264
- }
265
-
266
- $opts = '';
267
- foreach ( $options as $option ) {
268
- extract( $option );
269
-
270
- $opts .= html( 'option', compact( 'value', 'selected' ), $title );
271
- }
272
-
273
- $extra['name'] = self::$cur_name;
274
-
275
- $input = html( 'select', $extra, $opts );
276
-
277
- return self::add_label( $input, $desc, $desc_pos );
278
- }
279
-
280
- // Handle args for a single checkbox or radio input
281
- private static function _checkbox( $args ) {
282
- $args = wp_parse_args( $args, array(
283
- 'value' => true,
284
- 'desc' => NULL,
285
- 'checked' => false,
286
- 'extra' => array(),
287
- ) );
288
-
289
- foreach ( $args as $key => &$val )
290
- $$key = &$val;
291
- unset( $val );
292
-
293
- $extra['checked'] = $checked;
294
-
295
- if ( is_null( $desc ) && !is_bool( $value ) )
296
- $desc = str_replace( '[]', '', $value );
297
-
298
- return self::_input_gen( $args );
299
- }
300
-
301
- // Handle args for text inputs
302
- private static function _input( $args ) {
303
- $args = wp_parse_args( $args, array(
304
- 'value' => '',
305
- 'desc_pos' => 'after',
306
- 'extra' => array( 'class' => 'regular-text' ),
307
- ) );
308
-
309
- foreach ( $args as $key => &$val )
310
- $$key = &$val;
311
- unset( $val );
312
-
313
- if ( !isset( $extra['id'] ) && !is_array( $name ) && false === strpos( $name, '[' ) )
314
- $extra['id'] = $name;
315
-
316
- return self::_input_gen( $args );
317
- }
318
-
319
- // Generate html with the final args
320
- private static function _input_gen( $args ) {
321
- extract( wp_parse_args( $args, array(
322
- 'value' => NULL,
323
- 'desc' => NULL,
324
- 'extra' => array()
325
- ) ) );
326
-
327
- $extra['name'] = self::$cur_name;
328
-
329
- if ( 'textarea' == $type ) {
330
- $input = html( 'textarea', $extra, esc_textarea( $value ) );
331
- } else {
332
- $extra['value'] = $value;
333
- $extra['type'] = $type;
334
- $input = html( 'input', $extra );
335
- }
336
-
337
- return self::add_label( $input, $desc, $desc_pos );
338
- }
339
-
340
- private static function add_label( $input, $desc, $desc_pos ) {
341
- return html( 'label', self::add_desc( $input, $desc, $desc_pos ) ) . "\n";
342
- }
343
-
344
- private static function add_desc( $input, $desc, $desc_pos ) {
345
- if ( empty( $desc ) )
346
- return $input;
347
-
348
- if ( 'before' == $desc_pos )
349
- return $desc . ' ' . $input;
350
- else
351
- return $input . ' ' . $desc;
352
- }
353
-
354
-
355
  // Utilities
356
 
357
 
@@ -377,9 +175,9 @@ class scbForms {
377
  /**
378
  * Traverses the formdata and retrieves the correct value.
379
  *
380
- * @param array|string $name The name of the value
381
- * @param array $value The data that will be traversed
382
- * @param mixed $fallback The value returned when the key is not found
383
  *
384
  * @return mixed
385
  */
@@ -395,57 +193,61 @@ class scbForms {
395
  }
396
 
397
  /**
398
- * Given a list of fields, extract the appropriate POST data and return it.
399
  *
400
- * @param array $fields List of args that would be sent to scbForms::input()
401
- * @param array $to_update Existing data to update
 
402
  *
403
  * @return array
404
  */
405
- static function validate_post_data( $fields, $to_update = array() ) {
406
- foreach ( $fields as $field ) {
407
- $value = scbForms::get_value( $field['name'], $_POST );
408
-
409
- $value = stripslashes_deep( $value );
410
 
411
- switch ( $field['type'] ) {
412
- case 'checkbox':
413
- if ( isset( $field['values'] ) && is_array( $field['values'] ) )
414
- $value = array_intersect( $field['values'], (array) $value );
415
- else
416
- $value = (bool) $value;
417
 
418
- break;
419
- case 'radio':
420
- case 'select':
421
- self::_expand_values( $field );
422
 
423
- if ( !isset( $field['values'][ $value ] ) )
424
- continue 2;
425
- }
426
 
427
- self::set_value( $to_update, $field['name'], $value );
 
428
  }
429
 
430
  return $to_update;
431
  }
432
 
 
 
 
 
 
 
 
 
 
 
 
433
  static function input_from_meta( $args, $object_id, $meta_type = 'post' ) {
434
  $single = ( 'checkbox' != $args['type'] );
435
 
436
  $key = (array) $args['name'];
437
  $key = end( $key );
438
 
439
- $value = get_metadata( $meta_type, $object_id, $key );
440
-
441
- if ( empty( $value ) )
442
- $value = null;
443
- elseif( $single )
444
- $value = reset( $value );
445
 
446
  return self::input_with_value( $args, $value );
447
  }
448
 
 
 
 
 
 
 
449
  static function update_meta( $fields, $data, $object_id, $meta_type = 'post' ) {
450
  foreach ( $fields as $field_args ) {
451
  $key = $field_args['name'];
@@ -461,7 +263,7 @@ class scbForms {
461
  foreach ( array_diff( $old_values, $new_values ) as $value )
462
  delete_metadata( $meta_type, $object_id, $key, $value );
463
  } else {
464
- $value = $data[$key];
465
 
466
  if ( '' === $value )
467
  delete_metadata( $meta_type, $object_id, $key );
@@ -471,6 +273,11 @@ class scbForms {
471
  }
472
  }
473
 
 
 
 
 
 
474
  private static function set_value( &$arr, $name, $value ) {
475
  $name = (array) $name;
476
 
@@ -487,11 +294,6 @@ class scbForms {
487
 
488
  $arr[ $final_key ] = $value;
489
  }
490
-
491
- private static function is_associative( $array ) {
492
- $keys = array_keys( $array );
493
- return array_keys( $keys ) !== $keys;
494
- }
495
  }
496
 
497
 
@@ -499,9 +301,13 @@ class scbForms {
499
  * A wrapper for scbForms, containing the formdata
500
  */
501
  class scbForm {
502
- protected $data = array();
503
  protected $prefix = array();
504
 
 
 
 
 
505
  function __construct( $data, $prefix = false ) {
506
  if ( is_array( $data ) )
507
  $this->data = $data;
@@ -510,6 +316,11 @@ class scbForm {
510
  $this->prefix = (array) $prefix;
511
  }
512
 
 
 
 
 
 
513
  function traverse_to( $path ) {
514
  $data = scbForms::get_value( $path, $this->data );
515
 
@@ -518,6 +329,11 @@ class scbForm {
518
  return new scbForm( $data, $prefix );
519
  }
520
 
 
 
 
 
 
521
  function input( $args ) {
522
  $value = scbForms::get_value( $args['name'], $this->data );
523
 
@@ -529,3 +345,585 @@ class scbForm {
529
  }
530
  }
531
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <?php
2
 
3
+ /**
4
+ * Data-aware form generator
5
+ */
6
  class scbForms {
7
 
8
  const TOKEN = '%input%';
9
 
10
+ /**
11
+ * @param array|scbFormField_I $args
12
+ * @param mixed $value
13
+ *
14
+ * @return string
15
+ */
16
+ static function input_with_value( $args, $value ) {
17
+ $field = scbFormField::create( $args );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ return $field->render( $value );
20
  }
21
 
22
+ /**
23
+ * @param array|scbFormField_I $args
24
+ * @param array $formdata
25
+ *
26
+ * @return string
27
+ */
28
+ static function input( $args, $formdata = null ) {
29
+ $field = scbFormField::create( $args );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ return $field->render( scbForms::get_value( $args['name'], $formdata ) );
32
  }
33
 
34
+ /**
35
+ * Generates a table wrapped in a form
36
+ *
37
+ * @param array $rows
38
+ * @param array $formdata
39
+ *
40
+ * @return string
41
+ */
42
+ static function form_table( $rows, $formdata = null ) {
43
  $output = '';
44
  foreach ( $rows as $row )
45
  $output .= self::table_row( $row, $formdata );
49
  return $output;
50
  }
51
 
52
+ /**
53
+ * Generates a form
54
+ *
55
+ * @param array $inputs
56
+ * @param array $formdata
57
+ * @param string $nonce
58
+ *
59
+ * @return string
60
+ */
61
+ static function form( $inputs, $formdata = null, $nonce ) {
62
  $output = '';
63
  foreach ( $inputs as $input )
64
  $output .= self::input( $input, $formdata );
68
  return $output;
69
  }
70
 
71
+ /**
72
+ * Generates a table
73
+ *
74
+ * @param array $rows
75
+ * @param array $formdata
76
+ *
77
+ * @return string
78
+ */
79
+ static function table( $rows, $formdata = null ) {
80
  $output = '';
81
  foreach ( $rows as $row )
82
  $output .= self::table_row( $row, $formdata );
86
  return $output;
87
  }
88
 
89
+ /**
90
+ * Generates a table row
91
+ *
92
+ * @param array $args
93
+ * @param array $formdata
94
+ *
95
+ * @return string
96
+ */
97
+ static function table_row( $args, $formdata = null ) {
98
  return self::row_wrap( $args['title'], self::input( $args, $formdata ) );
99
  }
100
 
101
 
102
  // ____________WRAPPERS____________
103
 
104
+ /**
105
+ * @param string $content
106
+ * @param string $nonce
107
+ *
108
+ * @return string
109
+ */
110
  static function form_table_wrap( $content, $nonce = 'update_options' ) {
111
+ return self::form_wrap( self::table_wrap( $content ), $nonce );
 
 
 
112
  }
113
 
114
+ /**
115
+ * @param string $content
116
+ * @param string $nonce
117
+ *
118
+ * @return string
119
+ */
120
  static function form_wrap( $content, $nonce = 'update_options' ) {
121
+ return html( "form method='post' action=''",
122
+ $content,
123
+ wp_nonce_field( $nonce, '_wpnonce', $referer = true, $echo = false )
124
+ );
 
 
125
  }
126
 
127
+ /**
128
+ * @param string $content
129
+ *
130
+ * @return string
131
+ */
132
  static function table_wrap( $content ) {
133
+ return html( "table class='form-table'", $content );
 
 
134
  }
135
 
136
+ /**
137
+ * @param string $title
138
+ * @param string $content
139
+ *
140
+ * @return string
141
+ */
142
  static function row_wrap( $title, $content ) {
143
+ return html( 'tr',
144
+ html( "th scope='row'", $title ),
145
+ html( 'td', $content )
146
+ );
147
  }
148
 
149
 
150
  // ____________PRIVATE METHODS____________
151
 
152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  // Utilities
154
 
155
 
175
  /**
176
  * Traverses the formdata and retrieves the correct value.
177
  *
178
+ * @param string $name The name of the value
179
+ * @param array $value The data that will be traversed
180
+ * @param mixed $fallback The value returned when the key is not found
181
  *
182
  * @return mixed
183
  */
193
  }
194
 
195
  /**
196
+ * Given a list of fields, validate some data.
197
  *
198
+ * @param array $fields List of args that would be sent to scbForms::input()
199
+ * @param array $data The data to validate. Defaults to $_POST
200
+ * @param array $to_update Existing data to populate. Necessary for nested values
201
  *
202
  * @return array
203
  */
204
+ static function validate_post_data( $fields, $data = null, $to_update = array() ) {
205
+ if ( null === $data ) {
206
+ $data = stripslashes_deep( $_POST );
207
+ }
 
208
 
209
+ foreach ( $fields as $field ) {
210
+ $value = scbForms::get_value( $field['name'], $data );
 
 
 
 
211
 
212
+ $fieldObj = scbFormField::create( $field );
 
 
 
213
 
214
+ $value = $fieldObj->validate( $value );
 
 
215
 
216
+ if ( null !== $value )
217
+ self::set_value( $to_update, $field['name'], $value );
218
  }
219
 
220
  return $to_update;
221
  }
222
 
223
+ /**
224
+ * For multiple-choice fields, we can never distinguish between "never been set" and "set to none".
225
+ * For single-choice fields, we can't distinguish either, because of how self::update_meta() works.
226
+ * Therefore, the 'default' parameter is always ignored.
227
+ *
228
+ * @param array $args Field arguments.
229
+ * @param int $object_id The object ID the metadata is attached to
230
+ * @param string $meta_type
231
+ *
232
+ * @return string
233
+ */
234
  static function input_from_meta( $args, $object_id, $meta_type = 'post' ) {
235
  $single = ( 'checkbox' != $args['type'] );
236
 
237
  $key = (array) $args['name'];
238
  $key = end( $key );
239
 
240
+ $value = get_metadata( $meta_type, $object_id, $key, $single );
 
 
 
 
 
241
 
242
  return self::input_with_value( $args, $value );
243
  }
244
 
245
+ /**
246
+ * @param array $fields
247
+ * @param array $data
248
+ * @param int $object_id
249
+ * @param string $meta_type
250
+ */
251
  static function update_meta( $fields, $data, $object_id, $meta_type = 'post' ) {
252
  foreach ( $fields as $field_args ) {
253
  $key = $field_args['name'];
263
  foreach ( array_diff( $old_values, $new_values ) as $value )
264
  delete_metadata( $meta_type, $object_id, $key, $value );
265
  } else {
266
+ $value = isset( $data[$key] ) ? $data[$key] : '';
267
 
268
  if ( '' === $value )
269
  delete_metadata( $meta_type, $object_id, $key );
273
  }
274
  }
275
 
276
+ /**
277
+ * @param array $arr
278
+ * @param string $name
279
+ * @param mixed $value
280
+ */
281
  private static function set_value( &$arr, $name, $value ) {
282
  $name = (array) $name;
283
 
294
 
295
  $arr[ $final_key ] = $value;
296
  }
 
 
 
 
 
297
  }
298
 
299
 
301
  * A wrapper for scbForms, containing the formdata
302
  */
303
  class scbForm {
304
+ protected $data = array();
305
  protected $prefix = array();
306
 
307
+ /**
308
+ * @param array $data
309
+ * @param string|boolean $prefix
310
+ */
311
  function __construct( $data, $prefix = false ) {
312
  if ( is_array( $data ) )
313
  $this->data = $data;
316
  $this->prefix = (array) $prefix;
317
  }
318
 
319
+ /**
320
+ * @param string $path
321
+ *
322
+ * @return scbForm
323
+ */
324
  function traverse_to( $path ) {
325
  $data = scbForms::get_value( $path, $this->data );
326
 
329
  return new scbForm( $data, $prefix );
330
  }
331
 
332
+ /**
333
+ * @param array $args
334
+ *
335
+ * @return string
336
+ */
337
  function input( $args ) {
338
  $value = scbForms::get_value( $args['name'], $this->data );
339
 
345
  }
346
  }
347
 
348
+ /**
349
+ * Interface for form fields.
350
+ */
351
+ interface scbFormField_I {
352
+
353
+ /**
354
+ * Generate the corresponding HTML for a field
355
+ *
356
+ * @param mixed $value The value to use
357
+ *
358
+ * @return string
359
+ */
360
+ function render( $value = null );
361
+
362
+ /**
363
+ * Validates a value against a field.
364
+ *
365
+ * @param mixed $value The value to check
366
+ *
367
+ * @return mixed null if the validation failed, sanitized value otherwise.
368
+ */
369
+ function validate( $value );
370
+ }
371
+
372
+ /**
373
+ * Base class for form fields implementations.
374
+ */
375
+ abstract class scbFormField implements scbFormField_I {
376
+
377
+ protected $args;
378
+
379
+ /**
380
+ * @param array|scbFormField_I $args
381
+ *
382
+ * @return mixed false on failure or instance of form class
383
+ */
384
+ public static function create( $args ) {
385
+ if ( is_a( $args, 'scbFormField_I' ) )
386
+ return $args;
387
+
388
+ if ( empty( $args['name'] ) ) {
389
+ return trigger_error( 'Empty name', E_USER_WARNING );
390
+ }
391
+
392
+ if ( isset( $args['value'] ) && is_array( $args['value'] ) ) {
393
+ $args['choices'] = $args['value'];
394
+ unset( $args['value'] );
395
+ }
396
+
397
+ if ( isset( $args['values'] ) ) {
398
+ $args['choices'] = $args['values'];
399
+ unset( $args['values'] );
400
+ }
401
+
402
+ if ( isset( $args['extra'] ) && !is_array( $args['extra'] ) )
403
+ $args['extra'] = shortcode_parse_atts( $args['extra'] );
404
+
405
+ $args = wp_parse_args( $args, array(
406
+ 'desc' => '',
407
+ 'desc_pos' => 'after',
408
+ 'wrap' => scbForms::TOKEN,
409
+ 'wrap_each' => scbForms::TOKEN,
410
+ ) );
411
+
412
+ // depends on $args['desc']
413
+ if ( isset( $args['choices'] ) )
414
+ self::_expand_choices( $args );
415
+
416
+ switch ( $args['type'] ) {
417
+ case 'radio':
418
+ return new scbRadiosField( $args );
419
+ case 'select':
420
+ return new scbSelectField( $args );
421
+ case 'checkbox':
422
+ if ( isset( $args['choices'] ) )
423
+ return new scbMultipleChoiceField( $args );
424
+ else
425
+ return new scbSingleCheckboxField( $args );
426
+ case 'custom':
427
+ return new scbCustomField( $args );
428
+ default:
429
+ return new scbTextField( $args );
430
+ }
431
+ }
432
+
433
+ /**
434
+ * @param array $args
435
+ */
436
+ protected function __construct( $args ) {
437
+ $this->args = $args;
438
+ }
439
+
440
+ /**
441
+ * @param string $key
442
+ *
443
+ * @return mixed
444
+ */
445
+ public function __get( $key ) {
446
+ return $this->args[ $key ];
447
+ }
448
+
449
+ /**
450
+ * @param string $key
451
+ *
452
+ * @return bool
453
+ */
454
+ public function __isset( $key ) {
455
+ return isset( $this->args[ $key ] );
456
+ }
457
+
458
+ /**
459
+ * @param mixed $value
460
+ *
461
+ * @return string
462
+ */
463
+ public function render( $value = null ) {
464
+ if ( null === $value && isset( $this->default ) )
465
+ $value = $this->default;
466
+
467
+ $args = $this->args;
468
+
469
+ if ( null !== $value )
470
+ $this->_set_value( $args, $value );
471
+
472
+ $args['name'] = scbForms::get_name( $args['name'] );
473
+
474
+ return str_replace( scbForms::TOKEN, $this->_render( $args ), $this->wrap );
475
+ }
476
+
477
+ /**
478
+ * Mutate the field arguments so that the value passed is rendered.
479
+ *
480
+ * @param array $args
481
+ * @param mixed $value
482
+ */
483
+ abstract protected function _set_value( &$args, $value );
484
+
485
+ /**
486
+ * The actual rendering
487
+ *
488
+ * @param array $args
489
+ */
490
+ abstract protected function _render( $args );
491
+
492
+ /**
493
+ * Handle args for a single checkbox or radio input
494
+ *
495
+ * @param array $args
496
+ *
497
+ * @return string
498
+ */
499
+ protected static function _checkbox( $args ) {
500
+ $args = wp_parse_args( $args, array(
501
+ 'value' => true,
502
+ 'desc' => null,
503
+ 'checked' => false,
504
+ 'extra' => array(),
505
+ ) );
506
+
507
+ $args['extra']['checked'] = $args['checked'];
508
+
509
+ if ( is_null( $args['desc'] ) && ! is_bool( $args['value'] ) )
510
+ $args['desc'] = str_replace( '[]', '', $args['value'] );
511
+
512
+ return self::_input_gen( $args );
513
+ }
514
+
515
+ /**
516
+ * Generate html with the final args
517
+ *
518
+ * @param array $args
519
+ *
520
+ * @return string
521
+ */
522
+ protected static function _input_gen( $args ) {
523
+ $args = wp_parse_args( $args, array(
524
+ 'value' => null,
525
+ 'desc' => null,
526
+ 'extra' => array(),
527
+ ) );
528
+
529
+ $args['extra']['name'] = $args['name'];
530
+
531
+ if ( 'textarea' == $args['type'] ) {
532
+ $input = html( 'textarea', $args['extra'], esc_textarea( $args['value'] ) );
533
+ } else {
534
+ $args['extra']['value'] = $args['value'];
535
+ $args['extra']['type'] = $args['type'];
536
+ $input = html( 'input', $args['extra'] );
537
+ }
538
+
539
+ return self::add_label( $input, $args['desc'], $args['desc_pos'] );
540
+ }
541
+
542
+ /**
543
+ * @param string $input
544
+ * @param string $desc
545
+ * @param string $desc_pos
546
+ *
547
+ * @return string
548
+ */
549
+ protected static function add_label( $input, $desc, $desc_pos ) {
550
+ return html( 'label', self::add_desc( $input, $desc, $desc_pos ) ) . "\n";
551
+ }
552
+
553
+ /**
554
+ * @param string $input
555
+ * @param string $desc
556
+ * @param string $desc_pos
557
+ *
558
+ * @return string
559
+ */
560
+ protected static function add_desc( $input, $desc, $desc_pos ) {
561
+ if ( empty( $desc ) )
562
+ return $input;
563
+
564
+ if ( 'before' == $desc_pos )
565
+ return $desc . ' ' . $input;
566
+ else
567
+ return $input . ' ' . $desc;
568
+ }
569
+
570
+ /**
571
+ * @param array $args
572
+ */
573
+ private static function _expand_choices( &$args ) {
574
+ $choices =& $args['choices'];
575
+
576
+ if ( !empty( $choices ) && !self::is_associative( $choices ) ) {
577
+ if ( is_array( $args['desc'] ) ) {
578
+ $choices = array_combine( $choices, $args['desc'] ); // back-compat
579
+ $args['desc'] = false;
580
+ } elseif ( !isset( $args['numeric'] ) || !$args['numeric'] ) {
581
+ $choices = array_combine( $choices, $choices );
582
+ }
583
+ }
584
+ }
585
+
586
+ /**
587
+ * @param array $array
588
+ *
589
+ * @return bool
590
+ */
591
+ private static function is_associative( $array ) {
592
+ $keys = array_keys( $array );
593
+ return array_keys( $keys ) !== $keys;
594
+ }
595
+ }
596
+
597
+ /**
598
+ * Text form field.
599
+ */
600
+ class scbTextField extends scbFormField {
601
+
602
+ /**
603
+ * @param string $value
604
+ *
605
+ * @return string
606
+ */
607
+ public function validate( $value ) {
608
+ $sanitize = isset( $this->sanitize ) ? $this->sanitize : 'wp_filter_kses';
609
+
610
+ return call_user_func( $sanitize, $value, $this );
611
+ }
612
+
613
+ /**
614
+ * @param array $args
615
+ *
616
+ * @return string
617
+ */
618
+ protected function _render( $args ) {
619
+ $args = wp_parse_args( $args, array(
620
+ 'value' => '',
621
+ 'desc_pos' => 'after',
622
+ 'extra' => array( 'class' => 'regular-text' ),
623
+ ) );
624
+
625
+ if ( ! isset( $args['extra']['id'] ) && ! is_array( $args['name'] ) && false === strpos( $args['name'], '[' ) )
626
+ $args['extra']['id'] = $args['name'];
627
+
628
+ return scbFormField::_input_gen( $args );
629
+ }
630
+
631
+ /**
632
+ * @param array $args
633
+ * @param string $value
634
+ */
635
+ protected function _set_value( &$args, $value ) {
636
+ $args['value'] = $value;
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Base class for form fields with single choice.
642
+ */
643
+ abstract class scbSingleChoiceField extends scbFormField {
644
+
645
+ /**
646
+ * @param mixed $value
647
+ *
648
+ * @return mixed|null
649
+ */
650
+ public function validate( $value ) {
651
+ if ( isset( $this->choices[ $value ] ) )
652
+ return $value;
653
+
654
+ return null;
655
+ }
656
+
657
+ /**
658
+ * @param array $args
659
+ *
660
+ * @return string
661
+ */
662
+ protected function _render( $args ) {
663
+ $args = wp_parse_args( $args, array(
664
+ 'numeric' => false, // use numeric array instead of associative
665
+ ) );
666
+
667
+ if ( isset( $args['selected'] ) ) {
668
+ $args['selected'] = (string) $args['selected'];
669
+ } else {
670
+ $args['selected'] = array('foo'); // hack to make default blank
671
+ }
672
+
673
+ return $this->_render_specific( $args );
674
+ }
675
+
676
+ /**
677
+ * @param array $args
678
+ * @param mixed $value
679
+ */
680
+ protected function _set_value( &$args, $value ) {
681
+ $args['selected'] = $value;
682
+ }
683
+
684
+ /**
685
+ * @param array $args
686
+ *
687
+ * @return string
688
+ */
689
+ abstract protected function _render_specific( $args );
690
+ }
691
+
692
+ /**
693
+ * Dropdown field.
694
+ */
695
+ class scbSelectField extends scbSingleChoiceField {
696
+
697
+ /**
698
+ * @param array $args
699
+ *
700
+ * @return string
701
+ */
702
+ protected function _render_specific( $args ) {
703
+ $args = wp_parse_args( $args, array(
704
+ 'text' => false,
705
+ 'extra' => array(),
706
+ ) );
707
+
708
+ $options = array();
709
+
710
+ if ( false !== $args['text'] ) {
711
+ $options[] = array(
712
+ 'value' => '',
713
+ 'selected' => ( $args['selected'] === array( 'foo' ) ),
714
+ 'title' => $args['text'],
715
+ );
716
+ }
717
+
718
+ foreach ( $args['choices'] as $value => $title ) {
719
+ $value = (string) $value;
720
+
721
+ $options[] = array(
722
+ 'value' => $value,
723
+ 'selected' => ( $value == $args['selected'] ),
724
+ 'title' => $title,
725
+ );
726
+ }
727
+
728
+ $opts = '';
729
+ foreach ( $options as $option ) {
730
+ $opts .= html( 'option', array( 'value' => $option['value'], 'selected' => $option['selected'] ), $option['title'] );
731
+ }
732
+
733
+ $args['extra']['name'] = $args['name'];
734
+
735
+ $input = html( 'select', $args['extra'], $opts );
736
+
737
+ return scbFormField::add_label( $input, $args['desc'], $args['desc_pos'] );
738
+ }
739
+ }
740
+
741
+ /**
742
+ * Radio field.
743
+ */
744
+ class scbRadiosField extends scbSelectField {
745
+
746
+ /**
747
+ * @param array $args
748
+ *
749
+ * @return string
750
+ */
751
+ protected function _render_specific( $args ) {
752
+
753
+ if ( array( 'foo' ) === $args['selected'] ) {
754
+ // radio buttons should always have one option selected
755
+ $args['selected'] = key( $args['choices'] );
756
+ }
757
+
758
+ $opts = '';
759
+ foreach ( $args['choices'] as $value => $title ) {
760
+ $value = (string) $value;
761
+
762
+ $single_input = scbFormField::_checkbox( array(
763
+ 'name' => $args['name'],
764
+ 'type' => 'radio',
765
+ 'value' => $value,
766
+ 'checked' => ( $value == $args['selected'] ),
767
+ 'desc' => $title,
768
+ 'desc_pos' => 'after',
769
+ ) );
770
+
771
+ $opts .= str_replace( scbForms::TOKEN, $single_input, $args['wrap_each'] );
772
+ }
773
+
774
+ return scbFormField::add_desc( $opts, $args['desc'], $args['desc_pos'] );
775
+ }
776
+ }
777
+
778
+ /**
779
+ * Checkbox field with multiple choices.
780
+ */
781
+ class scbMultipleChoiceField extends scbFormField {
782
+
783
+ /**
784
+ * @param mixed $value
785
+ *
786
+ * @return array
787
+ */
788
+ public function validate( $value ) {
789
+ return array_intersect( array_keys( $this->choices ), (array) $value );
790
+ }
791
+
792
+ /**
793
+ * @param array $args
794
+ *
795
+ * @return string
796
+ */
797
+ protected function _render( $args ) {
798
+ $args = wp_parse_args( $args, array(
799
+ 'numeric' => false, // use numeric array instead of associative
800
+ 'checked' => null,
801
+ ) );
802
+
803
+ if ( ! is_array( $args['checked'] ) )
804
+ $args['checked'] = array();
805
+
806
+ $opts = '';
807
+ foreach ( $args['choices'] as $value => $title ) {
808
+ $single_input = scbFormField::_checkbox( array(
809
+ 'name' => $args['name'] . '[]',
810
+ 'type' => 'checkbox',
811
+ 'value' => $value,
812
+ 'checked' => in_array( $value, $args['checked'] ),
813
+ 'desc' => $title,
814
+ 'desc_pos' => 'after',
815
+ ) );
816
+
817
+ $opts .= str_replace( scbForms::TOKEN, $single_input, $args['wrap_each'] );
818
+ }
819
+
820
+ return scbFormField::add_desc( $opts, $args['desc'], $args['desc_pos'] );
821
+ }
822
+
823
+ /**
824
+ * @param array $args
825
+ * @param mixed $value
826
+ */
827
+ protected function _set_value( &$args, $value ) {
828
+ $args['checked'] = (array) $value;
829
+ }
830
+ }
831
+
832
+ /**
833
+ * Checkbox field.
834
+ */
835
+ class scbSingleCheckboxField extends scbFormField {
836
+
837
+ /**
838
+ * @param mixed $value
839
+ *
840
+ * @return boolean
841
+ */
842
+ public function validate( $value ) {
843
+ return (bool) $value;
844
+ }
845
+
846
+ /**
847
+ * @param array $args
848
+ *
849
+ * @return string
850
+ */
851
+ protected function _render( $args ) {
852
+ $args = wp_parse_args( $args, array(
853
+ 'value' => true,
854
+ 'desc' => null,
855
+ 'checked' => false,
856
+ 'extra' => array(),
857
+ ) );
858
+
859
+ $args['extra']['checked'] = $args['checked'];
860
+
861
+ if ( is_null( $args['desc'] ) && ! is_bool( $args['value'] ) )
862
+ $args['desc'] = str_replace( '[]', '', $args['value'] );
863
+
864
+ return scbFormField::_input_gen( $args );
865
+ }
866
+
867
+ /**
868
+ * @param array $args
869
+ * @param mixed $value
870
+ */
871
+ protected function _set_value( &$args, $value ) {
872
+ $args['checked'] = ( $value || ( isset( $args['value'] ) && $value == $args['value'] ) );
873
+ }
874
+ }
875
+
876
+ /**
877
+ * Wrapper field for custom callbacks.
878
+ */
879
+ class scbCustomField implements scbFormField_I {
880
+
881
+ protected $args;
882
+
883
+ /**
884
+ * @param array $args
885
+ */
886
+ function __construct( $args ) {
887
+ $this->args = wp_parse_args( $args, array(
888
+ 'render' => 'var_dump',
889
+ 'sanitize' => 'wp_filter_kses',
890
+ ) );
891
+ }
892
+
893
+ /**
894
+ * @param string $key
895
+ *
896
+ * @return mixed
897
+ */
898
+ public function __get( $key ) {
899
+ return $this->args[ $key ];
900
+ }
901
+
902
+ /**
903
+ * @param string $key
904
+ *
905
+ * @return boolean
906
+ */
907
+ public function __isset( $key ) {
908
+ return isset( $this->args[ $key ] );
909
+ }
910
+
911
+ /**
912
+ * @param mixed $value
913
+ *
914
+ * @return string
915
+ */
916
+ public function render( $value = null ) {
917
+ return call_user_func( $this->render, $value, $this );
918
+ }
919
+
920
+ /**
921
+ * @param mixed $value
922
+ *
923
+ * @return mixed
924
+ */
925
+ public function validate( $value ) {
926
+ return call_user_func( $this->sanitize, $value, $this );
927
+ }
928
+ }
929
+
scb/Options.php CHANGED
@@ -35,27 +35,25 @@ class scbOptions {
35
  }
36
 
37
  /**
38
- * Get option values for one, many or all fields
39
  *
40
- * @param string|array $field The field(s) to get
41
  * @return mixed Whatever is in those fields
42
  */
43
- public function get( $field = '' ) {
44
- $data = get_option( $this->key, array() );
45
 
46
- $data = array_merge( $this->defaults, $data );
47
-
48
- return $this->_get( $field, $data );
49
  }
50
 
51
  /**
52
- * Get default values for one, many or all fields
53
  *
54
- * @param string|array $field The field( s ) to get
55
  * @return mixed Whatever is in those fields
56
  */
57
- public function get_defaults( $field = '' ) {
58
- return $this->_get( $field, $this->defaults );
59
  }
60
 
61
  /**
@@ -89,7 +87,7 @@ class scbOptions {
89
  * @return bool
90
  */
91
  public function cleanup() {
92
- $this->update( $this->_clean( $this->get() ) );
93
  }
94
 
95
  /**
@@ -129,19 +127,7 @@ class scbOptions {
129
  return wp_array_slice_assoc( $data, array_keys( $this->defaults ) );
130
  }
131
 
132
- // Get one, more or all fields from an array
133
  private function &_get( $field, $data ) {
134
- if ( empty( $field ) )
135
- return $data;
136
-
137
- if ( is_string( $field ) )
138
- return $data[$field];
139
-
140
- foreach ( $field as $key )
141
- if ( isset( $data[$key] ) )
142
- $result[] = $data[$key];
143
-
144
- return $result;
145
  }
146
 
147
  // Magic method: $options->field
35
  }
36
 
37
  /**
38
+ * Get option values for one or all fields
39
  *
40
+ * @param string|array $field The field to get
41
  * @return mixed Whatever is in those fields
42
  */
43
+ public function get( $field = null, $default = null ) {
44
+ $data = array_merge( $this->defaults, get_option( $this->key, array() ) );
45
 
46
+ return scbForms::get_value( $field, $data, $default );
 
 
47
  }
48
 
49
  /**
50
+ * Get default values for one or all fields
51
  *
52
+ * @param string|array $field The field to get
53
  * @return mixed Whatever is in those fields
54
  */
55
+ public function get_defaults( $field = null ) {
56
+ return scbForms::get_value( $field, $this->defaults );
57
  }
58
 
59
  /**
87
  * @return bool
88
  */
89
  public function cleanup() {
90
+ $this->update( $this->get(), true );
91
  }
92
 
93
  /**
127
  return wp_array_slice_assoc( $data, array_keys( $this->defaults ) );
128
  }
129
 
 
130
  private function &_get( $field, $data ) {
 
 
 
 
 
 
 
 
 
 
 
131
  }
132
 
133
  // Magic method: $options->field
scb/PostMetabox.php ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class scbPostMetabox {
4
+
5
+ private $id, $title;
6
+
7
+ private $post_types;
8
+
9
+ private $post_data = array();
10
+
11
+ protected $actions = array( 'admin_enqueue_scripts', 'post_updated_messages' );
12
+
13
+ public function __construct( $id, $title, $args = array() ) {
14
+ $this->id = $id;
15
+ $this->title = $title;
16
+
17
+ $args = wp_parse_args( $args, array(
18
+ 'post_type' => 'post',
19
+ 'context' => 'advanced',
20
+ 'priority' => 'default'
21
+ ) );
22
+
23
+ if ( is_string( $args['post_type'] ) )
24
+ $args['post_type'] = array( $args['post_type'] );
25
+ $this->post_types = $args['post_type'];
26
+
27
+ $this->context = $args['context'];
28
+ $this->priority = $args['priority'];
29
+
30
+ add_action( 'load-post.php', array( $this, 'pre_register' ) );
31
+ add_action( 'load-post-new.php', array( $this, 'pre_register' ) );
32
+ }
33
+
34
+ final public function pre_register() {
35
+ if ( ! in_array( get_current_screen()->post_type, $this->post_types ) )
36
+ return;
37
+
38
+ if ( ! $this->condition() )
39
+ return;
40
+
41
+ if ( isset( $_GET['post'] ) )
42
+ $this->post_data = $this->get_meta( intval( $_GET['post'] ) );
43
+
44
+ add_action( 'add_meta_boxes', array( $this, 'register' ) );
45
+ add_action( 'save_post', array( $this, '_save_post' ), 10, 2 );
46
+
47
+ foreach ( $this->actions as $action ) {
48
+ if ( method_exists( $this, $action ) )
49
+ add_action( $action, array( $this, $action ) );
50
+ }
51
+ }
52
+
53
+ // Additional checks before registering the metabox
54
+ protected function condition() {
55
+ return true;
56
+ }
57
+
58
+ final public function register() {
59
+ add_meta_box( $this->id, $this->title, array( $this, 'display' ), null, $this->context, $this->priority );
60
+ }
61
+
62
+ public function before_display( $form_data, $post ) {
63
+ return $form_data;
64
+ }
65
+
66
+ public function display( $post ) {
67
+ $form_fields = $this->form_fields();
68
+ if ( ! $form_fields )
69
+ return;
70
+
71
+ $form_data = $this->post_data;
72
+ $error_fields = array();
73
+
74
+ if ( isset( $form_data['_error_data_' . $this->id ] ) ) {
75
+ $data = unserialize( $form_data['_error_data_' . $this->id ] );
76
+
77
+ $error_fields = $data['fields'];
78
+ $form_data = $data['data'];
79
+ }
80
+
81
+ $form_data = $this->before_display( $form_data, $post );
82
+
83
+ $this->before_form( $post );
84
+ echo $this->table( $form_fields, $form_data, $error_fields );
85
+ $this->after_form( $post );
86
+
87
+ delete_post_meta( $post->ID, '_error_data_' . $this->id );
88
+ }
89
+
90
+ public function table( $rows, $formdata, $errors = array() ) {
91
+ $output = '';
92
+ foreach ( $rows as $row ) {
93
+ $output .= $this->table_row( $row, $formdata, $errors );
94
+ }
95
+
96
+ $output = scbForms::table_wrap( $output );
97
+
98
+ return $output;
99
+ }
100
+
101
+ public function table_row( $row, $formdata, $errors = array() ) {
102
+ $input = scbForms::input( $row, $formdata );
103
+
104
+ // If row has an error, highlight it
105
+ $style = ( in_array( $row['name'], $errors ) ) ? 'style= "background-color: #FFCCCC"' : '';
106
+
107
+ return html( 'tr',
108
+ html( "th $style scope='row'", $row['title'] ),
109
+ html( "td $style", $input )
110
+ );
111
+ }
112
+
113
+ // Display some extra HTML before the form
114
+ public function before_form( $post ) { }
115
+
116
+ // Return the list of form fields
117
+ public function form_fields() {
118
+ return array();
119
+ }
120
+
121
+ // Display some extra HTML after the form
122
+ public function after_form( $post ) { }
123
+
124
+ // Makes sure that the saving occurs only for the post being edited
125
+ final public function _save_post( $post_id, $post ) {
126
+ if ( ! isset( $_POST['action'] ) || $_POST['action'] != 'editpost' )
127
+ return;
128
+
129
+ if ( $post_id != $_POST['post_ID'] )
130
+ return;
131
+
132
+ if ( ! in_array( $post->post_type, $this->post_types ) )
133
+ return;
134
+
135
+ $this->save( $post->ID );
136
+ }
137
+
138
+ protected function save( $post_id ) {
139
+ $form_fields = $this->form_fields();
140
+
141
+ $to_update = scbForms::validate_post_data( $form_fields );
142
+
143
+ // Filter data
144
+ $to_update = $this->before_save( $to_update, $post_id );
145
+
146
+ // Validate dataset
147
+ $is_valid = $this->validate_post_data( $to_update, $post_id );
148
+ if ( $is_valid instanceof WP_Error && $is_valid->get_error_codes() ) {
149
+
150
+ $error_data = array(
151
+ 'fields' => $is_valid->get_error_codes(),
152
+ 'data' => $to_update
153
+ );
154
+ update_post_meta( $post_id, '_error_data_' . $this->id, $error_data );
155
+
156
+ $location = add_query_arg( 'message', 1, get_edit_post_link( $post_id, 'url' ) );
157
+ wp_redirect( apply_filters( 'redirect_post_location', $location, $post_id ) );
158
+ exit;
159
+ }
160
+
161
+ foreach ( $to_update as $key => $value ) {
162
+ update_post_meta( $post_id, $key, $value );
163
+ }
164
+ }
165
+
166
+ protected function before_save( $post_data, $post_id ) {
167
+ return $post_data;
168
+ }
169
+
170
+ protected function validate_post_data( $post_data ) {
171
+ return false;
172
+ }
173
+
174
+ private function get_meta( $post_id ) {
175
+ $meta = get_post_custom( $post_id );
176
+ foreach ( $meta as $key => $values )
177
+ $meta[$key] = $meta[$key][0];
178
+
179
+ return $meta;
180
+ }
181
+ }
182
+
scb/Table.php CHANGED
@@ -45,11 +45,27 @@ function scb_register_table( $key, $name = false ) {
45
  $wpdb->$key = $wpdb->prefix . $name;
46
  }
47
 
48
- function scb_install_table( $key, $columns, $upgrade_method = 'dbDelta' ) {
 
 
 
 
 
 
 
49
  global $wpdb;
50
 
51
  $full_table_name = $wpdb->$key;
52
 
 
 
 
 
 
 
 
 
 
53
  $charset_collate = '';
54
  if ( $wpdb->has_cap( 'collation' ) ) {
55
  if ( ! empty( $wpdb->charset ) )
@@ -58,16 +74,18 @@ function scb_install_table( $key, $columns, $upgrade_method = 'dbDelta' ) {
58
  $charset_collate .= " COLLATE $wpdb->collate";
59
  }
60
 
61
- if ( 'dbDelta' == $upgrade_method ) {
 
 
62
  require_once ABSPATH . 'wp-admin/includes/upgrade.php';
63
- dbDelta( "CREATE TABLE $full_table_name ( $columns ) $charset_collate" );
64
  return;
65
  }
66
 
67
- if ( 'delete_first' == $upgrade_method )
68
  $wpdb->query( "DROP TABLE IF EXISTS $full_table_name;" );
69
 
70
- $wpdb->query( "CREATE TABLE IF NOT EXISTS $full_table_name ( $columns ) $charset_collate;" );
71
  }
72
 
73
  function scb_uninstall_table( $key ) {
45
  $wpdb->$key = $wpdb->prefix . $name;
46
  }
47
 
48
+ /**
49
+ * Runs the SQL query for installing/upgrading a table
50
+ *
51
+ * @param string $key The key used in scb_register_table()
52
+ * @param string $columns The SQL columns for the CREATE TABLE statement
53
+ * @param array $opts Various other options
54
+ */
55
+ function scb_install_table( $key, $columns, $opts = array() ) {
56
  global $wpdb;
57
 
58
  $full_table_name = $wpdb->$key;
59
 
60
+ if ( is_string( $opts ) ) {
61
+ $opts = array( 'upgrade_method' => $opts );
62
+ }
63
+
64
+ $opts = wp_parse_args( $opts, array(
65
+ 'upgrade_method' => 'dbDelta',
66
+ 'table_options' => '',
67
+ ) );
68
+
69
  $charset_collate = '';
70
  if ( $wpdb->has_cap( 'collation' ) ) {
71
  if ( ! empty( $wpdb->charset ) )
74
  $charset_collate .= " COLLATE $wpdb->collate";
75
  }
76
 
77
+ $table_options = $charset_collate . ' ' . $opts['table_options'];
78
+
79
+ if ( 'dbDelta' == $opts['upgrade_method'] ) {
80
  require_once ABSPATH . 'wp-admin/includes/upgrade.php';
81
+ dbDelta( "CREATE TABLE $full_table_name ( $columns ) $table_options" );
82
  return;
83
  }
84
 
85
+ if ( 'delete_first' == $opts['upgrade_method'] )
86
  $wpdb->query( "DROP TABLE IF EXISTS $full_table_name;" );
87
 
88
+ $wpdb->query( "CREATE TABLE IF NOT EXISTS $full_table_name ( $columns ) $table_options;" );
89
  }
90
 
91
  function scb_uninstall_table( $key ) {
scb/Util.php CHANGED
@@ -119,7 +119,7 @@ class scbUtil {
119
 
120
  // Return a standard admin notice
121
  function scb_admin_notice( $msg, $class = 'updated' ) {
122
- return "<div class='$class fade'><p>$msg</p></div>\n";
123
  }
124
 
125
  // Transform a list of objects into an associative array
@@ -137,6 +137,26 @@ function scb_list_fold( $list, $key, $value ) {
137
  return $r;
138
  }
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
  //_____Minimalist HTML framework_____
142
 
@@ -145,6 +165,8 @@ function scb_list_fold( $list, $key, $value ) {
145
  */
146
  if ( ! function_exists( 'html' ) ):
147
  function html( $tag ) {
 
 
148
  $args = func_get_args();
149
 
150
  $tag = array_shift( $args );
@@ -165,7 +187,7 @@ function html( $tag ) {
165
  list( $closing ) = explode( ' ', $tag, 2 );
166
  }
167
 
168
- if ( in_array( $closing, array( 'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta' ) ) ) {
169
  return "<{$tag} />";
170
  }
171
 
@@ -185,6 +207,18 @@ function html_link( $url, $title = '' ) {
185
  }
186
  endif;
187
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
  //_____Compatibility layer_____
190
 
119
 
120
  // Return a standard admin notice
121
  function scb_admin_notice( $msg, $class = 'updated' ) {
122
+ return html( "div class='$class fade'", html( "p", $msg ) );
123
  }
124
 
125
  // Transform a list of objects into an associative array
137
  return $r;
138
  }
139
 
140
+ /**
141
+ * Splits a list into sets, grouped by the result of running each value through $fn.
142
+ *
143
+ * @param array List of items to be partitioned
144
+ * @param callback Function that takes an element and returns a string key
145
+ */
146
+ function scb_list_group_by( $list, $fn ) {
147
+ $groups = array();
148
+
149
+ foreach ( $list as $item ) {
150
+ $key = call_user_func( $fn, $item );
151
+
152
+ if ( null === $key )
153
+ continue;
154
+
155
+ $groups[ $key ][] = $item;
156
+ }
157
+
158
+ return $groups;
159
+ }
160
 
161
  //_____Minimalist HTML framework_____
162
 
165
  */
166
  if ( ! function_exists( 'html' ) ):
167
  function html( $tag ) {
168
+ static $SELF_CLOSING_TAGS = array( 'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta' );
169
+
170
  $args = func_get_args();
171
 
172
  $tag = array_shift( $args );
187
  list( $closing ) = explode( ' ', $tag, 2 );
188
  }
189
 
190
+ if ( in_array( $closing, $SELF_CLOSING_TAGS ) ) {
191
  return "<{$tag} />";
192
  }
193
 
207
  }
208
  endif;
209
 
210
+ function scb_get_query_flags( $wp_query = null ) {
211
+ if ( !$wp_query )
212
+ $wp_query = $GLOBALS['wp_query'];
213
+
214
+ $flags = array();
215
+ foreach ( get_object_vars( $wp_query ) as $key => $val ) {
216
+ if ( 'is_' == substr( $key, 0, 3 ) && $val )
217
+ $flags[] = substr( $key, 3 );
218
+ }
219
+
220
+ return $flags;
221
+ }
222
 
223
  //_____Compatibility layer_____
224
 
scb/load.php CHANGED
@@ -1,8 +1,8 @@
1
  <?php
2
 
3
- $GLOBALS['_scb_data'] = array( 53, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
- 'scbWidget', 'scbAdminPage', 'scbBoxesPage',
6
  'scbCron', 'scbHooks',
7
  ) );
8
 
@@ -32,8 +32,10 @@ class scbLoad4 {
32
  add_action( 'activate_plugin', array( __CLASS__, 'delayed_activation' ) );
33
  }
34
 
35
- // TODO: don't load when activating a plugin ?
36
- add_action( 'plugins_loaded', array( __CLASS__, 'load' ), 9, 0 );
 
 
37
  }
38
 
39
  static function delayed_activation( $plugin ) {
1
  <?php
2
 
3
+ $GLOBALS['_scb_data'] = array( 58, __FILE__, array(
4
  'scbUtil', 'scbOptions', 'scbForms', 'scbTable',
5
+ 'scbWidget', 'scbAdminPage', 'scbBoxesPage', 'scbPostMetabox',
6
  'scbCron', 'scbHooks',
7
  ) );
8
 
32
  add_action( 'activate_plugin', array( __CLASS__, 'delayed_activation' ) );
33
  }
34
 
35
+ if ( did_action( 'plugins_loaded' ) )
36
+ self::load();
37
+ else
38
+ add_action( 'plugins_loaded', array( __CLASS__, 'load' ), 9, 0 );
39
  }
40
 
41
  static function delayed_activation( $plugin ) {
thumb.php CHANGED
@@ -13,6 +13,7 @@ Matt Mullenweg, creator of WordPress, on the whole issue: http://ma.tt/2011/08/t
13
 
14
  */
15
 
 
16
  define( 'FILE_CACHE_MAX_FILE_AGE', 2592000 );
17
  define( 'FILE_CACHE_SUFFIX', '.img' );
18
  define( 'FILE_CACHE_PREFIX', 'thumb' );
@@ -21,4 +22,4 @@ define( 'BROWSER_CACHE_MAX_AGE', 31536000 );
21
  define( 'MAX_WIDTH', 3000 );
22
  define( 'MAX_HEIGHT', 3000 );
23
 
24
- require( 'timthumb.php' );
13
 
14
  */
15
 
16
+ define( 'FILE_CACHE_DIRECTORY', dirname( __FILE__ ) . '/../cache' );
17
  define( 'FILE_CACHE_MAX_FILE_AGE', 2592000 );
18
  define( 'FILE_CACHE_SUFFIX', '.img' );
19
  define( 'FILE_CACHE_PREFIX', 'thumb' );
22
  define( 'MAX_WIDTH', 3000 );
23
  define( 'MAX_HEIGHT', 3000 );
24
 
25
+ require( dirname( __FILE__ ) . '/inc/timthumb.php' );